目录javascript
在上一篇教程《WebGL简易教程(五):图形变换(模型、视图、投影变换)》中,详细讲解了OpenGL\WebGL关于绘制场景的模型变换、视图变换以及投影变换的过程。不过那篇教程是纯理论知识,这里就具体结合一个实际的例子,进一步理解WebGL中是如何经过图形变换让一个真正的三维场景显示出来。html
继续改进以前的代码,此次就更进一步,在一个场景中绘制了三个三角形。java
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Hello Triangle</title> </head> <body onload="main()"> <canvas id="webgl" width="400" height="400"> Please use a browser that supports "canvas" </canvas> <script src="../lib/webgl-utils.js"></script> <script src="../lib/webgl-debug.js"></script> <script src="../lib/cuon-utils.js"></script> <script src="../lib/cuon-matrix.js"></script> <script src="Triangle_MVPMatrix.js"></script> </body> </html>
与之间的代码相比,这段代码主要是引入了一个cuon-matrix.js,这个是一个图形矩阵的处理库,可以方便与GLSL进行交互。web
// 顶点着色器程序 var VSHADER_SOURCE = 'attribute vec4 a_Position;\n' + // attribute variable 'attribute vec4 a_Color;\n' + 'uniform mat4 u_MvpMatrix;\n' + 'varying vec4 v_Color;\n' + 'void main() {\n' + ' gl_Position = u_MvpMatrix * a_Position;\n' + // Set the vertex coordinates of the point ' v_Color = a_Color;\n' + '}\n'; // 片元着色器程序 var FSHADER_SOURCE = 'precision mediump float;\n' + 'varying vec4 v_Color;\n' + 'void main() {\n' + ' gl_FragColor = v_Color;\n' + '}\n'; function main() { // 获取 <canvas> 元素 var canvas = document.getElementById('webgl'); // 获取WebGL渲染上下文 var gl = getWebGLContext(canvas); if (!gl) { console.log('Failed to get the rendering context for WebGL'); return; } // 初始化着色器 if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.log('Failed to intialize shaders.'); return; } // 设置顶点位置 var n = initVertexBuffers(gl); if (n < 0) { console.log('Failed to set the positions of the vertices'); return; } //设置MVP矩阵 setMVPMatrix(gl,canvas); // 指定清空<canvas>的颜色 gl.clearColor(0.0, 0.0, 0.0, 1.0); // 开启深度测试 gl.enable(gl.DEPTH_TEST); // 清空颜色和深度缓冲区 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 绘制三角形 gl.drawArrays(gl.TRIANGLES, 0, n); } //设置MVP矩阵 function setMVPMatrix(gl,canvas) { // Get the storage location of u_MvpMatrix var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); if (!u_MvpMatrix) { console.log('Failed to get the storage location of u_MvpMatrix'); return; } //模型矩阵 var modelMatrix = new Matrix4(); modelMatrix.setTranslate(0.75, 0, 0); //视图矩阵 var viewMatrix = new Matrix4(); // View matrix viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0); //投影矩阵 var projMatrix = new Matrix4(); // Projection matrix projMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100); //MVP矩阵 var mvpMatrix = new Matrix4(); mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); } // function initVertexBuffers(gl) { // 顶点坐标和颜色 var verticesColors = new Float32Array([ 0.0, 1.0, -4.0, 0.4, 1.0, 0.4, //绿色在后 -0.5, -1.0, -4.0, 0.4, 1.0, 0.4, 0.5, -1.0, -4.0, 1.0, 0.4, 0.4, 0.0, 1.0, -2.0, 1.0, 1.0, 0.4, //黄色在中 -0.5, -1.0, -2.0, 1.0, 1.0, 0.4, 0.5, -1.0, -2.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, ]); // var n = 9; // 点的个数 var FSIZE = verticesColors.BYTES_PER_ELEMENT; //数组中每一个元素的字节数 // 建立缓冲区对象 var vertexBuffer = gl.createBuffer(); if (!vertexBuffer) { console.log('Failed to create the buffer object'); return -1; } // 将缓冲区对象绑定到目标 gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // 向缓冲区对象写入数据 gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW); //获取着色器中attribute变量a_Position的地址 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); if (a_Position < 0) { console.log('Failed to get the storage location of a_Position'); return -1; } // 将缓冲区对象分配给a_Position变量 gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0); // 链接a_Position变量与分配给它的缓冲区对象 gl.enableVertexAttribArray(a_Position); //获取着色器中attribute变量a_Color的地址 var a_Color = gl.getAttribLocation(gl.program, 'a_Color'); if (a_Color < 0) { console.log('Failed to get the storage location of a_Color'); return -1; } // 将缓冲区对象分配给a_Color变量 gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3); // 链接a_Color变量与分配给它的缓冲区对象 gl.enableVertexAttribArray(a_Color); // 解除绑定 gl.bindBuffer(gl.ARRAY_BUFFER, null); return n; }
相比以前的代码,主要作了3点改进:编程
以前绘制的三角形,只有X坐标和Y坐标,Z值坐标自动补足为默认为0的。在这里会绘制了3个三角形,每一个三角形的深度不一样。以下代码所示,定义了3个三角形9个点,每一个点包含xyz信息和rgb信息:canvas
// 顶点坐标和颜色 var verticesColors = new Float32Array([ 0.0, 1.0, -4.0, 0.4, 1.0, 0.4, //绿色在后 -0.5, -1.0, -4.0, 0.4, 1.0, 0.4, 0.5, -1.0, -4.0, 1.0, 0.4, 0.4, 0.0, 1.0, -2.0, 1.0, 1.0, 0.4, //黄色在中 -0.5, -1.0, -2.0, 1.0, 1.0, 0.4, 0.5, -1.0, -2.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, ]);
这意味着与着色器传输变量的函数gl.vertexAttribPointer()的参数也得相应的变化。注意要深刻理解这个函数每一个参数表明的含义:数组
// ... // 将缓冲区对象分配给a_Position变量 gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0); // ... // 将缓冲区对象分配给a_Color变量 gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
在默认状况下,WebGL是根据顶点在缓冲区的顺序来进行绘制的,后绘制的图形会覆盖已经绘制好的图形。可是这样每每与实际物体遮挡状况不一样,形成一些很怪异的现象,好比远的物体反而遮挡了近的物体。因此WebGL提供了一种深度检测(DEPTH_TEST)的功能,启用该功能就会检测物体(实际是每一个像素)的深度,来决定是否绘制。其启用函数为:
除此以外,还应该注意在绘制每一帧以前都应该清除深度缓冲区(depth buffer)。WebGL有多种缓冲区。咱们以前用到的与顶点着色器交互的缓冲区对象就是顶点缓冲区,每次从新绘制刷新的就是颜色缓冲区。深度缓冲区记录的就是每一个几何图形的深度信息,每绘制一帧,都应清除深度缓冲区:
在本例中的相关代码为:浏览器
// ... // 开启深度测试 gl.enable(gl.DEPTH_TEST); // 清空颜色和深度缓冲区 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // ...
在上一篇教程中提到过,WebGL的任何图形变换过程影响的都是物体的顶点,模型变换、视图变换、投影变换都是在顶点着色器中实现的。因为每一个顶点都是要进行模型视图投影变换的,因此能够合并成一个MVP矩阵,将其传入到顶点着色器中的:函数
//... 'uniform mat4 u_MvpMatrix;\n' + 'void main() {\n' + ' gl_Position = u_MvpMatrix * a_Position;\n' + // Set the vertex coordinates of the point //... '}\n';
在函数setMVPMatrix()中,建立了MVP矩阵,并将其传入到着色器:测试
//设置MVP矩阵 function setMVPMatrix(gl,canvas) { // Get the storage location of u_MvpMatrix var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); if (!u_MvpMatrix) { console.log('Failed to get the storage location of u_MvpMatrix'); return; } //模型矩阵 var modelMatrix = new Matrix4(); modelMatrix.setTranslate(0.75, 0, 0); //视图矩阵 var viewMatrix = new Matrix4(); // View matrix viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0); //投影矩阵 var projMatrix = new Matrix4(); // Projection matrix projMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100); //MVP矩阵 var mvpMatrix = new Matrix4(); mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); }
在上述代码中,依次分别设置了:
三者级联,获得MVP矩阵,将其传入到顶点着色器中。
用浏览器打开Triangle_MVPMatrix.html,就会发现浏览器页面显示了一个由远及近,近大远小的三个三角形。如图所示:
原本部分代码和插图来自《WebGL编程指南》,源代码连接:https://share.weiyun.com/5VjlUKo ,密码:sw0x2x。会在此共享目录中持续更新后续的内容。