Three.js 入门指南:从零构建 WebGL 3D 场景

作者:小圆点时间:2026-10-26 23:40:26浏览:224

本文介绍如何使用 Three.js 在浏览器中创建 3D 场景。涵盖场景初始化、相机与渲染器配置、几何体与材质创建、光照设置及渲染循环。重点解析浏览器兼容性、性能优化及动态几何体更新机制,帮助开发者快速上手 WebGL 开发。

Three.js 概述与学习资源

Three.js 是一款优秀的开源 WebGL 库,它允许 JavaScript 直接操作 GPU,从而在浏览器中实现真正的 3D 渲染。然而,由于该技术尚处于发展阶段,相关学习资料相对稀缺,爱好者通常需要借助 Demo 源码以及 Three.js 自身的源代码来进行学习。



国外网站 aerotwist.com 提供了一套较为简单的入门教程,本文尝试将其翻译分享,供读者参考。


在多个实验项目中,我使用了 Three.js,发现它对快速上手浏览器 3D 编程极具帮助。借助 Three.js,开发者可以创建相机、物体、光线和材质等元素,并可选择着色器,决定使用 WebGL、Canvas 或 SVG 技术来渲染网页上的 3D 图形。Three.js 是开源项目,开发者甚至可以直接参与贡献。目前,我将重点放在基础介绍上,展示如何使用该引擎。



尽管 Three.js 功能强大,但有时也会让人头疼。例如,你可能需要花费大量时间阅读例程、进行逆向工程(在我的案例中)以理解某个函数的作用,有时还需在 GitHub 上提问。如果需要寻求帮助,Mr. doob 和 AlteredQualia 是极好的交流对象。



1. 基础

本文假设你具备三维图形学知识,并掌握了一定的 JavaScript 基础。若不具备这些,建议先学习相关知识,否则直接阅读本教程可能会感到困惑。



在我们的三维世界中,包含以下核心元素。本文将带你逐步创建它们。


1. 场景


2. 渲染器


3. 相机


4. 物体(带有材质)


当然,你也可以创建其他元素,鼓励你尝试创新。


2. 浏览器支持

浏览器兼容性与渲染技术选择

首先简要查看浏览器的支持情况。Google Chrome 浏览器支持 Three.js,在我的实验中,无论是渲染器支持程度还是 JavaScript 解释器的运行速度,Chrome 表现最佳:它支持 Canvas、WebGL 和 SVG,且运行速度非常快。Firefox 浏览器紧随其后,其 JavaScript 引擎速度略慢于 Chrome,但对渲染器的支持也很出色,且随着版本更新,Firefox 的速度也在不断提升。Opera 浏览器正在逐步增加对 WebGL 的支持,Mac 上的 Safari 浏览器提供了开启 WebGL 的选项。总体而言,这两个浏览器仅支持 Canvas 渲染。微软的 IE9 目前仅支持 Canvas 渲染,且微软似乎对支持 WebGL 这一新特性并不积极,因此我们目前不会使用 IE9 进行实验。


3. 设置场景

假设你已选择支持所有渲染技术的浏览器,并准备通过 Canvas 或 WebGL 来渲染场景(这是更标准化的选择)。Canvas 具有更广泛的支持,但 WebGL 可以直接在 GPU 上操作,这意味着你的 CPU 可以专注于处理非渲染类工作,如物理引擎或与用户交互等。



无论选择何种渲染器,都必须牢记:JavaScript 代码需要优化。三维显示对浏览器来说并非轻松任务(目前能实现这一点已非常伟大),因此如果你的渲染速度过慢,需要找出代码瓶颈并尽可能进行改善。



场景初始化与基础配置

说了这么多,我想你已经下载好 Three.js 源代码,并将其引入到你的 HTML 文档中。那么如何开始创建一个场景呢?代码如下所示:


代码如下:

 
// 设置场景大小
var WIDTH = 400,
HEIGHT = 300;
// 设置一些相机参数
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
// 获取DOM结构中的元素
// - 假设我们使用了JQuery
var $container = $('#container');
// 创建渲染器、相机和场景
var renderer = new THREE.WebGLRenderer();
var camera =
new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
var scene = new THREE.Scene();
// 将相机加入场景
scene.add(camera);
// 相机的初始位置为原点
// 将相机拉回来一些(译者注:这样才能看到原点)
camera.position.z = 300;
// 启动渲染器
renderer.setSize(WIDTH, HEIGHT);
// 将渲染器加到DOM结构中
$container.append(renderer.domElement);

你看,简单吧!


4. 构建网格表面

几何体与网格创建

现在我们有了一个场景、一个相机和一个渲染器(在我的例子中,当然是一个 WebGL 渲染器),但我们实际上什么还没画呢。事实上,Three.js 提供了载入某几种标准格式 3D 文件的支持,如果你在 Blender、Maya、Cinema4D 或其他工具中建模,这简直太棒了。为了简单(毕竟这才刚开始呢!)我们先来考虑基元。基元就是基本的几何表面,比如最基本的球体、平面、立方体、圆柱体。利用 Three.js 可以很方便地创建这些基元:


