Skip to content

前言 🌈 ​

在开发 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 Calls1000+<10090%
FPS3060100%
内存占用2GB500MB75%
加载时间5s2s60%

最佳实践建议 💡 ​

  1. 预优化准则

    • 先测量,后优化
    • 找到性能瓶颈
    • 权衡收益成本
  2. 开发阶段

    • 使用开发工具监控性能
    • 保持代码可维护性
    • 做好性能测试
  3. 部署阶段

    • 资源压缩
    • CDN加速
    • 分包加载

总结 📝 ​

Three.js 性能优化是一个系统工程,需要从多个层面进行优化:

  • 模型层面:合并、实例化、LOD
  • 渲染层面:配置优化、缓冲区管理
  • 资源层面:纹理压缩、预加载
  • 运行时:内存管理、动画优化

最重要的是要根据实际项目需求和性能瓶颈,选择合适的优化策略。

参考资料 📚 ​

希望这篇文章能帮助大家更好地优化 Three.js 项目!如果有任何问题,欢迎在评论区讨论。 🎉