示例可以在这里查看。 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
这次的试验项目是图形的变换,简单平移一下,按上下左右就可以玩了。
片元着色器不用改。改动顶点着色器:
1 2 3 4 5 6 7 8 9 10 11
| attribute vec4 a_Position; attribute vec4 a_Color; varying vec4 v_Color;
uniform mat4 u_ModelMatrix; uniform mat4 u_ProjMatrix;
void main() { gl_Position = u_ProjMatrix * u_ModelMatrix * a_Position; v_Color = a_Color; }
|
这里就有点线性代数的知识了。对于一个点(相对于原点的向量 <x, y, z>,齐次化后为 <x, y, z, 1>),要计算其在经过三维变换(平移、缩放、旋转、扭曲)和投影(正交投影/透视投影)后的新坐标,可以根据以下公式计算得到:
1
| <x', y', z', 1> = M(proj) * M(view) * <x, y, z, 1>
|
其中 M(proj) 是投影矩阵,M(view) 是模型的变换矩阵。两种矩阵的生成和原理到处都是,自行学习。
为了迎接新成员,要声明新的变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
var modelMatrix;
var u_ModelMatrix;
var locX = 0;
var locY = 0;
modelMatrix = new Matrix4(); modelMatrix.setIdentity(); updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
|
Matrix4 使用的是原书附的代码,作者是 Takafumi Kanda 和 Kouichi Matsuda。
对于这个矩阵,数据要适时更新:
1 2 3 4 5 6 7 8
|
function updateModelMatrix(glc, m, location) { glc.uniformMatrix4fv(location, false, m.elements); }
|
然后注册键盘事件,在响应函数中处理即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
function documentOnKeyDown(ev) { switch (ev.keyCode) { case 38: locY += 2; modelMatrix.setTranslate(locX, locY, 0); updateModelMatrix(glc, modelMatrix, u_ModelMatrix); break; case 40: locY -= 2; modelMatrix.setTranslate(locX, locY, 0); updateModelMatrix(glc, modelMatrix, u_ModelMatrix); break; case 37: locX -= 2; modelMatrix.setTranslate(locX, locY, 0); updateModelMatrix(glc, modelMatrix, u_ModelMatrix); break; case 39: locX += 2; modelMatrix.setTranslate(locX, locY, 0); updateModelMatrix(glc, modelMatrix, u_ModelMatrix); break; default: break; } }
|
基本的介绍就到这个地方。此次的示例还添加了一点新东西:Three.js 的性能监视器插件 Stats(压缩后)。
Stats 的基础使用可以见这篇文章。本例中的使用也很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function initStatElement(parentID) { var stats = new Stats(); var d = stats.domElement; d.style.position = "absolute"; d.style.right = "0"; d.style.top = "0"; document.getElementById(parentID).appendChild(d); return stats; }
var stats = initStatElement("canvas-frame");
function animate1() { stats.update(); requestAnimationFrame(animate1); } animate1();
|
这里就出现了一个问题。可以看到,为了更新 stats 显示的内容,我们要用 requestAnimationFrame() 注册一个回调函数。同时,为了响应三角形的位置变化,我们还要另一个回调函数来绘制(而不像之前那样静态绘制):
1 2 3 4 5
| function animate2() { drawLines(glc); requestAnimationFrame(animate2); } animate2();
|
二者不会打架吗?更好的方法是统一到一个函数里,例如这样:
1 2 3 4 5 6
| function animateUltimate() { stats.update(); drawLines(glc); requestAnimationFrame(animateUltimate); } animateUltimate();
|
考虑到要调用的函数一旦多起来,这样就难以管理,于是可以设计一个简单的调用队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
var fs = []; var fthis = []; var fargs = [];
function addToAnimationLoop(f, _this, args) { fs.push(f); fthis.push(_this); fargs.push(args); }
addToAnimationLoop(stats.update, stats); addToAnimationLoop(drawLines, this, [glc]);
function animate() { for (var k = 0; k < fs.length; k++) { fs[k].apply(fthis[k], fargs[k]); } requestAnimationFrame(animate); } animate();
|
于是这样就差不多了。Have fun.