前言 🌈
在开发 Three.js 项目时,性能优化是一个永恒的话题。本文将结合实际项目经验,详细介绍 Three.js 性能优化的各个方面,并提供具体的优化方案和代码示例。
场景介绍 🎮
假设我们正在开发一个大型的3D可视化项目,包含以下场景:
- 城市建筑模型展示
- 大量动态粒子效果
- 实时数据更新
- 多个光源和阴影
- 后期处理效果
1. 几何体优化 📐
1.1 合并几何体
当场景中有大量相似的几何体时,可以使用合并来减少 draw calls。
js
// 优化前
buildings.forEach(building => {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
// 优化后
const geometries = [];
buildings.forEach(building => {
const geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.translate(building.x, building.y, building.z);
geometries.push(geometry);
});
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);1.2 使用 InstancedMesh
对于大量相同的对象,使用 InstancedMesh 可以显著提升性能。
js
// 优化前: 10000个立方体
for (let i = 0; i < 10000; i++) {
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
// 优化后
const instancedMesh = new THREE.InstancedMesh(
geometry,
material,
10000
);
const matrix = new THREE.Matrix4();
for (let i = 0; i < 10000; i++) {
matrix.setPosition(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50,
);
instancedMesh.setMatrixAt(i, matrix);
}
scene.add(instancedMesh);1.3 LOD (Level of Detail)
根据距离使用不同精度的模型。
js
const lod = new THREE.LOD();
// 高精度模型 (近距离)
const highDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 32, 32, 32);
const highDetailMesh = new THREE.Mesh(highDetailGeometry, material);
lod.addLevel(highDetailMesh, 0);
// 中等精度模型 (中距离)
const mediumDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 16, 16, 16);
const mediumDetailMesh = new THREE.Mesh(mediumDetailGeometry, material);
lod.addLevel(mediumDetailMesh, 50);
// 低精度模型 (远距离)
const lowDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 8, 8, 8);
const lowDetailMesh = new THREE.Mesh(lowDetailGeometry, material);
lod.addLevel(lowDetailMesh, 100);
scene.add(lod);2. 材质优化 🎨
2.1 共享材质
js
// 优化前
objects.forEach(obj => {
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
roughness: 0.5,
metalness: 0.5
});
const mesh = new THREE.Mesh(geometry, material);
});
// 优化后
const sharedMaterial = new THREE.MeshStandardMaterial({
color: 0xff0000,
roughness: 0.5,
metalness: 0.5
});
objects.forEach(obj => {
const mesh = new THREE.Mesh(geometry, sharedMaterial);
});2.2 材质降级
js
// 根据设备性能选择材质
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
const material = isMobile
? new THREE.MeshBasicMaterial({ color: 0xff0000 })
: new THREE.MeshStandardMaterial({
color: 0xff0000,
roughness: 0.5,
metalness: 0.5
});3. 渲染优化 🖼️
3.1 选择合适的渲染器配置
js
const renderer = new THREE.WebGLRenderer({
antialias: false, // 移动端关闭抗锯齿
powerPreference: 'high-performance',
precision: 'mediump', // 使用中等精度
stencil: false,
depth: true
});
// 设置像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));3.2 使用 WebGLRenderer.setScissor
js
// 只渲染视口内的内容
renderer.setScissor(x, y, width, height);
renderer.setScissorTest(true);3.3 使用 WebGL2
js
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
context: canvas.getContext('webgl2'),
antialias: true
});4. 纹理优化 🖼️
4.1 纹理压缩
js
// 使用压缩纹理
const loader = new THREE.KTX2Loader()
.setTranscoderPath('js/libs/basis/')
.detectSupport(renderer);
loader.load('compressed.ktx2', function(texture) {
material.map = texture;
material.needsUpdate = true;
});4.2 纹理预加载
js
const textureLoader = new THREE.TextureLoader();
const loadingManager = new THREE.LoadingManager();
// 预加载所有纹理
const textures = {};
const textureUrls = ['texture1.jpg', 'texture2.jpg', 'texture3.jpg'];
textureUrls.forEach(url => {
textureLoader.load(url, texture => {
textures[url] = texture;
});
});5. 光照和阴影优化 💡
5.1 使用 LightProbe 替代多光源
js
const lightProbe = new THREE.LightProbe();
scene.add(lightProbe);
// 生成环境光照
new THREE.EXRLoader()
.load('environment.exr', function(texture) {
lightProbe.copy(new THREE.LightProbeGenerator.fromCubeTexture(texture));
});5.2 优化阴影设置
js
// 优化阴影贴图大小和精度
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 100;
// 使用 VSM 阴影
renderer.shadowMap.type = THREE.VSMShadowMap;6. 动画优化 🎬
6.1 使用 requestAnimationFrame
js
let lastTime = 0;
const animate = (currentTime) => {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// 基于时间的动画
object.rotation.y += 0.001 * deltaTime;
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);6.2 使用 Object3D.matrixAutoUpdate
js
// 静态对象关闭自动更新
staticObject.matrixAutoUpdate = false;
staticObject.updateMatrix();
// 动态对象保持开启
dynamicObject.matrixAutoUpdate = true;7. 内存管理 🧹
7.1 正确释放资源
js
function dispose() {
// 释放几何体
geometry.dispose();
// 释放材质
material.dispose();
// 释放纹理
texture.dispose();
// 从场景中移除
scene.remove(mesh);
}7.2 使用对象池
js
class ParticlePool {
constructor(size) {
this.pool = [];
this.activeParticles = new Set();
// 预创建粒子
for (let i = 0; i < size; i++) {
this.pool.push(this.createParticle());
}
}
createParticle() {
return new THREE.Mesh(
new THREE.SphereGeometry(0.1),
new THREE.MeshBasicMaterial()
);
}
get() {
const particle = this.pool.pop();
if (particle) {
this.activeParticles.add(particle);
return particle;
}
return this.createParticle();
}
release(particle) {
this.activeParticles.delete(particle);
this.pool.push(particle);
}
}8. 监控和调试 📊
8.1 使用 Stats.js
js
import Stats from 'three/examples/jsm/libs/stats.module';
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// 渲染代码
stats.end();
requestAnimationFrame(animate);
}8.2 自定义性能监控
js
class PerformanceMonitor {
constructor() {
this.fps = 0;
this.frames = 0;
this.lastTime = performance.now();
}
update() {
const currentTime = performance.now();
this.frames++;
if (currentTime >= this.lastTime + 1000) {
this.fps = (this.frames * 1000) / (currentTime - this.lastTime);
this.lastTime = currentTime;
this.frames = 0;
console.log(`FPS: ${this.fps.toFixed(1)}`);
}
}
}9. 实战案例:城市场景优化 🌆
js
class OptimizedCityScene {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({
powerPreference: 'high-performance',
antialias: false
});
// 实例化建筑
this.buildingGeometry = new THREE.BoxGeometry(1, 1, 1);
this.buildingMaterial = new THREE.MeshStandardMaterial();
this.buildingInstances = new THREE.InstancedMesh(
this.buildingGeometry,
this.buildingMaterial,
1000
);
// LOD系统
this.setupLOD();
// 光照优化
this.setupLights();
// 性能监控
this.stats = new Stats();
// 对象池
this.particlePool = new ParticlePool(1000);
}
setupLOD() {
// LOD实现代码
}
setupLights() {
// 光照设置代码
}
update() {
// 更新逻辑
}
dispose() {
// 资源释放
}
}性能优化效果对比 📊
| 优化项目 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| Draw Calls | 1000+ | <100 | 90% |
| FPS | 30 | 60 | 100% |
| 内存占用 | 2GB | 500MB | 75% |
| 加载时间 | 5s | 2s | 60% |
最佳实践建议 💡
预优化准则
- 先测量,后优化
- 找到性能瓶颈
- 权衡收益成本
开发阶段
- 使用开发工具监控性能
- 保持代码可维护性
- 做好性能测试
部署阶段
- 资源压缩
- CDN加速
- 分包加载
总结 📝
Three.js 性能优化是一个系统工程,需要从多个层面进行优化:
- 模型层面:合并、实例化、LOD
- 渲染层面:配置优化、缓冲区管理
- 资源层面:纹理压缩、预加载
- 运行时:内存管理、动画优化
最重要的是要根据实际项目需求和性能瓶颈,选择合适的优化策略。
参考资料 📚
希望这篇文章能帮助大家更好地优化 Three.js 项目!如果有任何问题,欢迎在评论区讨论。 🎉