Three.js 性能优化 #

性能优化是 3D 应用开发中的重要环节。良好的性能优化可以让你的应用在各种设备上流畅运行。

性能指标 #

帧率(FPS) #

text
帧率目标:
┌─────────────────────────────────────────┐
│  60 FPS  │  30 FPS  │  15 FPS  │  < 15  │
│  优秀    │  可接受   │  较差    │  卡顿   │
└─────────────────────────────────────────┘

每帧时间预算:
60 FPS = 16.67ms / 帧
30 FPS = 33.33ms / 帧

性能监控 #

javascript
import Stats from 'three/addons/libs/stats.module.js';

const stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  
  renderer.render(scene, camera);
  
  stats.end();
  requestAnimationFrame(animate);
}
animate();

自定义性能监控 #

javascript
const fps = {
  frames: 0,
  lastTime: performance.now(),
  value: 0
};

function updateFPS() {
  fps.frames++;
  const currentTime = performance.now();
  
  if (currentTime >= fps.lastTime + 1000) {
    fps.value = Math.round((fps.frames * 1000) / (currentTime - fps.lastTime));
    fps.frames = 0;
    fps.lastTime = currentTime;
    console.log(`FPS: ${fps.value}`);
  }
}

function animate() {
  requestAnimationFrame(animate);
  updateFPS();
  renderer.render(scene, camera);
}
animate();

渲染优化 #

减少绘制调用 #

javascript
// 不好的做法:每个物体单独渲染
for (let i = 0; i < 1000; i++) {
  const mesh = new THREE.Mesh(geometry, material);
  mesh.position.set(Math.random() * 10, 0, Math.random() * 10);
  scene.add(mesh);
}

// 好的做法:使用 InstancedMesh
const count = 1000;
const mesh = new THREE.InstancedMesh(geometry, material, count);

const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
  dummy.position.set(Math.random() * 10, 0, Math.random() * 10);
  dummy.updateMatrix();
  mesh.setMatrixAt(i, dummy.matrix);
}

scene.add(mesh);

几何体合并 #

javascript
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';

const geometries = [];
for (let i = 0; i < 100; i++) {
  const geometry = new THREE.BoxGeometry(1, 1, 1);
  geometry.translate(Math.random() * 10, 0, Math.random() * 10);
  geometries.push(geometry);
}

const mergedGeometry = mergeGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);

视锥裁剪 #

javascript
// Three.js 默认启用视锥裁剪
// 确保物体的 boundingSphere 或 boundingBox 已计算

geometry.computeBoundingSphere();
geometry.computeBoundingBox();

// 对于动态更新的物体
mesh.frustumCulled = true;

LOD(细节层次) #

javascript
const lod = new THREE.LOD();

const highDetail = new THREE.Mesh(
  new THREE.SphereGeometry(1, 64, 32),
  highQualityMaterial
);
lod.addLevel(highDetail, 0);

const mediumDetail = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 16),
  mediumQualityMaterial
);
lod.addLevel(mediumDetail, 20);

const lowDetail = new THREE.Mesh(
  new THREE.SphereGeometry(1, 16, 8),
  lowQualityMaterial
);
lod.addLevel(lowDetail, 50);

scene.add(lod);

遮挡剔除 #

javascript
import { OcclusionCulling } from 'three/addons/utils/OcclusionCulling.js';

const occlusionCulling = new OcclusionCulling(scene, camera);
occlusionCulling.update();

材质优化 #

简化材质 #

javascript
// 不好的做法:复杂材质
const complexMaterial = new THREE.MeshPhysicalMaterial({
  clearcoat: 1.0,
  clearcoatRoughness: 0.1,
  transmission: 0.9,
  thickness: 0.5,
  ior: 1.5
});

// 好的做法:根据需求选择材质
const simpleMaterial = new THREE.MeshLambertMaterial({
  color: 0xff0000
});

const standardMaterial = new THREE.MeshStandardMaterial({
  color: 0xff0000,
  metalness: 0.5,
  roughness: 0.5
});

共享材质 #

javascript
// 不好的做法:每个物体创建新材质
for (let i = 0; i < 100; i++) {
  const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
}

// 好的做法:共享材质
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
for (let i = 0; i < 100; i++) {
  const mesh = new THREE.Mesh(geometry, sharedMaterial);
  scene.add(mesh);
}

材质属性优化 #

javascript
material.precision = 'lowp';

material.flatShading = true;

material.wireframe = false;

material.side = THREE.FrontSide;

几何体优化 #

减少顶点数 #

