网站营销软件,做企业网站需要哪些,设计师找灵感的网站,网站优化推广培训从本篇起#xff0c;我们将正式进入webgl的3D世界本篇涵盖的内容包括#xff1a;webgl它在干啥#xff1f;如何画一个正方体#xff1f;如何成为一个“有深度”的正方体#xff1f;正方体要离家出走了#xff01;webgl它在干啥#xff1f;首先我们需要知道webgl的世界其…从本篇起我们将正式进入webgl的3D世界本篇涵盖的内容包括webgl它在干啥如何画一个正方体如何成为一个“有深度”的正方体正方体要离家出走了webgl它在干啥首先我们需要知道webgl的世界其实是一个x[-1,1],y[-1,1],z[-1,1]的小世界所有在这个长度2的世界中的物体才能被显示。如图所示黄色区域是webgl的[-1,1]区域上方的球和下方的长方体都完全在这个区域内可以完全被显示而中间的立方体这是部分显示从正面看就会是段棒子和球完全显示常棒子超出区域被截掉简单说来这个过程中webgl把三纬空间xyz[-11]压扁成xy[1-1]再根据实际画布的尺寸绘制到canvas上这个过程就是光栅化。不过如果三纬空间xyz[-11]中的物体如图所示而canvas是2:1的尺寸的话实际得到的图片会是这样的那么为啥我平常用的canvas不会变形请耐心看到文末你会有答案的如何画一个正方体本篇我们并不会在作色器上大动手脚所以按照老方法做一遍就可以了不懂得可以看看《前端图形学从入门到放弃》第一篇。我们对作色器的唯一改造就是开放了标示深度的z值!-- 一个顶点着色器提供裁剪空间坐标值 --
script idvertex-shader-2d typenotjsattribute vec3 a_position;uniform mat4 u_matrix;// 这个是后续空间变化需要的这一步void main() {vec4 position u_matrix * vec4(a_position,1.0);gl_Position vec4(position.xyz,1.0);}
/script
!-- 一个片断着色器提供颜色值 --
script idfragment-shader-2d typenotjsprecision mediump float;void main() {// 输出颜色固定即可我选的是(1.0,0.1,.45)rgb都除以255gl_FragColor vec4(vec3(1.0,0.1,.45), 1.0); // }/script我们当然可以使用gl.drawArrays方法继续绘制但对于数量更多的图形这个方法其实不够高效想象一下有如图ABCD四个点组成了两个三角形对于gl.drawArrays我们需要向buffer中传入ABC后再传入BCD绘制两个三角形但buffer就会重复存储点BC目前还只有位置坐标如果是复杂的场景一个点上除了位置信息外还会包含颜色法线等一些列数据这就相当浪费存储空间了。所以我们采用gl.drawElements方法。顶点数据还是如前往buffer中丢只不过每个点只传入一次例如我们需要绘制一个以[000]为中心边长是0.4的正方形他的的八个顶点组成的数据是var data new Float32Array([0.2, 0.2, 0.2,-0.2, 0.2, 0.2,-0.2, -0.2, 0.2,0.2, -0.2, 0.2,0.2, 0.2, -0.2,-0.2, 0.2, -0.2,-0.2, -0.2, -0.2,0.2, -0.2, -0.2,
]);
这时我们还需要一个索引数组来标识用哪些点围城图形。//顶点索引数组
var indexes new Uint8Array([//右侧四个点0, 1, 2, 3,//左侧四个顶点4, 5, 6, 7,//左右对应关系0, 4,1, 5,2, 6,3, 7
]);
然后把这两个数据丢给buffer var indexesBuffer gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW);var vBuffer gl.createBuffer();// 顶点需要是 ARRAY_BUFFERgl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
最后就是绘制//LINE_LOOP模式绘制右侧四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0);
//LINE_LOOP模式从第五个点开始绘制左侧四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4);
//LINES模式绘制连线左右点连线
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);
这样运行一遍会得到如下结果奇怪为什么只有一个正方形说好的3D这是因为我们现在还是正交视图后面的点完全被前面的点挡住了所以我们需要移动这个立方体。让后面的点被看到所以接下来我们创建6个滑块分别控制正方形向xyz移动和绕xyz旋转。你怎么实现都可以div idcontroldiv classitem item-xspanx:/spaninput typerange iditem-x value0 min-1 max1 step0.01 /divdiv classitem item-yspany:/spaninput typerange iditem-y value0 min-1 max1 step0.01 /divdiv classitemspanz:/spaninput typerange iditem-z value0 min-1 max1 step0.01 /divdiv classitem item-xspanx轴角度:/spaninput typerange iditem-r-x value0 min-3.14 max3.14 step0.001 /divdiv classitem item-yspany轴角度:/spaninput typerange iditem-r-y value0 min-3.14 max3.14 step0.001 /divdiv classitemspanz轴角度:/spaninput typerange iditem-r-z value0 min-3.14 max3.14 step0.001 /div/div而通过这些数据我们可以组成一个矩阵对立方体的点进行变换。只是从《前端图形学从入门到放弃》002第二篇的2D支持齐次变换的3维矩阵变成了3D支持齐次变换的4维矩阵。他们分别是一个平移矩阵和3个旋转矩阵var m4 { // 用来存放三位变换方法的对象transform: function (x, y, z) {return [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,x, y, z, 1,];},rotateX: function (deg) {var c Math.cos(deg);var s Math.sin(deg);return [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,]},rotateY: function (deg) {var c Math.cos(deg);var s Math.sin(deg);return [c, 0, -s, 0,0, 1, 0, 0,s, 0, c, 0,0, 0, 0, 1,]},rotateZ: function (deg) {var c Math.cos(deg);var s Math.sin(deg);return [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,]},
}
当然我们还需要一个矩阵的乘法方法multiply以及可以支持多个矩阵连乘的multiplys方法var m4 {
// .......multiply: function (a, b) {var a00 a[0 * 4 0];var a01 a[0 * 4 1];var a02 a[0 * 4 2];var a03 a[0 * 4 3];var a10 a[1 * 4 0];var a11 a[1 * 4 1];var a12 a[1 * 4 2];var a13 a[1 * 4 3];var a20 a[2 * 4 0];var a21 a[2 * 4 1];var a22 a[2 * 4 2];var a23 a[2 * 4 3];var a30 a[3 * 4 0];var a31 a[3 * 4 1];var a32 a[3 * 4 2];var a33 a[3 * 4 3];var b00 b[0 * 4 0];var b01 b[0 * 4 1];var b02 b[0 * 4 2];var b03 b[0 * 4 3];var b10 b[1 * 4 0];var b11 b[1 * 4 1];var b12 b[1 * 4 2];var b13 b[1 * 4 3];var b20 b[2 * 4 0];var b21 b[2 * 4 1];var b22 b[2 * 4 2];var b23 b[2 * 4 3];var b30 b[3 * 4 0];var b31 b[3 * 4 1];var b32 b[3 * 4 2];var b33 b[3 * 4 3];return [b00 * a00 b01 * a10 b02 * a20 b03 * a30,b00 * a01 b01 * a11 b02 * a21 b03 * a31,b00 * a02 b01 * a12 b02 * a22 b03 * a32,b00 * a03 b01 * a13 b02 * a23 b03 * a33,b10 * a00 b11 * a10 b12 * a20 b13 * a30,b10 * a01 b11 * a11 b12 * a21 b13 * a31,b10 * a02 b11 * a12 b12 * a22 b13 * a32,b10 * a03 b11 * a13 b12 * a23 b13 * a33,b20 * a00 b21 * a10 b22 * a20 b23 * a30,b20 * a01 b21 * a11 b22 * a21 b23 * a31,b20 * a02 b21 * a12 b22 * a22 b23 * a32,b20 * a03 b21 * a13 b22 * a23 b23 * a33,b30 * a00 b31 * a10 b32 * a20 b33 * a30,b30 * a01 b31 * a11 b32 * a21 b33 * a31,b30 * a02 b31 * a12 b32 * a22 b33 * a32,b30 * a03 b31 * a13 b32 * a23 b33 * a33,];},multiplys: function (list) {var that this;return list.reduce(function (a, b) {return that.multiply(a, b);})},
// .......
}
要完成对立方体的控制我们需要把渲染写入一个循环中 var xNode document.querySelector(#item-x);var yNode document.querySelector(#item-y);var zNode document.querySelector(#item-z);var rXNode document.querySelector(#item-r-x);var rYNode document.querySelector(#item-r-y);var rZNode document.querySelector(#item-r-z);// 。。。。。loop();function loop() {var mx m4.rotateX(rXNode.value); var my m4.rotateY(rYNode.value);var mz m4.rotateZ(rZNode.value);var mTransform m4.transform(xNode.value,yNode.value,zNode.value);var matrix m4.multiplys([mTransform,mz,my,mx]);gl.uniformMatrix4fv(u_matrix, false, matrix);// 把矩阵传入顶点作色器gl.clearColor(0.75, 0.85, 0.8, 1.0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//LINE_LOOP模式绘制右侧四个点gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0);//LINE_LOOP模式从第五个点开始绘制左侧四个点gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4);//LINES模式绘制连线左右点连线gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);requestAnimationFrame(loop)}这样一来我们就能看到立方体的屁股了知乎视频www.zhihu.com如何成为一个“有深度”的正方体虽然我们实现了3D效果但这种正交效果并不会有日常我们见到的透视中的近大远小。我们要如何实现呢我们先来想想近大远小是什么近大远小不就是近处的看起来大一点远处的看起来小一点么。那么最直接的近大远小就是根据z值大小去缩放点的位置。这里我们添加一个控制点来缩放透视程度div classitemspanz透视参数:/spaninput typerange iditem-z-factor value0 min0 max2 step0.001 /div
// .......
script
//........
var zFactorNode document.querySelector(#item-z-factor)
// ........
/script然后我们把这个变换写成一个矩阵的形式var zFactorMatrix [1,0,0,0,0,1,0,0,0,0,1,zFactorNode.value,0,0,0,1
];
var matrix m4.multiplys([zFactorMatrix,mTransform,mz,my,mx]);
这样通过调节z透视参数我们的立方体就有了纵深感知乎视频www.zhihu.com需要注意的是xyz经过zFactorMatrix处理后没有变化out_x inx000out_y in_y000out_z in_z000但w会变成out_w in_w1给 gl_Position 的 x,y,z,w 值自动除以 w所以所有点的xy才会受到z的控制。但实际开发中我们不会使用zFactor来控制深度。而是用透视矩阵不过在此之前我们先来解决物体超出xyz[-1,1]空间的问题。正方体要离家出走了例如我把立方体变成一个边长20中心在(0070)的立方体var data new Float32Array([10, 10, 80,-10, 10, 80,-10, -10, 80,10, -10, 80,10, 10, 60,-10, 10, 60,-10, -10, 60,10, -10, 60,
]);
它立刻消失在屏幕上。所以我们现在要做的是指定一个区域假设是以(0070)为中心变成是50的空间图中黑色区域将其矩阵变换到空间[-11]之中。第一步我们自然是要把这个空间移动到(000)第二部就是把这个空间缩小到[-1,1]这里我们在m4对象中再创建一个方法orthographic它接受6个参数t, b, l, r, n, f分别所需要呈现空间的yxz的最小和最大值对于以(0070)为中心变成是50的空间这6个参数是25,-25,25,-25,45,95var m4 {
// ......orthographic: function (t, b, l, r, n, f) {//先把空间移动到000var trans [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,-(r l) / 2, -(t b) / 2, -(n f) / 2, 1,]//在把空间缩放到[-1,1]var scale [2 / (r - l), 0, 0, 0,0, 2 / (t - b), 0, 0,0, 0, 2 / (n - f), 0,0, 0, 0, 1,]return this.multiply(scale, trans);},
//......
}
大家可以用原始空间的顶点带入矩阵计算看看是不是都被移动到了[-1,1]的八个顶点上。这样我们在loop中再创建一个矩阵var Ortho m4.orthographic(25,-25,25,-25,45,95);
而此时传入作色器的matrix矩阵将变成var matrix m4.multiplys([Ortho, mTransform, mx, my, mz]);
而运行后通过调整参数我们又得到立方体这样我们就实现了将空间中任意区域的几何体画到canvas上了虽然如此但我们的透视又消失了回到了正交的状态而且旋转的时候物体好像并不是绕着自己的中心在转这又是怎么回事由于篇幅有限这些事我们留到下回再说吧下回我们将解决如何加入透视如何让物体绕着自己旋转如何让相机移动与旋转如何盯住物体参考资料https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-3d-perspective.htmlwebglfundamentals.orgWebGL 三维透视投影WebGL 三维透视投影webglfundamentals.orgMarschner, S., Shirley, P. (2018). Fundamentals of computer graphics. Place of publication not identified: A K Peters/CRC Press.