绕 z 轴正向旋转 —— 逆时针

△ABC是怎么旋转到△A'B'C'的位置呢?首先我们看线段OP旋转到OP′,点 P(x, y, z) 是怎么变到 P'(x', y', z') 的。设 |OP| = r,则

x = r cosα
y = r sinα
x' = r cos(α+β)
y' = r sin(α+β)
z' = z

x' = r (cosα cosβ - sinα sinβ) = x cosβ - y sinβ
y' = r (sinα cosβ + cosα sinβ) = x sinβ + y cosβ

把上面的公式代入到顶点着色器里面去,注意,是写在 GLSL ES 的 main() 函数里,而不是在 JavaScript 里。因为每个点在z轴上的旋转都是一致的, 所以我们要使用 uniform 变量来实现。而attribute 变量的x、y坐标, 顶点着色器里的 gl_Position 变量的 x、y、z、w 坐标都需单独写出来。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; uniform float u_sinB, u_cosB; void main(){ gl_Position.x = a_Position.x * u_cosB - a_Position.y * u_sinB; gl_Position.y = a_Position.x * u_sinB + a_Position.y * u_cosB; gl_Position.z = a_Position.z; gl_Position.w = 1.0; } </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_sinB = gl.getUniformLocation(gl.program, 'u_sinB'), u_cosB = gl.getUniformLocation(gl.program, 'u_cosB'); if(a_Position < 0 || !u_sinB || !u_cosB){ console.log('Failed to get the location'); console.log(a_Position, u_sinB, u_cosB); return; } var vertics = new Float32Array([0.0, 0.5, -0.4, -0.5, 0.4, -0.5]); var angle = 60.0, radian = Math.PI * angle / 180.0, sinB = Math.sin( radian ), cosB = Math.cos( radian ); // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, vertics, a_Position, 2); // 对 uniform 变量赋值 gl.uniform1f(u_sinB, sinB); gl.uniform1f(u_cosB, cosB); // 设置背景色, 清空颜色缓冲区 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轴

下面分别是三角形在z轴、x轴、y轴上旋转的效果,以及对应的矩阵。但在 WebGL 里矩阵要按列主序重写。这时,GLSL ES 里的 Vertex Shader 中声明一个类型是 mat4 的 uniform 变量,它是一个 4×4 矩阵。在 main() 函数里进行矩阵运算 u_rotateMatrix * a_Position 再赋值给 gl_Position,不用对 gl_Position 变量的 x、y、z、w 坐标单独运算了。而在 JavaScript 里对 uniform 传 mat4 的值就用到 gl.uniformMatrix4fv( location, false, array )。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute vec4 a_Color; uniform mat4 u_rotateMatrix; varying vec4 v_Color; void main(){ gl_Position = u_rotateMatrix * 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_rotateMatrix = gl.getUniformLocation(gl.program, 'u_rotateMatrix'); if(a_Position < 0 || a_Color < 0 || !u_rotateMatrix){ console.log('Failed to get the location'); console.log(a_Position, a_Color, u_rotateMatrix); 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 angle = 60.0, radian = Math.PI * angle / 180.0, sinB = Math.sin( radian ), cosB = Math.cos( radian ); var rotateMatrix = new Float32Array([ // z轴 cosB, sinB, 0.0, 0.0, -sinB, cosB, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]); /* var rotateMatrix = new Float32Array([ // x轴 1.0, 0.0, 0.0, 0.0, 0.0, cosB, sinB, 0.0, 0.0, -sinB, cosB, 0.0, 0.0, 0.0, 0.0, 1.0 ]);*/ /*var rotateMatrix = new Float32Array([ // y轴 cosB, 0.0, sinB, 0.0, 0.0, 1.0, 0.0, 0.0, -sinB, 0.0, cosB, 0.0, 0.0, 0.0, 0.0, 1.0 ]);*/ // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, verticsColor, a_Position, a_Color, element_size); // 对 uniform 变量赋值 gl.uniformMatrix4fv(u_rotateMatrix, false, rotateMatrix); // 设置背景色, 清空颜色缓冲区 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 ); }

旋转动画

gl.clearColor() 只需设置一次,调用 requestAnimationFrame,在 animate() 里 不断的清除颜色缓存 gl.clear(gl.COLOR_BUFFER_BIT),也不断的改角度并不断用 gl.uniformMatrix4fv() 重新对 uniform 变量赋值。

function init(){ ... ... // 设置背景色 gl.clearColor(0.0, 0.0, 0.0, 1.0); // 旋转动画 animate(); function animate(){ changeAngle( gl, u_rotateMatrix ); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); requestAnimationFrame( animate ); } } var angle = 0, step = 1/2; function changeAngle( gl, u_rotateMatrix ){ angle += step; var radian = Math.PI * angle / 180.0, sinB = Math.sin( radian ), cosB = Math.cos( radian ); var rotateMatrix = new Float32Array([ // y轴 cosB, 0.0, sinB, 0.0, 0.0, 1.0, 0.0, 0.0, -sinB, 0.0, cosB, 0.0, 0.0, 0.0, 0.0, 1.0 ]); // 对 uniform 变量赋值 gl.uniformMatrix4fv(u_rotateMatrix, false, rotateMatrix); }

绕任意轴的旋转

点绕任意轴的旋转,由上面矩阵决定,其中nx、ny、nz都是在单位向量上的分量。轴上任意一点跟原点的连线都在同一直线上,所以把下面的 x = 0.2, y = 0.2, z = 0.2 改为 x = 0.7, y = 0.7, z = 0.7,旋转的效果都是一致的。

var angle = 60.0, radian = Math.PI * angle / 180.0, sinB = Math.sin( radian ), cosB = Math.cos( radian ); var x = 0.2, y = 0.2, z = 0.2, length = Math.sqrt(x*x + y*y + z*z); // 归一化成在单位向量上的长度 x /= length; y /= length; z /= length; var rotateMatrix = new Float32Array([ x*x*(1-cosB) + cosB, x*y*(1-cosB) + z*sinB, x*z*(1-cosB) - y*sinB, 0.0, x*y*(1-cosB) - z*sinB, y*y*(1-cosB) + cosB, y*z*(1-cosB) + x*sinB, 0.0, x*z*(1-cosB) + y*sinB, y*z*(1-cosB) - x*sinB, z*z*(1-cosB) + cosB, 0.0, 0.0, 0.0, 0.0, 1.0 ]);