写在前面
上一篇笔记中,我们全面了解了GLSL ES的语法,必须掌握的WebGL基础知识已经基本都有了(也就是说API没了..),接下来的内容基本都是数学,而变换只是其中最浅显的一部分
一.变换的数学原理
一句两句说不清楚,例如translate(平移)就是给图像每一个点的坐标的各个分量添上一个delta,点(0, 3, 2)
沿y轴正方向移动2个单位长度,结果是(0, 5, 2)
。原理就是这么简单,说不清楚是因为牵扯到公式推导和矩阵表示,如下:
x' = x + deltaX; how? why? |1 0 0 tx|
y' = y + deltaY; ------------> |0 1 0 ty|
z' = z + deltaZ; |0 0 1 tz|
|0 0 0 1 |
三维坐标为什么需要用4×4矩阵表示translate变换?这样做有什么好处?那rotate和scale呢?
感兴趣可以自行摸索(搜索),完整过程是公式推导->齐次坐标->矩阵乘法->变换矩阵->CTM,理解了CTM后,就完全理解变换的数学原理了
P.S.太数学的东西暂时不想写,但早晚会写。所以这里不展开变换矩阵,以后再说
二.WebGL变换矩阵
变换矩阵不都长一个样吗?不不不,上一篇笔记中说了,GLSL ES中的矩阵是列主序的,比如translate矩阵就得是这个样子:
Matrix4.prototype.setTranslate = function(x, y, z) {
var e = this.elements;
e[0] = 1; e[4] = 0; e[8] = 0; e[12] = x;
e[1] = 0; e[5] = 1; e[9] = 0; e[13] = y;
e[2] = 0; e[6] = 0; e[10] = 1; e[14] = z;
e[3] = 0; e[7] = 0; e[11] = 0; e[15] = 1;
return this;
};
P.S.以上代码摘自《WebGL编程指南》源码cuon-matrix.js,API风格用着很顺手,所以..以后就直接用了
注意:WebGl和OpenGL一样,矩阵元素是按列主序存储的,其实CSS中transform: matrix(a,b,c,d,e,f)
也是按列主序的,但线性代数里面一般习惯按行主序
三.矩阵库
一个简单的矩阵库是必须的,至少要支持3种基本变换和矩阵乘法,当然最好有一套友好的(用着舒服的)API
《WebGL编程指南》提供的矩阵库cuon-matrix.js就不错,小巧只提供最基本的功能,如果觉得不好用可以去找找其它矩阵库,但不建议使用Three.js,因为功能多而全,不利于我们深入了解WebGL
以后的例子将使用cuon-matrix.js,具体用法如下:
// 创建4x4矩阵,默认填充成单位阵
var m4 = new Matrix4();
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 1]
// 填充为平移矩阵,参数为deltaX, deltaY, deltaZ
m4.setTranslate(0.25, 0.25, 0.0);
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]
// 在平移的基础上旋转
m4.rotate(30, 0, 0, 1); // 逆时针30度,(0, 0, 1)是旋转轴 z轴
// m4.elements === [
// 0.8660253882408142, 0.5, 0, 0,
// -0.5, 0.8660253882408142, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]
// ...
// setXXX就是填充
// xxx就是求CTM,即m4 = m4 x new Matrix4().setXXX
也有很方便很实用的功能,比如:
// 复制
var newM4 = new Matrix4(oldM4);
// 矩阵乘法,乘积放在m4_1中
m4_1.multiply(m4_2);
// 矩阵转置
m4.transpose();
四.基本变换
变换是对图像上的每一个点的坐标做同样的运算,因为片元是根据顶点生成的,所以我们只需要对顶点做运算,也就是说只需要修改顶点着色器,如下:
// 顶点着色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
'uniform mat4 u_transformMatrix;' +
'void main() {' +
'gl_Position = u_transformMatrix * a_Position;' + // 设置坐标
// 一点小把戏,为了把变换前的三角形显示出来
'if (u_transformMatrix == mat4(0.0)) {gl_Position = a_Position;}' +
'}';
我们声明了矩阵类型uniform变量u_transformMatrix
,用来接受变换矩阵,接下来要把值传进去
首先要有一个变换矩阵,现做一个:
// 变换
var transformMatrix = new Matrix4();
// 先平移
transformMatrix.setTranslate(0.25, 0.25, 0.0); // 向右上平移
// 再旋转
transformMatrix.rotate(30, 0, 0, 1); // 逆时针30度,(0, 0, 1)是旋转轴 z轴
// 再缩放
transformMatrix.scale(0.5, 0.5, 1); // x, y缩放一半,z不变
这就折腾出了一个很复杂的变换矩阵,然后想办法赋值给uniform变量,如下:
// 把变换矩阵传递给顶点着色器
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);
给mat类型着色器变量赋值用到了gl.uniformMatrix4fv
,参数含义如下:
gl.uniformMatrix4fv(location, transpose, array)
---
transpose 在WebGL中只能为false,表示矩阵是不是转置矩阵,WebGL没有提供矩阵转置的方法
array 类型化数组,4x4矩阵按列主序存放
最后再draw出来,就完成了
五.动画
动画就是不断擦除重绘产生的视觉效果,比如旋转就是通过旋转角度不断递增绘制出来的,要保证流畅就要保证角度均匀递增。那么实现动画的关键变成了保证均匀变化,而单纯的setInterval
就不行,所以需要加入时间控制,具体如下:
// 变换
var angle = 0;
var ROTATE_SPEED = 60; // 60度/秒
var transformMatrix = new Matrix4();
var now;
var lastTime;
var delta = 0;
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
// draw
function draw() {
now = Date.now();
delta = (now - lastTime) * ROTATE_SPEED / 1000;
// 角度递增
angle += delta;
angle %= 360;
// console.log(angle);
// 旋转
transformMatrix.setRotate(angle, 0.0, 0.0, 1.0);
// 把旋转矩阵传递给顶点着色器
gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);
// clear
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制变换后的三角形
gl.drawArrays(gl.TRIANGLES, 0, arrVtx.length / 2);
// 记录时间
lastTime = now;
// 延迟递归
window.requestAnimationFrame(draw);
}
// 动画
window.requestAnimationFrame(draw);
lastTime = Date.now();
旋转速度是度/秒
,下一次绘制时转过了多少度是根据时间算的,这样就保证了旋转角度均匀变化
注意:requestAnimationFrame
类似于setTimeout/setInterval
,区别是requestAnimationFrame
只在标签页处于激活状态时才会生效,与setTimeout/setInterval
不同,性能也要更高一些
P.S.在本例中当然看不出标签页非激活状态动画停止,因为我们是根据时间计算的角度,本来中间停了一段,但恢复激活的时候旋转了一个大角度,把中间停的部分都追上了,css3动画内部原理也是这样的:
CSS3动画在Tab切换回来的时候,动画表现并不暂停;通过Chrome frames工具测试发现,Tab切换之后,计算渲染绘制都停止,Tab切换回来时似乎通过内置JS计算了动画位置实现重绘,造成动画不暂停的感觉
P.S.摘自CSS3动画那么强,requestAnimationFrame还有毛线用? « 张鑫旭-鑫空间-鑫生活,关于requestAnimationFrame
的更多信息可以查看这篇文章,很有价值
六.DEMO
包含上述代码的完整的例子,请查看:
变换:http://www.ayqy.net/temp/webgl/translate-rotate-scale-triangle/index.html
在console可以看到每一步变换后矩阵的值
动画:http://www.ayqy.net/temp/webgl/transform-animate/index.html
七.总结
变换矩阵也叫模型矩阵,3D中必不可少的mvp就是(Model Matrix、View Matrix、Projection Matrix)
至此,WebGL 2D就结束了,仔细想想好像也没学到什么,大部分API都在3D那边吗?不是,大部分常用API我们都用过了,剩下的除了一点点有用的就只有一些很高级的功能(比如动态纹理)了,3D场景几乎全都是用矩阵“搓”出来的,没什么可用API了,没有类似于Camera.setPosition(0, 0, 3)
这样的神奇方法
视角是什么,是矩阵,光照是什么,矩阵,那阴影呢,矩阵,雾化呢,说了都是矩阵。。。
参考资料
- WebGL编程指南》