代码如下:

 
// 设置球体参数(译者注:球体被划分为16×16的网格,如果后两个参数取4、2,则生成一个八面体,请想象)
var radius = 50,
segments = 16,
rings = 16;
// material覆盖在geometry上,生成mesh
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(
radius,
segments,
rings),
sphereMaterial);
// 将mesh加入到场景中
scene.add(sphere);

材质系统与光照设置

好了,但是球体上的材质呢?在代码中我们使用了一个 sphereMaterial 变量,我们还没定义它呢。那我们就先来看看怎么创建材质吧。


5. 材质

毫无疑问,这是 Three.js 最有用的部分了。这部分提供了几个非常易用的通用材质模型:


1. Basic 材质:表示一种不考虑光照的材质,现在只能这么说了。


2. Lambert 材质:(译者注:朗伯面,各向同性反射)。


3. Phong 材质:(译者注:冯氏面,有光泽的表面,介于镜面反射和朗伯反射之间的反射,描述真实世界的反射)。



除此之外,还有一些其他类型材质,简单起见,就留给你自己探索。事实上,在使用 WebGL 类型的渲染器时,材质实在太好用了。为什么呢?因为在原生 WebGL 中你必须亲自为每个渲染编写着色器,而着色器本身就是个巨大的工程:简单地说着色器是使用 GLSL 语言(OpenGL 的着色器语言)写的,用来操作 GPU 的程序,这意味着你要在数学上模拟光照、反射等等,这很快就变成一项极为复杂的工作。多亏有了 Three.js 你才可以不必去自己编写着色器,当然,如果你想亲自编写的话,你可以使用 MeshShaderMaterial,可见这是很灵活的设定。



现在,让我们用朗伯面材质覆盖球体:


代码如下:

 
// 创建球体表面的材质
var sphereMaterial =
new THREE.MeshLambertMaterial(
{ 
color: 0xCC0000
}); 

值得指出的是,创建材质的时候,除了颜色还有很多其他参数可以指定,比如光滑度和环境贴图。你可以需要检索这个 Wiki 页面来确认哪些是哪些属性可以设置在材质上,或 Three.js 引擎提供的任何对象上。


6. 光

如果你现在就想渲染场景,你会看到一个红色的圆。虽然我们在球体上覆盖了朗伯面材质,但场景里没有光。所以按照默认设定,Three.js 会恢复到满环境光,物体的看上去的颜色就是物体表面的颜色。让我们添加一个简单的点光源:


代码如下:

 
// 创建一个点光源
var pointLight =
new THREE.PointLight(0xFFFFFF);
// 设置点光源的位置
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
// 将点光源加入场景
scene.add(pointLight);

7. 渲染循环

渲染循环与动画处理

显然,关于渲染器的一切都设置好了。万事俱备,我们现在只需要:


代码如下:

 
// 画!
renderer.render(scene, camera);

你很可能像多次渲染,而不是只渲染一次,所以如果你要去做一个循环,你应该使用 requestAnimationFrame。这是目前最好的,在浏览器中处理动画的方法,虽然还没有得到最全面的支持,但我强烈建议你去看一看 Paul Irish 的博客。


8. 通用的对象属性

对象属性与动态更新机制

如果你花点时间去浏览一遍 Three.js 的源代码,你会发现很多对象都继承自 Object3D。这个基类包含了很多有用的属性,比如位置、旋转和缩放的信息。特别的,我们的球体是一个 Mesh 对象,而 Mesh 对象继承自 Object3D 对象,但是又增加了些自己的属性:geometry 和 material。为什么要说这些?因为你一定不会只满足于屏幕中一个什么都不做的圆球,而这些(译者注:基类中的)属性允许你操作 Mesh 对象更底层的细节和各种各样的材质。


代码如下:

 
// sphere是一个mesh对象
sphere.geometry
// sphere包含了一些点和面的信息
sphere.geometry.vertices // 一个数组
sphere.geometry.faces // 另一个数组
// mesh对象继承自object3d对象
sphere.position // 包含x,y,z
sphere.rotation // 同上
sphere.scale // ... 同上

9. 讨厌的秘密

我希望这样说你能很快弄明白:就是如果你修改了,比如说,一个 mesh 对象的顶点属性 vertices,你会发现在渲染循环中,什么都没变。为什么?因为 Three.js 将 mesh 对象的信息缓存为某种优化结构了。你真正要做的是给 Three.js 一个标识,告诉它如果什么东西改变了,需要重新计算缓存中的结构:


代码如下:

 
// 设置geometry为动态的,这样才允许改变其中的顶点
sphere.geometry.dynamic = true;
// 告诉Three.js,需要重新计算顶点
sphere.geometry.__dirtyVertices = true;
// 告诉Three.js,需要重新计算顶点
sphere.geometry.__dirtyNormals = true;

还有更多的标识,但我发现这两个是最有用的。你应该仅仅标识那些确实需要实时计算的属性来避免无谓的运算开销。


10. 小结

我希望这篇简单的介绍对你有所帮助。没什么能比得上卷起袖子亲手实践了,我强烈建议你这样做。在浏览器里面运行 3D 程序很有意思,而且使用像 Three.js 这样一个引擎免去了很多麻烦,让你一开始就能专注于那些真正 cool 的事情。



我将这篇教程的源码打包了,你可以下载下来作为一份参考。

作者最新文章