Three.js 进阶教程:从核心概念到高级应用
Three.js 不仅仅是一个库,它是一个通往 3D 世界的大门。通过它,我们可以在 Web 浏览器中构建出令人惊叹的交互式体验。本教程将带你超越入门,深入了解 Three.js 的核心组件、工作原理以及一些高级技巧,助你构建更复杂、更酷炫的 3D 应用。
“深入 Three.js,你将发现 Web 前端的无限可能性。”
一、Three.js 核心工作流回顾与进阶
在入门教程中,我们介绍了 Three.js 的“四大件”:场景 (Scene)、相机 (Camera)、渲染器 (Renderer) 和物体 (Object = Geometry + Material)。它们是构建任何 Three.js 应用的基础。
1.1 渲染管线概览
graph TD
A[JavaScript Code (Three.js)] --> B(初始化: Scene, Camera, Renderer);
B --> C(创建 Mesh: Geometry + Material);
C --> D(添加 Lights);
C --> E(Objects to Scene);
E --> F{Renderer.render(Scene, Camera)};
F --> G(WebGL 渲染管线);
G --> H(GPU 处理);
H --> I(绘制到 Canvas);
J[用户交互 / 动画逻辑] --> K(更新 Scene / Camera / Objects);
K --> F;
F -- Repeatedly via --> L[requestAnimationFrame Loop];
这个流程图展示了 Three.js 应用的核心渲染循环:
- 初始化:设置场景、相机和渲染器。
- 构建场景:创建几何体、材质,组合成网格物体,并添加到场景中。添加灯光。
- 渲染:在
requestAnimationFrame循环中,每次迭代都调用renderer.render(scene, camera),将相机视角下的场景绘制到canvas上。 - 交互/动画:在每次渲染前,更新物体位置、旋转、相机位置等,实现动画和响应用户交互。
二、深入 Three.js 核心组件
2.1 场景 (Scene)
THREE.Scene 是所有 3D 对象的容器,包括几何体、灯光、相机(有时相机也添加到场景中以方便管理,但渲染时仍需独立传入 renderer.render)。
常用属性/方法:
scene.add(object): 将对象添加到场景中。scene.remove(object): 从场景中移除对象。scene.children: 包含场景中所有子对象的数组。scene.traverse(callback): 遍历场景中的所有对象及其子对象。scene.background: 设置场景的背景,可以是颜色、纹理、立方体纹理(用于全景天空盒)。scene.fog: 添加雾效。
示例:设置背景和雾效
1 | import * as THREE from 'three'; |
2.2 相机 (Camera)
相机决定了场景如何被观察。
2.2.1 PerspectiveCamera (透视相机)
最常用的相机,模拟人眼观察效果。
fov(Field of View): 视野角度,垂直方向,单位度。aspect(Aspect Ratio): 视口宽高比 (通常是width / height)。near(Near Clipping Plane): 近裁剪面,此距离以外的物体可见。far(Far Clipping Plane): 远裁剪面,此距离以内且在近裁剪面以外的物体可见。
1 | // 创建透视相机 |
2.2.2 OrthographicCamera (正交相机)
用于 2D 场景或不需要透视效果的场景(如 CAD 工具、游戏俯视图)。
left,right,top,bottom: 定义了裁剪面的范围。near,far: 同透视相机。
1 | // 创建正交相机 |
2.2.3 相机助手 (CameraHelper)
用于可视化相机视锥体,方便调试。
1 | const helper = new THREE.CameraHelper(camera); |
2.3 渲染器 (Renderer)
THREE.WebGLRenderer 是将场景渲染到 canvas 的核心。
常用属性/方法:
renderer.setSize(width, height): 设置渲染器尺寸。renderer.setPixelRatio(window.devicePixelRatio): 解决高清屏模糊问题,通常设置为设备的像素比。renderer.setClearColor(color, alpha): 设置每次渲染前清除画布的颜色和透明度。renderer.render(scene, camera): 执行渲染操作。renderer.domElement: 渲染器创建的canvas元素。
示例:初始化渲染器
1 | const renderer = new THREE.WebGLRenderer({ |
2.4 几何体 (Geometry)
几何体定义了 3D 对象的形状。
常用几何体:
BoxGeometry(width, height, depth): 立方体SphereGeometry(radius, widthSegments, heightSegments): 球体CylinderGeometry(radiusTop, radiusBottom, height, radialSegments): 圆柱体PlaneGeometry(width, height, widthSegments, heightSegments): 平面TorusGeometry(radius, tube, radialSegments, tubularSegments): 圆环体BufferGeometry: 更底层、更高效的几何体,可以手动定义顶点、法线等数据。大多数内置几何体最终都是BufferGeometry的实例。
示例:创建不同几何体
1 | const boxGeometry = new THREE.BoxGeometry(1, 1, 1); |
2.5 材质 (Material)
材质定义了 3D 对象的表面外观,以及它如何与光照互动。
常用材质:
MeshBasicMaterial: 基础材质,不受光照影响,常用于非写实或调试。color: 颜色。map: 纹理贴图。transparent,opacity: 透明度。wireframe: 线框模式。
MeshLambertMaterial: 兰伯特材质,模拟无光泽表面,对漫反射光照有反应。color,map,transparent,opacity,wireframe。
MeshPhongMaterial: 冯氏材质,模拟有光泽表面,对漫反射和镜面反射光照都有反应。color,map,transparent,opacity,wireframe。specular: 镜面反射颜色。shininess: 镜面反射光泽度。
MeshStandardMaterial: 标准材质(物理渲染材质),基于PBR(Physically Based Rendering)模型,更真实地模拟物理世界的光照。color,map,transparent,opacity。metalness: 金属度 (0-1)。roughness: 粗糙度 (0-1)。- 支持更多高级贴图:
normalMap(法线贴图),aoMap(环境光遮蔽贴图),displacementMap(置换贴图),envMap(环境贴图) 等。
LineBasicMaterial,PointsMaterial: 用于渲染线段和点。
示例:使用物理渲染材质和纹理
1 | // 假设你有一个图片文件作为纹理 |
2.6 灯光 (Light)
灯光是让场景栩栩如生的关键。
常用灯光类型:
AmbientLight(color, intensity): 环境光。均匀地照亮场景中的所有物体,没有方向性,使物体不会完全变黑。DirectionalLight(color, intensity): 平行光。模拟太阳光。光线是平行的,有方向,没有衰减。light.position.set(x, y, z): 设置光源位置。
PointLight(color, intensity, distance, decay): 点光源。模拟灯泡,从一个点向所有方向发光,有衰减。light.position.set(x, y, z): 设置光源位置。
SpotLight(color, intensity, distance, angle, penumbra, decay): 聚光灯。类似手电筒,从一个点沿一个方向发光,有一个锥形区域和衰减。light.position.set(x, y, z): 设置光源位置。light.target: 控制灯光指向的目标对象(默认为(0,0,0))。
HemisphereLight(skyColor, groundColor, intensity): 半球光。模拟户外环境光,skyColor模拟天空光,groundColor模拟地面反射光。
示例:组合不同灯光
1 | scene.add(new THREE.AmbientLight(0xffffff, 0.4)); // 柔和的环境光 |
2.6.1 阴影 (Shadows)
实现真实的阴影需要几个步骤:
- 渲染器启用阴影:
renderer.shadowMap.enabled = true; - 灯光启用投射阴影:
light.castShadow = true;(仅DirectionalLight,PointLight,SpotLight支持)- 对这些灯光,还需要配置其阴影相机的参数 (
light.shadow.camera.near,far,left,right,top,bottom) 和阴影贴图尺寸 (light.shadow.mapSize.width,height)。
- 对这些灯光,还需要配置其阴影相机的参数 (
- 物体启用投射/接收阴影:
mesh.castShadow = true;(此物体投射阴影到其他物体上)mesh.receiveShadow = true;(此物体接收其他物体投射的阴影)
示例:启用阴影
1 | renderer.shadowMap.enabled = true; // 全局开启阴影 |
三、高级主题
3.1 动画 (Animation)
除了简单地在 animate 循环中改变 position 或 rotation,Three.js 还支持更复杂的动画。
3.1.1 requestAnimationFrame 循环
这是最基本的动画方式。
1 | function animate() { |
3.1.2 外部动画库 (GSAP)
对于复杂的缓动动画,通常会结合像 GSAP 这样的专业动画库。
1 | // 假设你已安装 GSAP 并引入 |
3.1.3 骨骼动画 (SkinnedMesh)
对于加载的人体或角色模型,Three.js 支持骨骼动画,通过 AnimationMixer 和 AnimationClip 来控制。这通常涉及到从外部模型文件(如 .gltf)中导入动画数据。
3.2 几何变换 (Transformations)
每个 Object3D (包括 Mesh, Light, Camera 等) 都有 position, rotation, scale 属性以及 matrix 等。
object.position.set(x, y, z);object.rotation.set(x, y, z, order);(欧拉角,order为旋转顺序,如'XYZ')object.rotation.x += 0.01;object.scale.set(x, y, z);object.translateOnAxis(axis, distance);(沿指定轴移动)object.lookAt(targetVector);(使对象看向目标点)
3.3 纹理与贴图 (Textures)
纹理是 3D 对象表面最常用的视觉增强方式。
THREE.TextureLoader().load(url): 加载图片纹理。texture.wrapS/texture.wrapT: 设置纹理在 S/T 轴上的包裹方式 (THREE.RepeatWrapping,THREE.ClampToEdgeWrapping)。texture.repeat.set(u, v): 设置纹理重复次数。texture.offset.set(u, v): 设置纹理偏移。texture.rotation: 旋转纹理。
高级贴图:
normalMap(法线贴图): 模拟表面细节,让物体看起来有凹凸感而无需增加几何体顶点。aoMap(环境光遮蔽贴图): 模拟 crevices/corners 处的阴影。displacementMap(置换贴图): 实际改变几何体的顶点位置以创建物理上的凹凸,需要更多几何细分。roughnessMap/metalnessMap: 控制物理材质的粗糙度和金属度。envMap(环境贴图 / 反射贴图): 模拟环境反射,常用于创建镜面反射或玻璃效果。通常使用CubeTextureLoader加载六张图片组成的环境贴图。
示例:加载法线贴图
1 | const textureLoader = new THREE.TextureLoader(); |
3.4 交互 (Interactions)
Three.js 交互通常通过以下方式实现:
- 控制器 (
Controls): 如OrbitControls(轨道控制器),PointerLockControls(第一人称射击游戏控制器) 等。- 安装:
npm install three后,控制器在node_modules/three/examples/jsm/controls/目录下。 - CDN 引入:
import { OrbitControls } from 'https://unpkg.com/three@0.163.0/examples/jsm/controls/OrbitControls.js';
- 安装:
- 射线投射 (
Raycaster): 用于检测鼠标点击或触摸事件与 3D 场景中对象的交集,实现拾取、悬停等效果。
示例:使用 Raycaster 进行点击检测
1 | import * as THREE from 'three'; |
3.5 模型加载 (Model Loading)
将外部 3D 模型导入 Three.js 场景是高复杂度应用中不可或缺的一部分。
最常用格式:GLTF/GLB (Graphics Library Transmission Format)。它是 3D 资产的开放标准,支持几何体、材质、动画、骨骼等所有数据,且文件体积小。
常用加载器:
GLTFLoader: 加载.gltf或.glb模型。OBJLoader: 加载.obj模型。FBXLoader: 加载.fbx模型。
示例:加载 GLTF 模型
1 | import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; |
3.6 性能优化
大规模 3D 场景的性能优化至关重要。
- 减少绘制调用 (Draw Calls):
- 合并几何体 (
BufferGeometryUtils.mergeBufferGeometries)。 - 使用多材质 (
Mesh.material属性可以是一个数组)。 - 使用实例化 (
InstancedMesh) 绘制大量相同几何体。
- 合并几何体 (
- 优化几何体:
- 使用低多边形模型。
- 移除不必要的面和顶点。
- 禁用背面剔除 (
material.side = THREE.FrontSide;) 如果不必要。
- 优化纹理:
- 使用合适尺寸的纹理。
- 开启
texture.mipmaps(默认开启,但需要了解)。 - 使用压缩纹理格式(如 KTX2)。
- 着色器优化:
- 避免在着色器中进行复杂计算。
- 使用
gl_Position代替position * matrix(Three.js 会自动优化)。
- 阴影优化:
- 调整
shadow.mapSize和shadow.camera范围。 - 减少投射阴影的灯光数量。
- 调整
- Dispose 资源:在
scene.remove()对象后,还需要手动释放其几何体、材质和纹理在 GPU 上的内存:1
2
3
4myMesh.geometry.dispose();
myMesh.material.dispose();
if (myMesh.material.map) myMesh.material.map.dispose();
scene.remove(myMesh); // 移除实际对象
四、项目结构与开发实践
对于更复杂的 Three.js 项目,良好的结构至关重要。
- 模块化:将场景初始化、几何体创建、动画逻辑等分别放在不同的模块文件中。
- 使用构建工具:Vite 或 Webpack 是处理 Three.js (包括其
examples/jsm中的模块) 的理想选择。 - 状态管理:对于复杂的交互,可以考虑使用简单的状态管理模式来协调各种组件。
- 调试工具:
- 浏览器开发者工具。
dat.gui或lil-gui用于创建可交互的调试 UI。- Three.js 提供的各类
Helper(如GridHelper,AxesHelper,CameraHelper,LightHelper)。
示例项目结构 (使用 Vite)
1 | my-threejs-app/ |
main.js 示例:
1 | import * as THREE from 'three'; |
五、总结
Three.js 是一个令人兴奋的库,它为 Web 带来了强大的 3D 能力。通过本教程,你应该对 Three.js 的核心组件、渲染管线、常用功能以及高级实践有了更深入的理解。
从简单的立方体到复杂的模型加载和交互,Three.js 的世界值得你去探索。不断实践,勇敢尝试新的功能和效果,你将能够构建出令人印象深刻的 3D Web 应用。
记住,实践是最好的老师! 开始你的 Three.js 项目,利用这些知识,将你的创意变为现实吧!
