Three.js是基于WebGL封装的3D渲染库,广泛用于Web端3D展示、数据可视化和交互场景。当场景复杂度上升,顶点数超过十万级或纹理分辨率过高时,帧率会迅速下降。Three.js性能优化涉及几何体简化、材质合并、渲染管线调优和内存管理等多个层面,目标是稳定保持60fps渲染。
几何体优化与顶点合并
Draw Call数量是WebGL性能的核心瓶颈。每个Mesh对应一次Draw Call,浏览器对不同WebGL实现有上限(通常几百到几千)。合并几何体减少Draw Call是最直接有效的优化手段。
import * as THREE from 'three';
// 反面示例:1000个独立Mesh = 1000次Draw Call
const badScene = new THREE.Scene();
for (let i = 0; i < 1000; i++) {
const geo = new THREE.BoxGeometry(1, 1, 1);
const mat = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(Math.random() * 100, 0, Math.random() * 100);
badScene.add(mesh);
}
// 性能:约5-15fps
// 正面示例:合并几何体 = 1次Draw Call
const geometries = [];
const matrix = new THREE.Matrix4();
for (let i = 0; i < 1000; i++) {
const geo = new THREE.BoxGeometry(1, 1, 1);
matrix.makeTranslation(Math.random() * 100, 0, Math.random() * 100);
geo.applyMatrix4(matrix);
geometries.push(geo);
}
const mergedGeo = THREE.BufferGeometryUtils.mergeGeometries(geometries);
const mergedMesh = new THREE.Mesh(mergedGeo, material);
scene.add(mergedMesh);
// 性能:稳定60fps
InstancedMesh适用于大量相同几何体不同变换的场景,如森林、建筑群:
const count = 10000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(
Math.random() * 200,
Math.random() * 50,
Math.random() * 200
);
dummy.rotation.y = Math.random() * Math.PI * 2;
dummy.scale.setScalar(Math.random() * 2 + 0.5);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);
纹理优化与内存管理
纹理是GPU内存的主要消耗者。一张4096×4096的RGBA纹理占用64MB显存,10张即640MB。合理设置纹理尺寸、格式和压缩方案至关重要。
const loader = new THREE.TextureLoader();
const texture = loader.load('diffuse.jpg', (tex) => {
tex.generateMipmaps = true;
tex.minFilter = THREE.LinearMipmapLinearFilter;
tex.magFilter = THREE.LinearFilter;
tex.colorSpace = THREE.SRGBColorSpace;
tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
});
// 限制纹理最大尺寸
const maxTextureSize = 2048;
if (texture.image.width > maxTextureSize) {
const canvas = document.createElement('canvas');
canvas.width = maxTextureSize;
canvas.height = (texture.image.height / texture.image.width) * maxTextureSize;
const ctx = canvas.getContext('2d');
ctx.drawImage(texture.image, 0, 0, canvas.width, canvas.height);
texture.image = canvas;
texture.needsUpdate = true;
}
KTX2纹理压缩格式可将纹理内存占用降低4-8倍,且不需GPU实时解压:
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
const ktx2Loader = new KTX2Loader()
.setTranscoderPath('libs/basis/')
.detectSupport(renderer);
const compressedTexture = ktx2Loader.load('texture.ktx2', (tex) => {
tex.colorSpace = THREE.SRGBColorSpace;
});
资源销毁防止内存泄漏,切换场景或卸载组件时必须手动释放:
function disposeScene(scene) {
scene.traverse((child) => {
if (child.isMesh) {
child.geometry.dispose();
if (child.material) {
const materials = Array.isArray(child.material)
? child.material
: [child.material];
materials.forEach((mat) => {
for (const key in mat) {
if (mat[key] && mat[key].isTexture) {
mat[key].dispose();
}
}
mat.dispose();
});
}
}
});
while (scene.children.length > 0) {
scene.remove(scene.children[0]);
}
}
渲染循环调优与LOD策略
按需渲染避免不必要的帧刷新,静态场景仅在交互时重绘:
let needsRender = true;
renderer.domElement.addEventListener('pointermove', () => {
needsRender = true;
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
needsRender = true;
});
function animate() {
requestAnimationFrame(animate);
if (needsRender) {
controls.update();
renderer.render(scene, camera);
needsRender = false;
}
}
animate();
LOD(Level of Detail)根据摄像机距离切换不同精度模型,远处用低面数模型节省渲染开销:
const lod = new THREE.LOD();
// 高精度模型:1万面
const highDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 64, 32),
material
);
lod.addLevel(highDetail, 0);
// 中精度模型:500面
const midDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 16, 8),
material
);
lod.addLevel(midDetail, 50);
// 低精度模型:12面
const lowDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 6, 4),
material
);
lod.addLevel(lowDetail, 200);
scene.add(lod);
// 雾效隐藏远处低精度模型的切换边界
scene.fog = new THREE.Fog(0x87ceeb, 100, 300);
scene.background = new THREE.Color(0x87ceeb);
生产环境部署前使用Stats.js监控帧率、内存和Draw Call数量。Chrome DevTools的Performance面板可定位渲染瓶颈。移动端需额外注意WebGL上下文丢失问题,监听webglcontextlost事件并做降级处理。复杂场景建议配合Web Worker做物理计算,避免主线程阻塞导致掉帧。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webgl-yu-threejs3d-xuan-ran-xing-neng-you-hua-shi-zhan-fang/