javascript
// 高精度
const highPoly = new THREE.SphereGeometry(1, 64, 32);

// 中等精度
const mediumPoly = new THREE.SphereGeometry(1, 32, 16);

// 低精度
const lowPoly = new THREE.SphereGeometry(1, 16, 8);

共享几何体 #

javascript
// 不好的做法:每个物体创建新几何体
for (let i = 0; i < 100; i++) {
  const geometry = new THREE.BoxGeometry(1, 1, 1);
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
}

// 好的做法:共享几何体
const sharedGeometry = new THREE.BoxGeometry(1, 1, 1);
for (let i = 0; i < 100; i++) {
  const mesh = new THREE.Mesh(sharedGeometry, material);
  scene.add(mesh);
}

BufferGeometry 优化 #

javascript
// 删除不需要的属性
geometry.deleteAttribute('uv');
geometry.deleteAttribute('normal');

// 压缩属性
geometry.setAttribute('position', new THREE.BufferAttribute(
  new Float32Array(positions),
  3,
  false
));

// 使用 Int16Array 减少内存
geometry.setAttribute('position', new THREE.BufferAttribute(
  new Int16Array(positions),
  3,
  true
));

纹理优化 #

纹理尺寸 #

javascript
// 推荐:2 的幂次方
const recommendedSizes = [64, 128, 256, 512, 1024, 2048];

// 非 2 的幂次方纹理会被调整
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;

纹理压缩 #

javascript
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

const ktx2Loader = new KTX2Loader(manager);
ktx2Loader.setTranscoderPath('jsm/libs/basis/');
ktx2Loader.detectSupport(renderer);

ktx2Loader.load('texture.ktx2', (texture) => {
  material.map = texture;
});

各向异性过滤 #

javascript
// 限制各向异性过滤级别
texture.anisotropy = Math.min(4, renderer.capabilities.getMaxAnisotropy());

纹理缓存 #

javascript
const textureCache = new Map();

function loadTexture(url) {
  if (textureCache.has(url)) {
    return textureCache.get(url);
  }
  
  const texture = textureLoader.load(url);
  textureCache.set(url, texture);
  return texture;
}

光照优化 #

减少光源数量 #

javascript
// 不好的做法:过多光源
for (let i = 0; i < 20; i++) {
  const light = new THREE.PointLight(0xffffff, 1);
  scene.add(light);
}

// 好的做法:合理使用光源
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);

阴影优化 #

javascript
// 降低阴影贴图分辨率
light.shadow.mapSize.width = 512;
light.shadow.mapSize.height = 512;

// 限制阴影范围
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 50;

// 使用 PCFSoftShadowMap
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

光照贴图 #

javascript
// 使用预计算的光照贴图
const lightMap = textureLoader.load('lightmap.jpg');
material.lightMap = lightMap;
material.lightMapIntensity = 1.0;

内存管理 #

及时释放资源 #

javascript
// 释放几何体
geometry.dispose();

// 释放材质
material.dispose();

// 释放纹理
texture.dispose();

// 释放渲染目标
renderTarget.dispose();

// 从场景移除物体
scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();

批量释放 #

javascript
function disposeObject(obj) {
  obj.traverse((child) => {
    if (child.geometry) {
      child.geometry.dispose();
    }
    if (child.material) {
      if (Array.isArray(child.material)) {
        child.material.forEach((material) => disposeMaterial(material));
      } else {
        disposeMaterial(child.material);
      }
    }
  });
}

function disposeMaterial(material) {
  material.dispose();
  
  Object.values(material).forEach((value) => {
    if (value && typeof value.dispose === 'function') {
      value.dispose();
    }
  });
}

disposeObject(model);
scene.remove(model);

资源监控 #

javascript
const info = renderer.info;

console.log('内存使用:');
console.log('几何体:', info.memory.geometries);
console.log('纹理:', info.memory.textures);

console.log('渲染统计:');
console.log('绘制调用:', info.render.calls);
console.log('三角形:', info.render.triangles);
console.log('点:', info.render.points);
console.log('线:', info.render.lines);

渲染器优化 #

像素比限制 #

javascript
// 限制像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

渲染区域限制 #

javascript
// 只渲染可见区域
const scissor = new THREE.Vector4();
renderer.getScissor(scissor);

// 设置渲染区域
renderer.setViewport(x, y, width, height);
renderer.setScissor(x, y, width, height);
renderer.setScissorTest(true);

按需渲染 #

javascript
let needsRender = true;

function render() {
  if (needsRender) {
    renderer.render(scene, camera);
    needsRender = false;
  }
}

