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