平移 Translation

如果你要把下图的点A(x, y, z)移到A'(x', y', z'),怎么做呢?把坐标分别加上对应的偏移值,就可以了

x' = x + dx
y' = y + dy
z' = z + dz

那如果你要△ABC移到△A'B'C'的位置呢?那就需要把每一个点都加上对应的偏移值。在 WebGL 里,我们对 gl_Position 里的每个顶点坐标都加上在x轴、y轴和z轴的偏移值dx、dy、dz,就能够实现整个图形的偏移 Thanslation。 而因为每个点的对应偏移分量都是一致的, 所以我们要使用 uniform 变量来实现。代码如下,原理是在 GLSL ES 中的 main() 函数里写上一个 attribute 变量再加上一个 uniform 变量,然后赋值给顶点着色器里的 gl_Position。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; uniform vec4 u_translate; void main(){ gl_Position = a_Position + u_translate; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> void main(){ gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0); } </script> function init(){ var canvas = document.getElementById('canvas'), gl = canvas.getContext('webgl'); if(!gl){ console.log('Failed to get WebGL.'); return; } if(!initShaders(gl, "vertexShader", "fragmentShader")){ console.log('Failed to init Shaders.'); return; } // 获取 attribute, uniform 变量的存储地址 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'), u_translate = gl.getUniformLocation(gl.program, 'u_translate'); if(a_Position < 0 || !u_translate){ console.log('Failed to get the location'); console.log(a_Position, u_translate); return; } var vertics = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]), dx = 0.3, dy = 0.3, dz = 0.0; // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, vertics, a_Position, 2); // 对 uniform 变量赋值 gl.uniform4f(u_translate, dx, dy, dz, 0.0); // 设置背景色, 清空颜色缓冲区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 画三角形 gl.drawArrays(gl.TRIANGLES, 0, 3); } function initVertexBuffer(gl, data, location, size){ var vertexBuffer = gl.createBuffer(); if(!vertexBuffer){ console.log('Failed to create the buffer object.'); return ; } gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); gl.bufferData( gl.ARRAY_BUFFER, data, gl.STATIC_DRAW ); gl.vertexAttribPointer( location, size, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray( location ); }

平移矩阵

下面分别是三角形在z轴、x轴、y轴上平移的效果,以及对应的矩阵。但在 WebGL 里矩阵要按列主序重写。这时,GLSL ES 里的 Vertex Shader 中声明一个类型是 mat4 的 uniform 变量,它是一个 4×4 矩阵。在 main() 函数里进行矩阵运算 u_translationMatrix * a_Position 再赋值给 gl_Position。而在 JavaScript 里对 uniform 传 mat4 的值就用到 gl.uniformMatrix4fv( location, false, array )。感觉它好像比 gl_Position = a_Position + u_translate 的运算复杂一些, 但如果跟旋转 Rotation缩放 Scale 在一起使用时,矩阵就方便多了。 从图中也可以看出,在没有相交的透视矩阵和深度计算时,修改 dz 对效果没有影响。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec4 a_Color; uniform mat4 u_translationMatrix; varying vec4 v_Color; void main(){ gl_Position = u_translationMatrix * a_Position; v_Color = a_Color; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> precision mediump float; varying vec4 v_Color; void main(){ gl_FragColor = v_Color; } </script> function init(){ ...... // 获取 attribute, uniform 变量的存储地址 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'), a_Color = gl.getAttribLocation(gl.program, 'a_Color'), u_translationMatrix = gl.getUniformLocation(gl.program, 'u_translationMatrix'); if(a_Position < 0 || a_Color < 0 || !u_translationMatrix){ console.log('Failed to get the location'); console.log(a_Position, a_Color, u_translationMatrix); return; } var verticsColor = new Float32Array([ 0.0, 0.5, 1.0, 0.0, 0.0, 1.0, -0.5, -0.4, 0.0, 1.0, 0.0, 1.0, 0.5, -0.4, 0.0, 0.0, 1.0, 1.0]); var element_size = verticsColor.BYTES_PER_ELEMENT; var dx = 0.1, dy = 0.3, dz = 0.5; var translateMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, dx, dy, dz, 1.0 ]); // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, verticsColor, a_Position, a_Color, element_size); // 对 uniform 变量赋值 gl.uniformMatrix4fv(u_translationMatrix, false, translateMatrix); // 设置背景色, 清空颜色缓冲区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 画三角形 gl.drawArrays(gl.TRIANGLES, 0, 3); } function initVertexBuffer(gl, data, a_Position, a_Color, size){ var vertexBuffer = gl.createBuffer(); if(!vertexBuffer){ console.log('Failed to create the buffer object.'); return ; } gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); gl.bufferData( gl.ARRAY_BUFFER, data, gl.STATIC_DRAW ); // 位置 gl.vertexAttribPointer( a_Position, 2, gl.FLOAT, false, size * 6, 0); gl.enableVertexAttribArray( a_Position ); // 颜色 gl.vertexAttribPointer( a_Color, 4, gl.FLOAT, false, size * 6, size * 2 ); gl.enableVertexAttribArray( a_Color ); }

平移动画

逻辑和原理跟 rotation 一致。

function init(){ ... ... // 设置背景色 gl.clearColor(0.0, 0.0, 0.0, 1.0); // 旋转动画 animate(); function animate(){ changePosition( gl, u_rotateMatrix ); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); requestAnimationFrame( animate ); } } var dx = 0, dy = 0, dz = 0; var step = 0.01, flag; function changePosition( gl, u_translationMatrix ){ if(!flag){ dx += step; if(dx > 1) flag = true; } else if(flag){ dx -= step; if(dx < -1) flag = false; } var translateMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, dx, dy, dz, 1.0 ]); // 对 uniform 变量赋值 gl.uniformMatrix4fv(u_translationMatrix, false, translateMatrix); }

同一个顶点数据,通过平移,画两次

var verticsColor = new Float32Array([ 0.0, 0.6, 1.0, 0.0, 0.0, 1.0, -0.4, -0.6, 0.0, 1.0, 0.0, 1.0, 0.4, -0.6, 0.0, 0.0, 1.0, 1.0 ]); // 第一次平移,右上角 var dx = 0.5, dy = 0.3, dz = 0.0; var translateMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, dx, dy, dz, 1.0 ]); // 对 uniform 变量赋值 gl.uniformMatrix4fv(u_translationMatrix, false, translateMatrix); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); // 第二次平移,左下角 dx = -0.5; dy = -0.3; translateMatrix = new Float32Array([ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, dx, dy, dz, 1.0 ]); // 对 uniform 变量再次赋值 gl.uniformMatrix4fv(u_translationMatrix, false, translateMatrix); gl.drawArrays(gl.TRIANGLES, 0, 3);