示例可以在这里查看。 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
这次的试验项目是图形的变换,简单平移一下,按上下左右就可以玩了。
片元着色器不用改。改动顶点着色器:
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>
),要计算其在经过三维变换(平移、缩放、旋转、扭曲)和投影(正交投影/透视投影)后的新坐标,可以根据以下公式计算得到:
<x', y', z', 1> = M(proj) * M(view) * <x, y, z, 1>
其中 M(proj)
是投影矩阵,M(view)
是模型的变换矩阵。两种矩阵的生成和原理到处都是,自行学习。
为了迎接新成员,要声明新的变量:
/**
* @type {Matrix4}
*/
var modelMatrix;
/**
* @type {WebGLUniformLocation}
*/
var u_ModelMatrix;
/**
* @type {Number}
*/
var locX = 0;
/**
* @type {Number}
*/
var locY = 0;
modelMatrix = new Matrix4();
modelMatrix.setIdentity();
updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
Matrix4
使用的是原书附的代码,作者是 Takafumi Kanda 和 Kouichi Matsuda。
对于这个矩阵,数据要适时更新:
/**
* @param {WebGLRenderingContext} glc
* @param {Matrix4} m
* @param {WebGLUniformLocation} location
*/
function updateModelMatrix(glc, m, location) {
glc.uniformMatrix4fv(location, false, m.elements);
}
然后注册键盘事件,在响应函数中处理即可。
/**
* @param {KeyboardEvent} ev
*/
function documentOnKeyDown(ev) {
switch (ev.keyCode) {
case 38: // u
locY += 2;
modelMatrix.setTranslate(locX, locY, 0);
updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
break;
case 40: // d
locY -= 2;
modelMatrix.setTranslate(locX, locY, 0);
updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
break;
case 37: // l
locX -= 2;
modelMatrix.setTranslate(locX, locY, 0);
updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
break;
case 39: // r
locX += 2;
modelMatrix.setTranslate(locX, locY, 0);
updateModelMatrix(glc, modelMatrix, u_ModelMatrix);
break;
default:
break;
}
}
基本的介绍就到这个地方。此次的示例还添加了一点新东西:Three.js 的性能监视器插件 Stats(压缩后)。
Stats 的基础使用可以见这篇文章。本例中的使用也很简单:
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()
注册一个回调函数。同时,为了响应三角形的位置变化,我们还要另一个回调函数来绘制(而不像之前那样静态绘制):
function animate2() {
drawLines(glc);
requestAnimationFrame(animate2);
}
animate2();
二者不会打架吗?更好的方法是统一到一个函数里,例如这样:
function animateUltimate() {
stats.update();
drawLines(glc);
requestAnimationFrame(animateUltimate);
}
animateUltimate();
考虑到要调用的函数一旦多起来,这样就难以管理,于是可以设计一个简单的调用队列:
/**
* @type {Function[]}
*/
var fs = [];
var fthis = [];
var fargs = [];
/**
* @param {Function} f
* @param {any} _this
* @param {any[]} args
*/
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++) {
// apply 展开参数数组,call 内联参数数组
fs[k].apply(fthis[k], fargs[k]);
}
requestAnimationFrame(animate);
}
animate();
于是这样就差不多了。Have fun.