首先我们需要配置一下本地环境以运行 Three.js。像书中说的那样,需要一款支持 JavaScript 的文本编辑器,以及一个本地 Web 服务器。
因为自己的博客就是使用的 ASP.NET 写的,所以我这边就直接使用本地的 ASP.NET 工程了。VS 已经集成了 JavaScript 编辑环境和 IIS 服务器,非常方便。
1. 搭建 HTML 框架
如代码清单 1.1 所示,是一个空的 HTML 框架。其中引入了 three.js 库;还引入了 TrackballControls.js,它用于相机控制交互。此外还包含了一个用于显示 Three.js 场景的 div 元素。
代码清单 1.2 是 01-01.js 的内容,它打印 Three.js 库的版本号。
如图 1 所示,我们可以在控制台里看到打印的结果。
2. 渲染并查看三维对象
现在我们改写上一节的 init 函数,创建一个场景,并往场景里添加几个物体。
编辑器对于 JS 的自动补齐支持的不太好。
本地使用 import 命令能解决一部分补齐:
import * as THREE from '../../../lib/three/three.js'
我们看到最后第 67 行,可以有一个总体上的感知:渲染器跟场景和相机有关。场景告知我们需要渲染什么物体,相机告诉我们从哪里观察物体。
THREE.PerspectiveCamera(fov, aspect, near, far)
fov:视角。
aspect:视口宽高比,表示渲染结果输出画布的宽高比。
near:近平面距离,表示相机能够看到的最近距离。在此距离内的物体将不会被渲染。
far:远平面距离,表示相机能够看到的最远距离。在此距离外的物体也将不会被渲染。
场景中我们会添加一个平面,注意它会绕 x 轴逆时针旋转 90 度;还添加一个方块和球体。为了方便调试查看物体摆放的位置,场景中还添加了一个坐标轴。
Three.js 坐标系的原点位于场景中心。X轴向右为正,Y轴向上为正,Z轴指向屏幕外侧为正。
AxesHelper 中的红轴为 X 轴,绿轴为 Y 轴,蓝轴为 Z 轴。
在 Three.js 中物体都通过 THREE.Mesh 表示,代表一个网络模型。它由两部分组成:几何体和材质。几何体包括形状信息,可以理解成顶点、块面这些信息;而材质包括了外观信息,可以理解成颜色、纹理这些信息。
最终的运行结果如图 2 所示,我们可以从图中感受和核对代码的逻辑。同时可以修改代码中的参数,结合结果进一步理解参数含义。
3. 添加材质、光源和阴影效果
在代码清单 3 中,我们添加了一个点光源 THREE.SpotLight。上一节中的默认材质不能反应光源,此处我们将物体材质改为兰伯特材质。兰伯特材质反应的光线强度与入射光线和表面法向量的夹角相关。
代码第 63 行,spotLight.castShadow 表示点光源是否产生阴影(不是所有光线都会产生阴影)。物体上也需要指定此物体是否是阴影的产生者,如第 20 行,可以通过 cube.castShadow 开启。除此之外,还需要定义阴影的接收者,此处是平面,如代码 47 行所示,可以通过 plane.receiveShadow 设置。
spotLight.shadow.camera.far 表示从光源位置开始,在光线照射下阴影范围的最远距离。如果一个物体在这个距离之外,则不会产生阴影。
spotLight.shadow.camera.near 表示从光源位置开始,在光线照射下阴影范围的最近距离。如果一个物体在这个距离之内,则不会产生阴影。
这边看着比较麻烦,又要指定阴影的产生者,又要指定接收者。
其实是因为阴影的模拟计算比较耗时,这样逐个指定更具选择性,对不必要的物体可以不进行渲染。
在 Three.js 中,使用深度贴图绘制阴影。如代码第 64 行,spotLight.shadow.mapSize 指定贴图的大小,大小越大,阴影精度越高。同时不要忘记渲染器使能深度贴图绘制,如代码第 12 行所示,通过 renderer.shadowMap.enabled 开启。
图 3 是运行结果,可以看到物体上有了明暗的渐变,同时也产生了阴影。
4. 让你的场景动起来
让场景动起来的方式,就是更新每帧渲染的内容。这边我们使用浏览器提供的 requestAnimationFrame API 来进行每帧的更新。
requestAnimationFrame 和 setInterval 类似,都是提供定时回调功能。
不过 requestAnimationFrame 的执行频率通常与浏览器的屏幕刷新频率相同。依托于浏览器本身的刷新策略,不会浪费系统资源,画面也更加流畅。
4.1 引入 requestAnimationFrame() 方法
我们先做好帧更新的框架,并先引入性能监测。如代码清单 4.1 所示,我们通过引入 Stats.js 库还实现性能监测、
代码清单 4.2 是帧更新的基本框架。我们主要看到第 82 至 88 行的 renderScene() 函数,其中对性能监测状态进行更新,并重新将 renderScene() 通过 requestAnimationFrame 注册为回调函数。需要注意,场景渲染 renderer.render 也移到了帧更新的函数中。
这边使用了一个新的光源 THREE.AmbientLight 环境光。环境光不会产生阴影效果。
运行结果如图 4 所示,可以看到左上角显示了帧率情况。同时因为添加了环境光,场景看着比之前的例子更亮了一点。
4.2 旋转立方体和弹跳球
现在我们在 4.1 的基础上加上物体的动画,让立方体进行旋转、让球进行弹跳。
如代码清单 4.3 所示,我们只需要在 renderScene() 增加对立方体角度的设置以及球的位置设置即可。
sphere.position.x = 20 + (10 * (Math.cos(step)));
振幅 10,范围 10 ~ 30。
sphere.position.y = 2 + (10 * Math.abs(Math.sin(step)));
振幅 10,范围 2 ~ 12。
最终的运行效果如视频 1 所示。
5. 使用 dat.GUI 简化试验流程
dat.GUI 是一个用于创建简单图形用户界面(GUI)的JavaScript库。它可以用于创建交互式控件,例如滑块、按钮、选择器等,用于在运行时调整程序的参数和行为。
引入 dat.GUI 库的方式和 4.1 中引入性能监测库一样:
- <script type="text/javascript" charset="utf-8" src="~/lib/util/dat.gui.js"></script>
代码清单 01-05.js 是具体的实现代码,我们直接从第 81 行看起。第 81 至 84 行,创建了一个名为 controls 的对象,它包含了两个属性 rotationSpeed 和 bouncingSpeed,分别表示立方体的旋转速度和球的弹跳速度。
第 86 行创建了一个dat.GUI对象,用于显示用户界面。第 87 和第 88 行通过调用 add 方法,将 controls 对象中的 rotationSpeed 和 bouncingSpeed 属性添加到用户界面中,以便用户可以通过界面来调整它们的值。
运行结果如图 5 所示,我们可以看到右上角的两个滑块,可以通过滑动它们调节旋转速度以及弹跳速度。
我们继续看到代码清单 5 的第 91 行。我们一早就引入了 TrackballControls 的库,但是没有初始化,此处就对其进行了初始化。需要注意的是,要在把渲染的 dom appendChild 到浏览器 dom 中,才能初始化 TrackballControls。初始化完之后,我们就可以按住鼠标左键来转动摄像机。
TrackballControls 库可以鼠标控制相机位置,所以必须在渲染的 dom 添加到了浏览器 dom 之后,才能绑定鼠标等事件。
6. 场景对浏览器的自适应
目前我们的渲染区域还不能自适应屏幕,渲染区域在初始化的时候就固定了。
为了实现渲染区域自适应屏幕,如代码清单 5 第 3 行所示,我们绑定 resize 事件监听。当窗口的大小发生改变时,会触发 resize 事件,然后执行我们绑定的 onResize 函数。
onResize() 函数在第 125 至 129 行,其中改变了相机的 aspect 宽高比参数,并更新了投影矩阵。最后设置了渲染器新的渲染尺寸。
相机的投影矩阵和 fov、aspect、far、near 相关,而窗口大小的改变只影响到 aspect 参数。
最后这个样例就不贴图了,因为样例本身就是能在 Web 上运行的。以下就是整体代码在浏览器上运行的效果。