WebGL 测试3 - 移动元素

示例可以在这里查看。 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄


这次的试验项目是图形的变换,简单平移一下,按上下左右就可以玩了。

片元着色器不用改。改动顶点着色器:

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.

分享到 评论