迄今为止我们绘制的3D场景都是静止的,死气沉沉啊有木有?现在我们可以 给它们注入点生命力了。

怎么画动画

如果有看过 之前笔记 ,应该有同学留意到像这样的代码:

1
2
3
4
5
6
7
function render() {
    requestAnimationFrame(render);
    controls.update();
    renderer.render(scene, camera);
}

render();

没错 render 函数就是我们画动画的地方。因为我们调用了 requestAnimationFrame 函数将 render 注册为动画渲染回调函数,所以浏览器在认为画面需要重绘的时候就会 调用 render. 我们可以在 render 中微调各个对象的位置、形状、贴图坐标等,那么 连续的渲染结果就能构成动画了。

不过使用 requestAnimationFrame 有个微妙的问题: render 函数的调用时间间隔不 是固定的,为了让动画速度保持恒定,我们还需要根据每次调用的不同时间间隔计算 动画微调的幅度。 Three.js 提供了方便的 Clock 类用于支持此类操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var clock = THREE.Clock();

function render() {
    requestAnimationFrame(render);

    var delta = clock.getDelta();
    // .... 根据 delta 大小决定动画幅度 ....

    controls.update();
    renderer.render(scene, camera);
}

这里取得的 delta 就是以秒为单位的间隔时间。

绵延不绝的地形

让我们从上次画的那个其实不怎么像的地形开始。我想要一个“低空飞过”的 动画效果,但是——如果大家有去看源码就会知道——这个地形 Mesh 只有 64x64 那么小,别说飞了,就是走也能走出去,除非我们一直原地画圈圈。

于是,我这样投机取巧了一下:

也就是说,移动的是地形,而不是我们的视角(视角一直静止在原点附近),而且 当地形快要罩不住原点的时候,我们生成一个 地形的镜像 ,将它放在地形移 动方向的后方。因为是镜像,所以能和原本的地形 Mesh “无缝连接”。然后,在视 角距离原本的地形 Mesh 足够远的时候,反正也看不到了,我们就可以将原来那个 地形移除以节省资源。

因为 Three.js 没有提供直接生成镜像的接口,所以这里我们对 Geometry 对象使 用了 Object3D.applyMatrix(...) ,直接对几何结构进行矩阵变换,变换矩阵 是这个样子的:

1
2
3
4
5
6
7
var mirrorMatrix = new THREE.Matrix4();
mirrorMatrix.set(
    -1, 0, 0, 0,
     0, 1, 0, 0,
     0, 0, 1, 0,
     0, 0, 0, 1
);

可见只是将 X 轴坐标反转而已。

纸飞机

为了体现场景的宏大(?),我决定在镜头前面画一只小小的纸飞机。为什么是纸 飞机?因为只有4个面很好画……

这里我用了 MeshBasicMaterial 的 vertexColors 选项,让材质颜色由每个顶点 单独确定,于是可以画出点简单的阴影效果——看过 上一篇笔记 源码的同学应该 也注意到了,地形的阴影也是这样搞出来的。

所有东西放在一起

为了让场景稍微好看一点,我给这里的 Scene 加了一个 FogExp2