// 只在需要时标记需要渲染
controls.addEventListener('change', () => {
  needsRender = true;
});

window.addEventListener('resize', () => {
  needsRender = true;
});

function animate() {
  requestAnimationFrame(animate);
  render();
}
animate();

降低帧率 #

javascript
const targetFPS = 30;
const interval = 1000 / targetFPS;
let lastTime = 0;

function animate(currentTime) {
  requestAnimationFrame(animate);
  
  const delta = currentTime - lastTime;
  
  if (delta >= interval) {
    lastTime = currentTime - (delta % interval);
    renderer.render(scene, camera);
  }
}
animate(0);

Web Worker #

在 Worker 中计算 #

javascript
// main.js
const worker = new Worker('worker.js');

worker.onmessage = (e) => {
  const { positions } = e.data;
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geometry.attributes.position.needsUpdate = true;
};

// worker.js
self.onmessage = (e) => {
  const { count } = e.data;
  const positions = new Float32Array(count * 3);
  
  for (let i = 0; i < count; i++) {
    positions[i * 3] = Math.random() * 10;
    positions[i * 3 + 1] = Math.random() * 10;
    positions[i * 3 + 2] = Math.random() * 10;
  }
  
  self.postMessage({ positions }, [positions.buffer]);
};

性能优化清单 #

text
□ 渲染优化
  ├─ □ 使用 InstancedMesh
  ├─ □ 合并几何体
  ├─ □ 启用视锥裁剪
  └─ □ 使用 LOD

□ 材质优化
  ├─ □ 共享材质
  ├─ □ 简化材质类型
  └─ □ 减少材质属性

□ 几何体优化
  ├─ □ 共享几何体
  ├─ □ 减少顶点数
  └─ □ 删除不需要的属性

□ 纹理优化
  ├─ □ 使用合适尺寸
  ├─ □ 压缩纹理
  └─ □ 缓存纹理

□ 光照优化
  ├─ □ 减少光源数量
  ├─ □ 优化阴影设置
  └─ □ 使用光照贴图

□ 内存管理
  ├─ □ 及时释放资源
  ├─ □ 监控内存使用
  └─ □ 避免内存泄漏

□ 渲染器优化
  ├─ □ 限制像素比
  ├─ □ 按需渲染
  └─ □ 降低帧率

完整示例 #

javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js';

const stats = new Stats();
document.body.appendChild(stats.dom);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 10, 10);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 10, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -20;
directionalLight.shadow.camera.right = 20;
directionalLight.shadow.camera.top = 20;
directionalLight.shadow.camera.bottom = -20;
scene.add(directionalLight);

const sharedGeometry = new THREE.BoxGeometry(1, 1, 1);
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0xff6b6b });

const count = 1000;
const mesh = new THREE.InstancedMesh(sharedGeometry, sharedMaterial, count);
mesh.castShadow = true;
mesh.receiveShadow = true;

const dummy = new THREE.Object3D();
const colors = [];

for (let i = 0; i < count; i++) {
  dummy.position.set(
    (Math.random() - 0.5) * 20,
    Math.random() * 10,
    (Math.random() - 0.5) * 20
  );
  dummy.rotation.set(
    Math.random() * Math.PI,
    Math.random() * Math.PI,
    Math.random() * Math.PI
  );
  dummy.scale.setScalar(Math.random() * 0.5 + 0.5);
  dummy.updateMatrix();
  
  mesh.setMatrixAt(i, dummy.matrix);
  
  colors.push(Math.random(), Math.random() * 0.5, Math.random() * 0.5);
}

mesh.instanceMatrix.needsUpdate = true;

const colorAttribute = new THREE.InstancedBufferAttribute(new Float32Array(colors), 3);
mesh.instanceColor = colorAttribute;

scene.add(mesh);

const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);

const clock = new THREE.Clock();
let lastTime = 0;
const targetFPS = 60;
const interval = 1000 / targetFPS;

function animate(currentTime) {
  requestAnimationFrame(animate);
  
  stats.begin();
  
  const delta = currentTime - lastTime;
  
  if (delta >= interval) {
    lastTime = currentTime - (delta % interval);
    
    const elapsedTime = clock.getElapsedTime();
    
    mesh.rotation.y = elapsedTime * 0.1;
    
    controls.update();
    renderer.render(scene, camera);
  }
  
  stats.end();
}
animate(0);

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

console.log('渲染统计:', renderer.info);

下一步 #

现在你已经掌握了性能优化技巧,接下来学习 实战案例,通过完整项目巩固所学知识!

最后更新:2026-03-28