原文地址:WebGL学习(2) - 3D场景
经过前面WebGL学习(1) - 三角形的学习,我们已经掌握了webGL的基础知识,也已经能够画出最基本的图形,比如点,线,三角形,矩形等。有了2D绘图的基础,现在终于可以进入精彩的3D世界了,来看一下这一节要实现的3D的效果吧。
实际效果:webGL3D场景
webGL渲染流程
重温一下webGL的渲染流程,这一节在第3、4、5、6步骤需要学习新的内容。其中写入数据交叉存放缓冲区,设置隐藏面消除,清空深度缓冲都是比较简单的部分。重点和难点是在3D变换的环节,在理解了矩阵的原理基础上,这次使用了《WebGL编程指南》提供的矩阵操作库。
- 获取webGL绘图上下文
- 初始化着色器
- 创建、绑定缓冲区对象
3. 3D变换
4. 向顶点着色器和片元着色器写入数据(数据交叉存放缓冲区)
5. 设置canvas背景色,设置隐藏面消除
6. 清空canvas|清空深度缓冲 - 绘制
着色器
着色器代码修改为下面,我们现在需要为每个顶点都使用不同的颜色,所以使用到了varying限定符的变量,这个变量目的就是连接顶点和片元着色器,把顶点信息和颜色信息结合起来。看到顶点着色器和片元着色器都有的v_color变量了吗?其实就是通过全局变量传递。
顶点着色器
???<script type="x-shader/x-vertex" id="vs"> ???attribute vec4 a_Position; //顶点 ???uniform mat4 u_MvpMatrix;//模型视点投影矩阵 ???attribute vec4 a_Color; ???varying vec4 v_color;// 连接片元着色器 ???void main() { ????????gl_Position = u_MvpMatrix * a_Position; ???????v_color=a_Color;//传递给片元着色器变量 ???} ????</script>
片元着色器
???<script type="x-shader/x-fragment" id="fs"> ???precision mediump float; // 精度限定 ???varying vec4 v_color; //从顶点着色器接收 ???void main() { ???????gl_FragColor = v_color; ???} ???</script>
3D坐标系
第1、2、3步骤前面文章已经介绍,现在我们直接进入3D的环节。3D比2D主要就是多了深度信息,用坐标系来描述就是,除xy轴外,还多了z轴。webGL的坐标系跟我们web的坐标系是不一样的,首先它原点不是在左上角而是位于中间,xyz方向也不同。
视点和视线
接着引入一个概念,视点,也就是定义观察者的位置,观察者能看多远,观察者的方向,直接看图吧
上方向就是观测者从哪个方向看,(0,1,0)是正常的Y轴正方向,(1,0,0)就相当于物体向左旋90度,等于我们把头打横看物体。通过定义视点矩阵,我们看到的图形的形状会产生变化的,就和我们实际环境不同的角度位置观察同一物体是一样一样的。我们调用矩阵库中的方法,会产生出一个4X4的矩阵,具体中间的产生过程,可以看源代码。
???// (视点,观察目标点,上方向) ???setLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
投影可视空间
只有指定了可视空间,webGL才会去绘制图形,有两种类型:
一是盒状可视空间,由正射投影产生,它产生的图形,前后物体没有大小区别,都是一样高宽。
调用矩阵库的setOrtho方法,产生矩阵
???// 正视投影 (left,right,bottom,top,near,far), 组成一个正方体的可视空间 ????setOrtho(left, right, bottom, top, near, far);
二是四棱锥可视空间,由透视投影产生,透视投影产生的3D场景更加真实自然,它产生的图形具有近大远小的透视效果,当然性能消耗相对正射投影高一些。
调用setPerspective方法,同理生成矩阵
???// 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> ) ???setPerspective(fovy, aspect, near, far)
数据交叉存放缓冲区
我们既可以给不同的信息分别创建单独缓冲区,也可以给不同的信息创建同一块合用的缓冲区,前者适合数据量小的情况,我们现在实现第二种情况:给不同的信息创建一块缓冲区,并交叉存放。首先用一个数组同时存放顶点信息和顶点对应的颜色信息,接着创建缓冲区后调用gl.vertexAttribPointer(),该方法有定义每个分量的个数,每一行的个数以及偏移数,当然相邻顶点数和偏移量要乘以单位字节,具体看代码的注释。
???????/** ????????* 混合缓冲区(包括顶点,颜色),每一行前3个是顶点信息,后3个是颜色信息 ????????*/ ???????var verticeColors=new Float32Array([ ????????????0.0, ?1.0, ?-2.0, ?0.3, ?1.0, ?0.4, ????????????-0.5, -1.0, ?-2.0, ?0.3, ?1.0, ?0.4, ????????????0.5, -1.0, ?-2.0, ?1.0, ?0.4, ?0.4, ?????????????0.0, ?1.0, ?-1.0, ?1.0, ?1.0, ?0.4, ????????????-0.5, -1.0, ?-1.0, ?1.0, ?1.0, ?0.4, ????????????0.5, -1.0, ?-1.0, ?1.0, ?0.4, ?0.4, ?????????????0.0, ?1.0, ??0.0, ?0.4, ?0.4, ?1.0, ????????????-0.5, -1.0, ??0.0, ?0.4, ?0.4, ?1.0, ????????????0.5, -1.0, ??0.0, ?1.0, ?0.4, ?0.4, ????????]); ???????// 创建缓冲区 ???????if(!createBuffer(verticeColors)){ ???????????console.log('Failed to create the buffer object'); ???????????return; ???????} ???????// 每个元素的字节 ???????var FSIZE = verticeColors.BYTES_PER_ELEMENT; ???????// 获取顶点位置 ???????var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); ???????// (地址,每个顶点分量的个数<1-4>,数据类型<整形,符点等>,是否归一化,指定相邻两个顶点间字节数<默认0>,指定缓冲区对象偏移字节数量<默认0>) ???????gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0); ???????// Enable the assignment to a_Position variable ???????gl.enableVertexAttribArray(a_Position); ???????// 获取a_Color变量的存储地址并赋值 ???????var a_Color = gl.getAttribLocation(gl.program, 'a_Color'); ???????gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE); ???????gl.enableVertexAttribArray(a_Color);
3D相关的其他设置
开启隐藏面消除可以减少渲染量,提高性能,同时还可以避免顺序不一致时,后面的图形盖住前面的图形。而多边形偏移,可以避免深度很接近的两个图形产生冲突。当然每次重新渲染的时候,在清屏的同时清除深度缓冲,具体实现请看代码。
???// 开启隐藏面消除 ???gl.enable(gl.DEPTH_TEST); ???//清屏|清深度缓冲 ???gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); ???// 启用多边形偏移,避免深度冲突 ???gl.enable(gl.POLYGON_OFFSET_FILL); ???//设置偏移量 ???gl.polygonOffset(1.0, 1.0);
执行动画
来看一下我们执行动画的部分,首先设置好用于位移旋转的模型矩阵,然后依次产生视点矩阵,投影矩阵,接着把它们相乘产生出mvp矩阵,然后传入变量,最后绘图。在绘制完第一组图形的时候,将前面的mvp矩阵再左移2个单位,再绘制一遍,于是就产生出了第二组图形。具体的逻辑情况代码注释。
???var angle=0; ???// 执行动画 ???(function animate(){ ???????// 旋转位移 等于绕原点Y旋转 ???????modelMatrix.setRotate((angle++)%360,0,1,0); ???????modelMatrix.translate(1, 0, 1); ???????// (视点,观察目标点,上方向) ???????viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0); ???????// 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> ) ???????projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); ???????// 矩阵相乘 ???????mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); ???????// 赋值 ???????gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); ???????//清屏|清深度缓冲 ???????gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); ???????// 启用多边形偏移,避免深度冲突 ???????gl.enable(gl.POLYGON_OFFSET_FILL); ???????// (基本图形,第几个顶点,执行几次),修改基本图形项可以生成点,线,三角形,矩形,扇形等 ???????gl.drawArrays(gl.TRIANGLES, 0, 9); ???????//位移后,再将前面3个三角形重新绘制 ???????modelMatrix.translate(-2, 0, 0); ???????mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); ???????gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); ???????//设置偏移量 ???????gl.polygonOffset(1.0, 1.0); ???????gl.drawArrays(gl.TRIANGLES, 0, 9); ????????requestAnimationFrame(animate); ???}());
总结
学习完3D场景后,我们又再一次领略到了线性代数中矩阵在图形学中的重要作用。3D的矩阵转换才是需要空间思维和深入理解的部分,其他地方说实话就是学习如何调用api。
最后,献上主体的全部代码
???var canvas=document.getElementById('canvas'), ???????gl=get3DContext(canvas,true); ???function main() { ???????if (!gl) { ???????????console.log('Failed to get the rendering context for WebGL'); ???????????return; ???????} ???????if (!createShaders(gl, 'fs', 'vs')) { ???????????console.log('Failed to intialize shaders.'); ???????????return; ???????} ???????/** ????????* 混合缓冲区(包括顶点,颜色) ????????*/ ???????var verticeColors=new Float32Array([ ????????????0.0, ?1.0, ?-2.0, ?0.3, ?1.0, ?0.4, ???????????-0.5, -1.0, ?-2.0, ?0.3, ?1.0, ?0.4, ????????????0.5, -1.0, ?-2.0, ?1.0, ?0.4, ?0.4, ?????????????0.0, ?1.0, ?-1.0, ?1.0, ?1.0, ?0.4, ???????????-0.5, -1.0, ?-1.0, ?1.0, ?1.0, ?0.4, ????????????0.5, -1.0, ?-1.0, ?1.0, ?0.4, ?0.4, ?????????????0.0, ?1.0, ??0.0, ?0.4, ?0.4, ?1.0, ???????????-0.5, -1.0, ??0.0, ?0.4, ?0.4, ?1.0, ????????????0.5, -1.0, ??0.0, ?1.0, ?0.4, ?0.4, ????????]); ???????// 创建缓冲区 ???????if(!createBuffer(verticeColors)){ ???????????console.log('Failed to create the buffer object'); ???????????return; ???????} ???????// 每个元素的字节 ???????var FSIZE = verticeColors.BYTES_PER_ELEMENT; ???????// 获取顶点位置 ???????var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); ???????// (地址,每个顶点分量的个数<1-4>,数据类型<整形,符点等>,是否归一化,指定相邻两个顶点间字节数<默认0>,指定缓冲区对象偏移字节数量<默认0>) ???????gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0); ???????// Enable the assignment to a_Position variable ???????gl.enableVertexAttribArray(a_Position); ???????// 获取a_Color变量的存储地址并赋值 ???????var a_Color = gl.getAttribLocation(gl.program, 'a_Color'); ???????gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 2*FSIZE); ???????gl.enableVertexAttribArray(a_Color); ???????var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); ???????if(!u_MvpMatrix) { ????????????console.log('Failed to get the storage location of u_MvpMatrix'); ???????????return; ???????} ???????// 设置背景颜色 ???????gl.clearColor(0.0, 0.0, 0.0, 1.0); ???????// 开启隐藏面消除 ???????gl.enable(gl.DEPTH_TEST); ???????var modelMatrix = new Matrix4(); // 模型矩阵 ???????var viewMatrix = new Matrix4(); ?// 视点矩阵 ???????var projMatrix = new Matrix4(); ?// 投影矩阵 ???????var mvpMatrix = new Matrix4(); ??// 用于相乘用 ???????var angle=0; ???????// 执行动画 ???????(function animate(){ ???????????// 旋转位移 等于绕原点Y旋转 ???????????modelMatrix.setRotate((angle++)%360,0,1,0); ???????????modelMatrix.translate(1, 0, 1); ???????????// (视点,观察目标点,上方向) ???????????viewMatrix.setLookAt(-0.25, -0.25, 5, 0, 0, -100, 0, 1, 0); ???????????// 投影矩阵(fov可视空间底面和顶面夹角<大于0>,近裁截面宽高比,近裁截面位置<大于0>,远裁截面位置<大于0> ) ???????????projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); ???????????// 矩阵相乘 ???????????mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); ???????????// 赋值 ???????????gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); ???????????//清屏|清深度缓冲 ???????????gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); ???????????// 启用多边形偏移,避免深度冲突 ???????????gl.enable(gl.POLYGON_OFFSET_FILL); ???????????// (基本图形,第几个顶点,执行几次),修改基本图形项可以生成点,线,三角形,矩形,扇形等 ???????????gl.drawArrays(gl.TRIANGLES, 0, 9); ???????????//位移后,再将前面3个三角形重新绘制 ???????????modelMatrix.translate(-2, 0, 0); ???????????mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); ???????????gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); ???????????//设置偏移量 ???????????gl.polygonOffset(1.0, 1.0); ???????????gl.drawArrays(gl.TRIANGLES, 0, 9); ???????????requestAnimationFrame(animate); ???????}()); ???} ???main();
WebGL学习(2) - 3D场景
原文地址:http://www.cnblogs.com/edwardloveyou/p/7833639.html