一、WebGL 是什么
WebGL(Web Graphics Library)是浏览器中实现 3D 渲染的 JavaScript API,基于 OpenGL ES 2.0/3.0 标准。它利用 GPU 进行硬件加速,能够在浏览器中渲染复杂的 3D 场景。
WebGL vs Canvas 2D
| 维度 | Canvas 2D | WebGL |
|---|
| 渲染方式 | CPU 渲染 | GPU 硬件加速 |
| 维度 | 2D | 2D / 3D |
| 图形数量 | 万级 | 百万级 |
| 着色器 | 无 | 顶点着色器 + 片元着色器 |
| 学习曲线 | 低 | 高 |
| 适用场景 | 图表、2D 游戏 | 3D 游戏、可视化、VR/AR |
直接使用 WebGL 还是 Three.js
原生 WebGL 的代码量极大——绘制一个旋转立方体需要约 200 行代码(顶点数据、着色器编译、缓冲区绑定、矩阵计算等)。Three.js 是对 WebGL 的高层次封装,将上述细节抽象为 Scene、Camera、Renderer、Mesh 等对象。
推荐路径:通过 Three.js 入门 3D 开发,理解 WebGL 概念(着色器、缓冲区、管线)以排查性能问题。
二、Three.js 核心概念
Three.js 的核心对象构成一个完整的 3D 场景:
1 2 3 4 5 6 7
| Scene(场景) ├── Camera(相机) → 决定了从哪个角度观察 ├── Renderer(渲染器) → 负责将场景绘制到 Canvas ├── Light(光源) → 照亮场景中的物体 └── Mesh(网格) → 可见物体 = Geometry + Material ├── Geometry(几何体) → 形状(顶点数据) └── Material(材质) → 外观(颜色、纹理、光照响应)
|
最小 3D 场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardMaterial({ color: 0x409eff }); const cube = new THREE.Mesh(geometry, material); scene.add(cube);
const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5); scene.add(light);
function animate() { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate();
|
三、场景(Scene)
Scene 是所有物体的容器。可以向场景中添加或移除物体:
1 2 3 4 5 6 7 8 9
| scene.add(cube); scene.remove(cube); scene.add(light);
scene.background = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.Fog(0x1a1a2e, 10, 50);
|
四、相机(Camera)
透视相机 vs 正交相机
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const camera = new THREE.OrthographicCamera( -10, 10, 10, -10, 0.1, 100 );
|
相机控制(OrbitControls)
1
| npm install three/examples/jsm/controls/OrbitControls.js
|
1 2 3 4 5 6 7 8
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.autoRotate = true; controls.autoRotateSpeed = 2; controls.target.set(0, 0, 0);
|
1 2 3 4 5 6
| function animate() { controls.update(); renderer.render(scene, camera); requestAnimationFrame(animate); }
|
五、几何体(Geometry)
Three.js 提供了丰富的内置几何体:
1 2 3 4 5 6 7 8
| const box = new THREE.BoxGeometry(1, 1, 1); const sphere = new THREE.SphereGeometry(1, 32, 32); const cylinder = new THREE.CylinderGeometry(1, 1, 2, 32); const cone = new THREE.ConeGeometry(1, 2, 32); const plane = new THREE.PlaneGeometry(5, 5); const torus = new THREE.TorusGeometry(1, 0.4, 16, 100); const ring = new THREE.RingGeometry(0.5, 1, 32); const circle = new THREE.CircleGeometry(1, 32);
|
分段数越高,表面越平滑,但顶点数越多:
1 2 3 4 5
| const lowPoly = new THREE.SphereGeometry(1, 8, 6);
const smooth = new THREE.SphereGeometry(1, 64, 64);
|
自定义几何体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([ -1, -1, 0, 1, -1, 0, 0, 1, 0, ]); geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const colors = new Float32Array([ 1, 0, 0, 0, 1, 0, 0, 0, 1, ]); geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const indices = [0, 1, 2]; geometry.setIndex(indices);
geometry.computeVertexNormals();
const material = new THREE.MeshBasicMaterial({ vertexColors: true }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
|
六、材质(Material)
| 材质 | 特点 | 需要光源 |
|---|
MeshBasicMaterial | 基础颜色/纹理,无光照响应 | ❌ |
MeshStandardMaterial | PBR 材质,物理真实 | ✅ |
MeshPhongMaterial | 经典光照模型 | ✅ |
MeshLambertMaterial | 漫反射光照,性能优于 Phong | ✅ |
MeshMatcapMaterial | 固定光照方向,性能高 | ❌(使用贴图模拟) |
MeshNormalMaterial | 法线可视化,用于调试 | ❌ |
ShaderMaterial | 自定义着色器 | 取决于实现 |
1 2 3 4 5 6 7 8 9 10 11
| const material = new THREE.MeshStandardMaterial({ color: 0x409eff, roughness: 0.4, metalness: 0.1, transparent: true, opacity: 0.8, side: THREE.DoubleSide, wireframe: false, emissive: 0x000000, });
|
纹理
1 2 3 4 5 6 7 8 9
| const loader = new THREE.TextureLoader(); const texture = loader.load('texture.jpg');
const material = new THREE.MeshStandardMaterial({ map: texture, normalMap: loader.load('normal.jpg'), roughnessMap: loader.load('rough.jpg'), metalnessMap: loader.load('metal.jpg'), });
|
透明纹理
1 2
| const texture = loader.load('tree.png'); texture.premultiplyAlpha = true;
|
七、光源(Light)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const ambient = new THREE.AmbientLight(0xffffff, 0.3); scene.add(ambient);
const directional = new THREE.DirectionalLight(0xffffff, 1); directional.position.set(5, 10, 5); directional.castShadow = true; scene.add(directional);
const point = new THREE.PointLight(0xff6600, 1, 20); point.position.set(2, 3, 4); scene.add(point);
const spot = new THREE.SpotLight(0xffffff, 1); spot.position.set(0, 5, 0); spot.angle = Math.PI / 6; scene.add(spot);
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x362b25, 0.6); scene.add(hemi);
|
建议:始终至少使用 AmbientLight + DirectionalLight 的组合,避免完全黑暗或过于平淡。
八、动画循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const clock = new THREE.Clock();
function animate() { const delta = clock.getDelta(); const elapsed = clock.getElapsedTime();
cube.position.y = Math.sin(elapsed) * 2; cube.rotation.x += delta;
controls.update(); renderer.render(scene, camera); requestAnimationFrame(animate); } animate();
|
帧率无关的动画
始终使用 delta 或 elapsed 控制动画速度,而不是 += 0.01 这种固定增量:
1 2 3 4 5
| mesh.rotation.y += 0.01;
mesh.rotation.y += delta * Math.PI / 2;
|
九、性能优化
1. 合并几何体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
for (let i = 0; i < 1000; i++) { const mesh = new THREE.Mesh(geo, mat); mesh.position.set(Math.random() * 100, 0, Math.random() * 100); scene.add(mesh); }
const geometries = []; for (let i = 0; i < 1000; i++) { const g = geo.clone(); g.translate(Math.random() * 100, 0, Math.random() * 100); geometries.push(g); } const merged = mergeGeometries(geometries); const mesh = new THREE.Mesh(merged, mat); scene.add(mesh);
|
2. 实例化网格(InstancedMesh)
适合大量相同几何体的场景(如森林、粒子):
1 2 3 4 5 6 7 8 9 10 11 12 13
| const count = 10000; const instancedMesh = new THREE.InstancedMesh(geometry, material, count); const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) { dummy.position.set(Math.random() * 200, 0, Math.random() * 200); dummy.rotation.set(0, Math.random() * Math.PI * 2, 0); dummy.scale.setScalar(0.5 + Math.random() * 1.5); dummy.updateMatrix(); instancedMesh.setMatrixAt(i, dummy.matrix); } instancedMesh.instanceMatrix.needsUpdate = true; scene.add(instancedMesh);
|
3. LOD(Level of Detail)
根据距离切换不同精度的模型:
1 2 3 4 5 6 7 8 9 10 11 12
| import { LOD } from 'three';
const lod = new LOD();
lod.addLevel(lowPolyMesh, 50);
lod.addLevel(mediumPolyMesh, 20);
lod.addLevel(highPolyMesh, 0);
scene.add(lod);
|
4. 其他优化措施
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| renderer.setAnimationLoop(null); let lastTime = 0; function animate(time) { if (time - lastTime >= 16) { lastTime = time; renderer.render(scene, camera); } requestAnimationFrame(animate); }
directionalLight.shadow.mapSize.width = 512; directionalLight.shadow.mapSize.height = 512;
renderer.shadowMap.enabled = false; renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
十、加载外部模型
Three.js 通过加载器支持多种 3D 格式:
1
| npm install three/examples/jsm/loaders/GLTFLoader.js
|
1 2 3 4 5 6 7 8 9 10 11 12
| import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader(); loader.load('model.glb', (gltf) => { scene.add(gltf.scene);
const mixer = new THREE.AnimationMixer(gltf.scene); gltf.animations.forEach((clip) => { mixer.clipAction(clip).play(); }); });
|
十一、常见问题
Q1: Three.js 包体积太大怎么办
1 2 3 4 5
| import { Scene, PerspectiveCamera, WebGLRenderer } from 'three';
import * as THREE from 'three';
|
Q2: 模型加载后看不到
Q3: 如何提高模型加载速度
1 2 3 4 5 6
| import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/'); loader.setDRACOLoader(dracoLoader);
|
Q4: 如何检测性能瓶颈
1 2 3 4 5 6 7 8 9 10 11 12 13
|
import Stats from 'three/addons/libs/stats.module.js';
const stats = new Stats(); document.body.appendChild(stats.dom);
function animate() { stats.begin(); renderer.render(scene, camera); stats.end(); requestAnimationFrame(animate); }
|
十二、WebGPU 与未来
WebGPU 是 WebGL 的下一代 API,提供更底层的 GPU 控制、更低的 CPU 开销和更好的性能。
1 2 3 4
| import { WebGPURenderer } from 'three/addons/renderers/webgpu/WebGPURenderer.js';
const renderer = new WebGPURenderer({ antialias: true });
|
WebGPU 的优势:
- 更高效的 GPU 计算(通用计算 Compute Shader)
- 更低的 API 调用开销
- 更好的多线程支持
- 更接近现代图形 API(Vulkan/Metal/DirectX 12)
目前 Chrome、Edge、Safari 已支持,Three.js r150+ 开始实验性支持。
十三、推荐学习路径
- 理解 Three.js 四大核心:Scene、Camera、Renderer、Mesh
- 掌握内置几何体和常用材质,搭建简单场景
- 添加光源和阴影,理解光照对材质的影响
- 用 OrbitControls 进行交互
- 学习纹理贴图和使用外部模型(GLTF)
- 优化大规模场景:InstancedMesh、合并几何体、LOD
- 了解 ShaderMaterial 和自定义着色器(进阶)
- 关注 WebGPU 的发展