性能优化
性能优化是 3D 应用开发中不可忽视的环节。良好的性能可以确保流畅的用户体验,特别是在移动设备或低端硬件上。本章将介绍 Three.js 中的各种性能优化技术和最佳实践。
性能分析
测量帧率
使用 stats.js 或手动计算来监控帧率:
// 使用 stats.js
import Stats from 'three/addons/libs/stats.module.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// 渲染代码
stats.end();
requestAnimationFrame(animate);
}
// 手动计算帧率
let frameCount = 0;
let lastTime = performance.now();
function animate() {
frameCount++;
const now = performance.now();
if (now >= lastTime + 1000) {
const fps = Math.round((frameCount * 1000) / (now - lastTime));
console.log(`FPS: ${fps}`);
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(animate);
}
渲染器信息
查看渲染器的统计信息:
console.log(renderer.info);
// {
// memory: { geometries: 1, textures: 2 },
// render: { calls: 3, triangles: 1000, points: 0, lines: 0 }
// }
renderer.info.render.calls 表示每帧的绘制调用次数,这是重要的性能指标。
几何体优化
减少顶点数量
顶点数量直接影响 GPU 的处理负担:
// 使用适当的分段数
const geometry = new THREE.SphereGeometry(1, 16, 16); // 而不是 64, 64
// 根据物体大小调整细节
function createSphere(radius) {
const segments = Math.max(8, Math.floor(radius * 8));
return new THREE.SphereGeometry(radius, segments, segments);
}
合并几何体
将多个静态物体合并为一个:
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() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
geometries.push(geometry);
}
const mergedGeometry = mergeGeometries(geometries);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);
合并后只需一次绘制调用,大大减少 CPU 与 GPU 的通信。
实例化渲染
对于大量相同几何体,使用实例化渲染:
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 10000;
const mesh = new THREE.InstancedMesh(geometry, material, count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
mesh.setMatrixAt(i, matrix);
}
mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);
实例化渲染比合并几何体更灵活,可以单独更新每个实例。
材质优化
选择合适的材质
材质复杂度影响渲染性能:
// 最快:无光照
const basicMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 较快:仅漫反射
const lambertMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
// 中等:有高光
const phongMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
// 较慢:PBR
const standardMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
// 最慢:高级 PBR
const physicalMaterial = new THREE.MeshPhysicalMaterial({ color: 0x00ff00 });
根据场景需求选择最简单的材质。
共享材质
多个物体共享同一个材质实例:
// 好的做法:共享材质
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
纹理优化
纹理尺寸
纹理尺寸应该是 2 的幂次方(如 512, 1024, 2048):
// 推荐的尺寸
const sizes = [64, 128, 256, 512, 1024, 2048, 4096];
// 避免非二次幂尺寸
// 虽然现代 GPU 支持,但性能会受影响
各向异性过滤
const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
texture.anisotropy = Math.min(4, maxAnisotropy); // 不要超过 4
纹理压缩
使用压缩纹理格式减少内存占用:
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;
});
光源和阴影优化
减少光源数量
// 少量高效的光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);
点光源和聚光灯的开销比方向光大得多,应尽量少用。
阴影优化
const light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
// 限制阴影贴图尺寸
light.shadow.mapSize.width = 1024; // 而不是 4096
light.shadow.mapSize.height = 1024;
// 限制阴影范围
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 50;
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
light.shadow.camera.top = 10;
light.shadow.camera.bottom = -10;
// 使用更简单的阴影类型
renderer.shadowMap.type = THREE.BasicShadowMap; // 或 PCFShadowMap
渲染优化
按需渲染
只在场景变化时渲染:
let needsRender = true;
controls.addEventListener('change', () => { needsRender = true; });
window.addEventListener('resize', () => { needsRender = true; });
function animate() {
requestAnimationFrame(animate);
controls.update();
if (needsRender) {
renderer.render(scene, camera);
needsRender = false;
}
}
降低分辨率
// 限制像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 或降低渲染分辨率
const scale = 0.75;
renderer.setSize(
window.innerWidth * scale,
window.innerHeight * scale,
false
);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
LOD(细节层次)
根据距离使用不同精度的模型:
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 0-50 单位
lod.addLevel(mediumDetailMesh, 50); // 50-100 单位
lod.addLevel(lowDetailMesh, 100); // 100+ 单位
scene.add(lod);
function animate() {
lod.update(camera);
}
内存管理
释放资源
不再使用的资源需要手动释放:
// 释放几何体
geometry.dispose();
// 释放材质
material.dispose();
// 释放纹理
texture.dispose();
// 完整释放物体
function disposeObject(object) {
object.traverse((child) => {
if (child.geometry) child.geometry.dispose();
if (child.material) {
const materials = Array.isArray(child.material) ? child.material : [child.material];
materials.forEach((material) => {
Object.values(material).forEach((value) => {
if (value && typeof value.dispose === 'function') {
value.dispose();
}
});
material.dispose();
});
}
});
}
// 使用
scene.remove(mesh);
disposeObject(mesh);
避免内存泄漏
// 移除事件监听器
function onResize() {}
window.addEventListener('resize', onResize);
// 不再需要时
window.removeEventListener('resize', onResize);
// 清理渲染目标
renderTarget.dispose();
// 清理后处理
composer.dispose();
移动端优化
设备检测
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
// 根据设备调整设置
const pixelRatio = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
const shadowMapSize = isMobile ? 512 : 2048;
const antialias = !isMobile;
renderer.setPixelRatio(pixelRatio);
renderer.shadowMap.enabled = !isMobile;
WebGL 扩展检测
const gl = renderer.getContext();
const hasInstancing = gl.getExtension('ANGLE_instanced_arrays');
const hasFloatTextures = gl.getExtension('OES_texture_float');
触摸事件优化
// 防止默认触摸行为
renderer.domElement.addEventListener('touchstart', (e) => {
e.preventDefault();
}, { passive: false });
// 节流触摸事件
let lastTouchTime = 0;
renderer.domElement.addEventListener('touchmove', (e) => {
const now = Date.now();
if (now - lastTouchTime < 16) return;
lastTouchTime = now;
// 处理触摸
});
性能检查清单
几何体
- 顶点数量是否合理?
- 是否可以合并静态几何体?
- 是否可以使用实例化渲染?
- 是否需要使用 LOD?
材质和纹理
- 是否使用了最简单的材质?
- 纹理尺寸是否合理?
- 是否共享了材质和纹理?
光源和阴影
- 光源数量是否过多?
- 阴影贴图尺寸是否合适?
- 是否限制了阴影范围?
渲染
- 是否启用了按需渲染?
- 分辨率是否合适?
- 后期处理效果是否过多?
内存
- 不再使用的资源是否释放?
- 是否存在内存泄漏?
完整示例
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 性能配置
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const config = {
pixelRatio: isMobile ? 1 : Math.min(window.devicePixelRatio, 2),
shadowMapSize: isMobile ? 512 : 2048,
antialias: !isMobile
};
// 场景设置
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(10, 10, 10);
const renderer = new THREE.WebGLRenderer({ antialias: config.antialias });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(config.pixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 使用实例化渲染
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshLambertMaterial({ color: 0x00ff88 });
const count = 1000;
const mesh = new THREE.InstancedMesh(geometry, material, count);
mesh.castShadow = true;
mesh.receiveShadow = true;
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(
Math.random() * 20 - 10,
Math.random() * 20 - 10,
Math.random() * 20 - 10
);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
// 地面
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x333344 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// 光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = config.shadowMapSize;
directionalLight.shadow.mapSize.height = config.shadowMapSize;
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);
// 按需渲染
let needsRender = true;
controls.addEventListener('change', () => { needsRender = true; });
function animate() {
requestAnimationFrame(animate);
controls.update();
if (needsRender) {
renderer.render(scene, camera);
needsRender = false;
}
}
animate();
// 响应窗口变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
needsRender = true;
});
小结
性能优化是 3D 应用开发的关键环节:
- 分析工具帮助定位性能瓶颈
- 几何体优化包括减少顶点、合并、实例化
- 材质优化选择合适的材质类型,共享材质实例
- 渲染优化按需渲染、降低分辨率、使用 LOD
- 内存管理及时释放不再使用的资源
- 移动端优化根据设备能力调整渲染策略
优化是一个持续的过程,需要在视觉效果和性能之间找到平衡。