渲染优化
渲染优化是提升 Three.js 应用性能的核心环节,通过减少 Draw Calls、优化几何体、使用 LOD 等技术显著提升帧率,为用户提供流畅的交互体验。
概述
渲染优化主要关注减少 GPU 负载,包括减少 Draw Calls、优化几何体、合理使用材质等。优化的核心目标是:
- 降低 GPU 负载:减少每帧需要处理的数据量
- 减少状态切换:最小化材质和 Shader 的切换
- 智能剔除:避免渲染不可见的对象
- 合理降级:根据距离调整细节层次
优化效果预期
| 优化技术 | 性能提升 | 实施难度 | 适用场景 |
|---|---|---|---|
| 几何体合并 | 50-90% | 低 | 静态场景 |
| InstancedMesh | 70-95% | 中 | 大量相同几何体 |
| LOD | 30-60% | 中 | 大型场景 |
| 视锥剔除 | 20-50% | 低 | 所有场景 |
| 材质简化 | 20-40% | 低 | 移动端 |
| 纹理压缩 | 10-30% | 中 | 大量纹理场景 |
系统架构
渲染优化系统架构
code
┌─────────────────────────────────────────────────────────────┐
│ 渲染优化系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 数据层 │ │ 处理层 │ │ 渲染层 │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ • 几何体 │ │ • 剔除算法 │ │ • Draw Call │ │
│ │ • 材质 │ │ • LOD 切换 │ │ • 状态管理 │ │
│ │ • 纹理 │ │ • 合并优化 │ │ • 批处理 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 优化策略引擎 │ │
│ ├─────────────────────────────────────────────────┤ │
│ │ InstancedMesh │ 合并 │ LOD │ 剔除 │ 缓存 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘渲染管线优化点
code
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 场景遍历 │ -> │ 剔除判断 │ -> │ 排序组织 │ -> │ 绘制提交 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ ▼
场景图优化 视锥/遮挡 材质排序 Draw Call 优化
LOD 管理 剔除优化 批处理 实例化渲染优化策略总览
性能瓶颈识别
在开始优化前,需要先识别性能瓶颈:
javascript
// 性能瓶颈诊断
function diagnosePerformance() {
const info = renderer.info;
const diagnosis = {
// Draw Call 过多(> 1000 需要优化)
drawCalls: {
value: info.render.calls,
status: info.render.calls > 1000 ? '需要优化' : '良好'
},
// 三角形数量过多
triangles: {
value: info.render.triangles,
status: info.render.triangles > 1000000 ? '需要优化' : '良好'
},
// 几何体数量过多
geometries: {
value: info.memory.geometries,
status: info.memory.geometries > 100 ? '需要优化' : '良好'
},
// 纹理数量过多
textures: {
value: info.memory.textures,
status: info.memory.textures > 50 ? '需要优化' : '良好'
}
};
console.table(diagnosis);
return diagnosis;
}减少绘制调用(Draw Calls)
Draw Calls 是衡量渲染性能的重要指标,每次 Draw Call 都会产生 CPU 到 GPU 的通信开销。
几何体合并
适用于静态场景中多个不移动的几何体。
javascript
import { mergeBufferGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
// 不推荐:多个独立网格
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(Math.random() * 10, 0, Math.random() * 10);
scene.add(mesh);
}
// Draw Calls: 100
// 推荐:合并几何体
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 = mergeBufferGeometries(geometries);
const mergedMesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mergedMesh);
// Draw Calls: 1几何体合并配置参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
geometries | Array | - | 要合并的几何体数组 |
useGroups | Boolean | false | 是否使用材质组 |
更新顶点 | Boolean | true | 是否更新顶点数据 |
javascript
// 高级合并:保留材质信息
const geometries = [];
const materials = [];
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1).clone();
geometry.translate(Math.random() * 10, 0, Math.random() * 10);
geometries.push(geometry);
materials.push(new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(i / 100, 1, 0.5)
}));
}
// 合并并保留材质组
const mergedGeometry = mergeBufferGeometries(geometries, true);
const mergedMesh = new THREE.Mesh(mergedGeometry, materials);
scene.add(mergedMesh);InstancedMesh
用于渲染大量相同几何体的实例,是最有效的优化方式之一。
javascript
// 创建 InstancedMesh
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 1000;
const mesh = new THREE.InstancedMesh(geometry, material, count);
// 设置每个实例的变换
const matrix = new THREE.Matrix4();
const position = new THREE.Vector3();
const rotation = new THREE.Euler();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3(1, 1, 1);
for (let i = 0; i < count; i++) {
position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
);
rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
quaternion.setFromEuler(rotation);
matrix.compose(position, quaternion, scale);
mesh.setMatrixAt(i, matrix);
}
mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);
// 为每个实例设置颜色
const colors = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const color = new THREE.Color().setHSL(Math.random(), 1, 0.5);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
}
mesh.instanceColor = new THREE.InstancedBufferAttribute(colors, 3);InstancedMesh 配置参数
| 参数 | 类型 | 说明 |
|---|---|---|
geometry | BufferGeometry | 实例使用的几何体 |
material | Material | 实例使用的材质 |
count | Number | 实例数量 |
instanceMatrix | InstancedBufferAttribute | 实例变换矩阵 |
instanceColor | InstancedBufferAttribute | 实例颜色属性 |
InstancedMesh 性能对比
| 对象数量 | 普通网格 | InstancedMesh | 性能提升 |
|---|---|---|---|
| 100 | 100 Draw Calls | 1 Draw Call | 99% |
| 1,000 | 1,000 Draw Calls | 1 Draw Call | 99.9% |
| 10,000 | 10,000 Draw Calls | 1 Draw Call | 99.99% |
材质合并与共享
javascript
// 不推荐:每个对象不同材质
objects.forEach((obj, i) => {
obj.material = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(i / objects.length, 1, 0.5)
});
});
// 推荐:共享材质
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
objects.forEach(obj => {
obj.material = sharedMaterial;
});批量渲染管理器
javascript
class BatchRenderer {
constructor() {
this.batches = new Map();
}
add(mesh, batchId) {
if (!this.batches.has(batchId)) {
this.batches.set(batchId, {
meshes: [],
material: mesh.material.clone()
});
}
this.batches.get(batchId).meshes.push(mesh);
}
merge() {
const results = [];
this.batches.forEach((batch, id) => {
const geometries = batch.meshes.map(m => {
const geo = m.geometry.clone();
geo.applyMatrix4(m.matrixWorld);
return geo;
});
const merged = mergeBufferGeometries(geometries);
const mesh = new THREE.Mesh(merged, batch.material);
results.push(mesh);
// 清理原始网格
batch.meshes.forEach(m => {
m.parent?.remove(m);
m.geometry.dispose();
});
});
return results;
}
}
// 使用
const batchRenderer = new BatchRenderer();
// 添加网格到批次
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(i, 0, 0);
scene.add(mesh);
batchRenderer.add(mesh, 'batch1');
}
// 合并批次
const mergedMeshes = batchRenderer.merge();
mergedMeshes.forEach(mesh => scene.add(mesh));LOD 细节层次
LOD(Level of Detail)根据对象与相机的距离自动切换不同精度的模型。
基础 LOD
javascript
const lod = new THREE.LOD();
// 高细节(近距离)
const highDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
material
);
lod.addLevel(highDetail, 0);
// 中等细节
const mediumDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 16, 16),
material
);
lod.addLevel(mediumDetail, 10);
// 低细节(远距离)
const lowDetail = new THREE.Mesh(
new THREE.SphereGeometry(1, 8, 8),
material
);
lod.addLevel(lowDetail, 20);
scene.add(lod);
// 更新 LOD
function animate() {
requestAnimationFrame(animate);
lod.update(camera);
renderer.render(scene, camera);
}LOD 配置参数
| 方法 | 参数 | 说明 |
|---|---|---|
addLevel(object, distance) | object: Object3D, distance: Number | 添加细节层次 |
getCurrentLevel() | - | 获取当前层级 |
getObjectForDistance(distance) | distance: Number | 获取指定距离的对象 |
update(camera) | camera: Camera | 更新 LOD 状态 |
动态 LOD 系统
javascript
class DynamicLODSystem {
constructor() {
this.objects = [];
this.levels = [
{ distance: 0, segments: 32 },
{ distance: 10, segments: 16 },
{ distance: 20, segments: 8 },
{ distance: 30, segments: 4 }
];
}
addObject(position, geometry, material) {
const lod = new THREE.LOD();
this.levels.forEach(level => {
const simplifiedGeo = this.simplifyGeometry(geometry, level.segments);
const mesh = new THREE.Mesh(simplifiedGeo, material);
lod.addLevel(mesh, level.distance);
});
lod.position.copy(position);
this.objects.push(lod);
scene.add(lod);
return lod;
}
simplifyGeometry(geometry, segments) {
// 根据段数创建简化几何体
// 这里只是一个示例,实际可以使用更复杂的简化算法
return new THREE.SphereGeometry(1, segments, segments);
}
update(camera) {
this.objects.forEach(lod => lod.update(camera));
}
getStats() {
let totalTriangles = 0;
this.objects.forEach(lod => {
const currentLevel = lod.getCurrentLevel();
const mesh = lod.getObjectForDistance(currentLevel);
if (mesh && mesh.geometry) {
totalTriangles += mesh.geometry.index
? mesh.geometry.index.count / 3
: mesh.geometry.attributes.position.count / 3;
}
});
return {
objects: this.objects.length,
triangles: Math.round(totalTriangles)
};
}
}
// 使用
const lodSystem = new DynamicLODSystem();
for (let i = 0; i < 50; i++) {
const position = new THREE.Vector3(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
lodSystem.addObject(position, baseGeometry, material);
}
function animate() {
requestAnimationFrame(animate);
lodSystem.update(camera);
console.log(lodSystem.getStats());
renderer.render(scene, camera);
}视锥剔除
视锥剔除自动跳过视锥体外的对象渲染,Three.js 默认启用。
自动视锥剔除
Three.js 自动进行视锥剔除。
javascript
// 确保边界框已计算
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
// 检查是否在视锥内
const frustum = new THREE.Frustum();
const projScreenMatrix = new THREE.Matrix4();
projScreenMatrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
);
frustum.setFromProjectionMatrix(projScreenMatrix);
if (frustum.intersectsObject(mesh)) {
console.log('对象在视锥内');
}
// 检查边界球
if (frustum.intersectsSphere(mesh.geometry.boundingSphere)) {
console.log('边界球在视锥内');
}
// 检查边界盒
if (frustum.intersectsBox(mesh.geometry.boundingBox)) {
console.log('边界盒在视锥内');
}手动剔除管理
javascript
class FrustumCullingManager {
constructor() {
this.frustum = new THREE.Frustum();
this.projScreenMatrix = new THREE.Matrix4();
}
update(camera) {
this.projScreenMatrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
);
this.frustum.setFromProjectionMatrix(this.projScreenMatrix);
}
cullObjects(objects) {
let visibleCount = 0;
let culledCount = 0;
objects.forEach(obj => {
// 确保有边界球
if (!obj.geometry.boundingSphere) {
obj.geometry.computeBoundingSphere();
}
// 更新世界矩阵
obj.updateMatrixWorld();
// 克隆边界球并应用世界变换
const sphere = obj.geometry.boundingSphere.clone();
sphere.applyMatrix4(obj.matrixWorld);
// 判断是否在视锥内
const isVisible = this.frustum.intersectsSphere(sphere);
obj.visible = isVisible;
if (isVisible) {
visibleCount++;
} else {
culledCount++;
}
});
return { visibleCount, culledCount };
}
}
// 使用
const cullingManager = new FrustumCullingManager();
const objects = []; // 场景中的对象
function animate() {
requestAnimationFrame(animate);
cullingManager.update(camera);
const stats = cullingManager.cullObjects(objects);
console.log(`可见: ${stats.visibleCount}, 剔除: ${stats.culledCount}`);
renderer.render(scene, camera);
}遮挡剔除
遮挡剔除隐藏被其他对象完全遮挡的对象。
javascript
class OcclusionCulling {
constructor() {
this.occluders = [];
this.raycaster = new THREE.Raycaster();
}
addOccluder(mesh) {
this.occluders.push(mesh);
}
isOccluded(point, camera) {
const direction = point.clone().sub(camera.position).normalize();
this.raycaster.set(camera.position, direction);
const intersects = this.raycaster.intersectObjects(this.occluders);
if (intersects.length > 0) {
const distance = camera.position.distanceTo(point);
return intersects[0].distance < distance;
}
return false;
}
cullObjects(objects, camera) {
const results = {
visible: [],
occluded: []
};
objects.forEach(obj => {
const center = new THREE.Vector3();
obj.getWorldPosition(center);
if (this.isOccluded(center, camera)) {
obj.visible = false;
results.occluded.push(obj);
} else {
obj.visible = true;
results.visible.push(obj);
}
});
return results;
}
}
// 使用
const occlusionCulling = new OcclusionCulling();
// 添加遮挡体(大型对象)
occlusionCulling.addOccluder(wall);
occlusionCulling.addOccluder(building);
// 在渲染循环中使用
function animate() {
requestAnimationFrame(animate);
const results = occlusionCulling.cullObjects(smallObjects, camera);
renderer.render(scene, camera);
}几何体优化
简化几何体
使用 SimplifyModifier 减少顶点数量。
javascript
import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';
const modifier = new SimplifyModifier();
// 简化 50% 的顶点
const simplifiedGeometry = modifier.modify(
geometry,
Math.floor(geometry.attributes.position.count * 0.5)
);
console.log('原始顶点:', geometry.attributes.position.count);
console.log('简化后顶点:', simplifiedGeometry.attributes.position.count);简化配置参数
| 参数 | 类型 | 说明 |
|---|---|---|
geometry | BufferGeometry | 要简化的几何体 |
count | Number | 要移除的顶点数量 |
顶点优化
javascript
// 去除重复顶点
function removeDuplicateVertices(geometry) {
const positions = geometry.attributes.position.array;
const uniquePositions = [];
const indices = [];
const positionMap = new Map();
for (let i = 0; i < positions.length; i += 3) {
const key = `${positions[i].toFixed(6)},${positions[i + 1].toFixed(6)},${positions[i + 2].toFixed(6)}`;
if (positionMap.has(key)) {
indices.push(positionMap.get(key));
} else {
const index = uniquePositions.length / 3;
positionMap.set(key, index);
uniquePositions.push(positions[i], positions[i + 1], positions[i + 2]);
indices.push(index);
}
}
const newGeometry = new THREE.BufferGeometry();
newGeometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(uniquePositions, 3)
);
newGeometry.setIndex(indices);
return newGeometry;
}
// 使用
const optimizedGeometry = removeDuplicateVertices(geometry);
console.log('优化前顶点:', geometry.attributes.position.count);
console.log('优化后顶点:', optimizedGeometry.attributes.position.count);几何体优化配置
javascript
class GeometryOptimizer {
constructor() {
this.options = {
removeDuplicates: true,
precision: 6,
computeNormals: true,
computeBoundingBox: true,
computeBoundingSphere: true
};
}
optimize(geometry) {
let optimized = geometry.clone();
// 去除重复顶点
if (this.options.removeDuplicates) {
optimized = this.removeDuplicates(optimized);
}
// 计算法线
if (this.options.computeNormals && !optimized.attributes.normal) {
optimized.computeVertexNormals();
}
// 计算边界
if (this.options.computeBoundingBox) {
optimized.computeBoundingBox();
}
if (this.options.computeBoundingSphere) {
optimized.computeBoundingSphere();
}
return optimized;
}
removeDuplicates(geometry) {
// 实现同上
return removeDuplicateVertices(geometry);
}
getStats(geometry) {
return {
vertices: geometry.attributes.position.count,
triangles: geometry.index
? geometry.index.count / 3
: geometry.attributes.position.count / 3,
attributes: Object.keys(geometry.attributes),
boundingBox: geometry.boundingBox ? {
min: geometry.boundingBox.min.toArray(),
max: geometry.boundingBox.max.toArray()
} : null
};
}
}
// 使用
const optimizer = new GeometryOptimizer();
const optimizedGeometry = optimizer.optimize(geometry);
console.log(optimizer.getStats(optimizedGeometry));材质优化
材质性能对比
| 材质类型 | 性能 | 光照 | 适用场景 |
|---|---|---|---|
| MeshBasicMaterial | 最高 | 无 | UI、指示器 |
| MeshLambertMaterial | 高 | 简单 | 低精度对象 |
| MeshStandardMaterial | 中 | PBR | 一般对象 |
| MeshPhysicalMaterial | 低 | 完整PBR | 高精度对象 |
javascript
// 高性能:MeshBasicMaterial(无光照)
const basicMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 中等性能:MeshLambertMaterial(简单光照)
const lambertMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
// 低性能:MeshStandardMaterial(PBR 光照)
const standardMaterial = new THREE.MeshStandardMaterial({
color: 0x00ff00,
metalness: 0.5,
roughness: 0.5
});
// 最低性能:MeshPhysicalMaterial(物理材质)
const physicalMaterial = new THREE.MeshPhysicalMaterial({
color: 0x00ff00,
metalness: 0.5,
roughness: 0.5,
clearcoat: 1
});减少材质属性
javascript
// 不推荐:启用所有特性
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 0.5,
roughness: 0.5,
normalMap: normalTexture,
roughnessMap: roughnessTexture,
metalnessMap: metalnessTexture,
aoMap: aoTexture,
emissiveMap: emissiveTexture
});
// 推荐:只使用必要的属性
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 0.5,
roughness: 0.5,
normalMap: normalTexture
});材质共享管理
javascript
class MaterialManager {
constructor() {
this.materials = new Map();
}
get(key, factory) {
if (!this.materials.has(key)) {
this.materials.set(key, factory());
}
return this.materials.get(key);
}
createBasic(color) {
return this.get(`basic-${color}`, () =>
new THREE.MeshBasicMaterial({ color })
);
}
createStandard(color, options = {}) {
const key = `standard-${color}-${JSON.stringify(options)}`;
return this.get(key, () =>
new THREE.MeshStandardMaterial({ color, ...options })
);
}
dispose() {
this.materials.forEach(material => material.dispose());
this.materials.clear();
}
getStats() {
return {
count: this.materials.size,
types: Array.from(this.materials.values()).reduce((acc, mat) => {
const type = mat.type;
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {})
};
}
}
// 使用
const materialManager = new MaterialManager();
const mat1 = materialManager.createStandard(0xff0000);
const mat2 = materialManager.createStandard(0x00ff00, { metalness: 0.5 });
const mat3 = materialManager.createBasic(0x0000ff);
console.log(materialManager.getStats());阴影优化
阴影是性能开销较大的功能,需要合理配置。
阴影配置参数
| 参数 | 说明 | 默认值 | 推荐值 |
|---|---|---|---|
mapSize.width | 阴影贴图宽度 | 512 | 1024-2048 |
mapSize.height | 阴影贴图高度 | 512 | 1024-2048 |
camera.near | 近裁剪面 | 0.5 | 根据场景 |
camera.far | 远裁剪面 | 500 | 根据场景 |
radius | 阴影模糊半径 | 2 | 2-4 |
javascript
// 降低阴影贴图分辨率
light.shadow.mapSize.width = 512;
light.shadow.mapSize.height = 512;
// 限制阴影相机范围
light.shadow.camera.near = 1;
light.shadow.camera.far = 20;
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
light.shadow.camera.top = 10;
light.shadow.camera.bottom = -10;
// 只对必要的对象启用阴影
mesh.castShadow = true; // 投射阴影
mesh.receiveShadow = true; // 接收阴影
// 减少阴影光源数量
// 不推荐:每个光源都投射阴影
scene.lights.forEach(light => {
light.castShadow = true;
});
// 推荐:只有主光源投射阴影
mainLight.castShadow = true;动态阴影优化
javascript
class ShadowOptimizer {
constructor(renderer, scene) {
this.renderer = renderer;
this.scene = scene;
this.enabled = true;
this.quality = 'medium'; // 'low', 'medium', 'high'
}
setQuality(quality) {
this.quality = quality;
const settings = {
low: { mapSize: 512, radius: 1 },
medium: { mapSize: 1024, radius: 2 },
high: { mapSize: 2048, radius: 4 }
}[quality];
this.scene.traverse(obj => {
if (obj.isLight && obj.shadow) {
obj.shadow.mapSize.width = settings.mapSize;
obj.shadow.mapSize.height = settings.mapSize;
obj.shadow.radius = settings.radius;
obj.shadow.map?.dispose();
obj.shadow.map = null;
}
});
}
disable() {
this.enabled = false;
this.renderer.shadowMap.enabled = false;
}
enable() {
this.enabled = true;
this.renderer.shadowMap.enabled = true;
}
getStats() {
let shadowLights = 0;
let castShadowObjects = 0;
this.scene.traverse(obj => {
if (obj.isLight && obj.castShadow) shadowLights++;
if (obj.isMesh && obj.castShadow) castShadowObjects++;
});
return { shadowLights, castShadowObjects };
}
}
// 使用
const shadowOptimizer = new ShadowOptimizer(renderer, scene);
shadowOptimizer.setQuality('medium');
// 根据性能动态调整
if (fps < 30) {
shadowOptimizer.setQuality('low');
}纹理优化
纹理尺寸选择
javascript
// 使用合适的纹理尺寸
// 推荐:2 的幂次方
const sizes = [128, 256, 512, 1024, 2048, 4096];
// 根据距离选择尺寸
function loadTextureWithLOD(distance) {
if (distance < 5) return textureLoader.load('high_2048.jpg');
if (distance < 10) return textureLoader.load('medium_1024.jpg');
return textureLoader.load('low_512.jpg');
}纹理尺寸配置表
| 距离范围 | 推荐尺寸 | 内存占用 | 适用场景 |
|---|---|---|---|
| < 5m | 2048x2048 | ~16MB | 近距离特写 |
| 5-10m | 1024x1024 | ~4MB | 中等距离 |
| 10-20m | 512x512 | ~1MB | 较远距离 |
| > 20m | 256x256 | ~256KB | 远距离 |
纹理压缩
javascript
// 使用压缩纹理格式
const texture = textureLoader.load('texture.jpg');
// 设置压缩格式
texture.format = THREE.RGBA_S3TC_DXT5_Format;
// 设置各向异性过滤
texture.anisotropy = 4; // 不要使用最大值
// 设置纹理过滤
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
// 生成 Mipmap
texture.generateMipmaps = true;纹理管理器
javascript
class TextureOptimizer {
constructor(renderer) {
this.renderer = renderer;
this.maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
}
optimize(texture, options = {}) {
// 设置各向异性过滤
texture.anisotropy = options.anisotropy || Math.min(4, this.maxAnisotropy);
// 设置编码
texture.encoding = options.encoding || THREE.sRGBEncoding;
// 设置过滤方式
texture.minFilter = options.minFilter || THREE.LinearMipmapLinearFilter;
texture.magFilter = options.magFilter || THREE.LinearFilter;
// 设置翻转
texture.flipY = options.flipY !== undefined ? options.flipY : true;
// 生成 Mipmap
if (options.generateMipmaps !== false) {
texture.generateMipmaps = true;
}
texture.needsUpdate = true;
return texture;
}
loadOptimized(url, options = {}) {
return new Promise((resolve, reject) => {
const loader = new THREE.TextureLoader();
loader.load(
url,
texture => resolve(this.optimize(texture, options)),
undefined,
reject
);
});
}
}
// 使用
const textureOptimizer = new TextureOptimizer(renderer);
const texture = await textureOptimizer.loadOptimized('texture.jpg', {
anisotropy: 4,
encoding: THREE.sRGBEncoding
});渲染设置优化
基础渲染设置
javascript
// 关闭不必要的功能
renderer.shadowMap.enabled = true; // 只在需要阴影时启用
renderer.antialias = false; // 移动端可关闭抗锯齿
// 降低像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 降低渲染分辨率
const scale = 0.5;
renderer.setSize(
window.innerWidth * scale,
window.innerHeight * scale
);
renderer.domElement.style.width = window.innerWidth + 'px';
renderer.domElement.style.height = window.innerHeight + 'px';渲染配置参数表
| 参数 | 说明 | 推荐值 |
|---|---|---|
pixelRatio | 设备像素比 | Math.min(dpr, 2) |
antialias | 抗锯齿 | PC: true, 移动: false |
shadowMap.enabled | 阴影开关 | 按需开启 |
shadowMap.type | 阴影类型 | PCFSoftShadowMap |
outputEncoding | 输出编码 | sRGBEncoding |
toneMapping | 色调映射 | ACESFilmicToneMapping |
toneMappingExposure | 曝光度 | 1.0 |
自适应渲染配置
javascript
class RenderSettingsManager {
constructor(renderer) {
this.renderer = renderer;
this.quality = 'medium';
this.targetFPS = 60;
this.fpsHistory = [];
}
setQuality(quality) {
this.quality = quality;
const settings = {
low: {
pixelRatio: 1,
antialias: false,
shadows: false,
toneMapping: THREE.NoToneMapping
},
medium: {
pixelRatio: Math.min(window.devicePixelRatio, 1.5),
antialias: true,
shadows: true,
toneMapping: THREE.ACESFilmicToneMapping
},
high: {
pixelRatio: Math.min(window.devicePixelRatio, 2),
antialias: true,
shadows: true,
toneMapping: THREE.ACESFilmicToneMapping
}
}[quality];
this.renderer.setPixelRatio(settings.pixelRatio);
this.renderer.shadowMap.enabled = settings.shadows;
this.renderer.toneMapping = settings.toneMapping;
}
update(fps) {
this.fpsHistory.push(fps);
if (this.fpsHistory.length > 60) {
this.fpsHistory.shift();
}
const avgFPS = this.fpsHistory.reduce((a, b) => a + b) / this.fpsHistory.length;
// 自动调整质量
if (avgFPS < this.targetFPS * 0.8 && this.quality !== 'low') {
this.downgradeQuality();
} else if (avgFPS > this.targetFPS * 0.95 && this.quality !== 'high') {
this.upgradeQuality();
}
}
downgradeQuality() {
const levels = ['high', 'medium', 'low'];
const currentIndex = levels.indexOf(this.quality);
if (currentIndex < levels.length - 1) {
this.setQuality(levels[currentIndex + 1]);
}
}
upgradeQuality() {
const levels = ['high', 'medium', 'low'];
const currentIndex = levels.indexOf(this.quality);
if (currentIndex > 0) {
this.setQuality(levels[currentIndex - 1]);
}
}
}性能测试对比
基准测试工具
javascript
function benchmark(fn, iterations = 100) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
const duration = end - start;
console.log(`总时间: ${duration.toFixed(2)}ms`);
console.log(`平均时间: ${(duration / iterations).toFixed(2)}ms`);
console.log(`每秒执行次数: ${(iterations / duration * 1000).toFixed(0)}`);
}
// 测试合并前后的性能
benchmark(() => {
renderer.render(sceneWithIndividualMeshes, camera);
}, 10);
benchmark(() => {
renderer.render(sceneWithMergedMeshes, camera);
}, 10);性能对比报告
javascript
class PerformanceComparator {
constructor() {
this.results = [];
}
test(name, scene, camera, renderer, iterations = 100) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
renderer.render(scene, camera);
}
const end = performance.now();
const duration = end - start;
const result = {
name,
totalTime: duration.toFixed(2) + 'ms',
avgTime: (duration / iterations).toFixed(2) + 'ms',
fps: (iterations / duration * 1000).toFixed(0),
drawCalls: renderer.info.render.calls,
triangles: renderer.info.render.triangles
};
this.results.push(result);
return result;
}
compare() {
console.table(this.results);
const baseline = this.results[0];
this.results.forEach((result, index) => {
if (index > 0) {
const improvement = ((baseline.avgTime - parseFloat(result.avgTime)) / parseFloat(baseline.avgTime) * 100).toFixed(1);
console.log(`${result.name} 相比 ${baseline.name}: ${improvement}% 性能提升`);
}
});
}
}
// 使用
const comparator = new PerformanceComparator();
comparator.test('原始场景', scene1, camera, renderer);
comparator.test('优化场景', scene2, camera, renderer);
comparator.compare();API 参考
InstancedMesh API
| 方法/属性 | 参数 | 说明 |
|---|---|---|
constructor(geometry, material, count) | - | 创建实例网格 |
setMatrixAt(index, matrix) | index: Number, matrix: Matrix4 | 设置实例变换矩阵 |
getMatrixAt(index, matrix) | index: Number, matrix: Matrix4 | 获取实例变换矩阵 |
setColorAt(index, color) | index: Number, color: Color | 设置实例颜色 |
getColorAt(index, color) | index: Number, color: Color | 获取实例颜色 |
instanceMatrix | InstancedBufferAttribute | 实例矩阵属性 |
instanceColor | InstancedBufferAttribute | 实例颜色属性 |
count | Number | 实例数量 |
LOD API
| 方法/属性 | 参数 | 说明 |
|---|---|---|
addLevel(object, distance) | object: Object3D, distance: Number | 添加细节层次 |
getCurrentLevel() | - | 获取当前层级 |
getObjectForDistance(distance) | distance: Number | 获取指定距离的对象 |
update(camera) | camera: Camera | 更新 LOD 状态 |
levels | Array | 所有层级数组 |
autoUpdate | Boolean | 是否自动更新 |
Frustum API
| 方法 | 参数 | 说明 |
|---|---|---|
setFromProjectionMatrix(m) | m: Matrix4 | 从投影矩阵设置视锥 |
intersectsObject(object) | object: Object3D | 判断对象是否在视锥内 |
intersectsSphere(sphere) | sphere: Sphere | 判断球是否在视锥内 |
intersectsBox(box) | box: Box3 | 判断盒子是否在视锥内 |
containsPoint(point) | point: Vector3 | 判断点是否在视锥内 |
常见问题
Q1: 何时使用 InstancedMesh vs 几何体合并?
选择指南:
| 特性 | InstancedMesh | 几何体合并 |
|---|---|---|
| 对象是否移动 | 支持动态更新 | 仅静态 |
| 几何体是否相同 | 必须相同 | 可以不同 |
| 材质是否相同 | 必须相同 | 可以不同(使用组) |
| 需要拾取 | 支持单个实例 | 无法单独拾取 |
| 性能提升 | 99%+ | 90%+ |
javascript
// 场景 1:大量移动的相同几何体 -> InstancedMesh
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
// 场景 2:静态建筑群 -> 几何体合并
const mergedGeometry = mergeBufferGeometries(buildingGeometries);
// 场景 3:需要单独交互 -> InstancedMesh
instancedMesh.addEventListener('click', (event) => {
const instanceId = event.instanceId;
// 处理单个实例
});Q2: LOD 距离如何设置?
推荐设置:
javascript
// 基于对象尺寸的 LOD 设置
function calculateLODDistances(radius) {
return {
high: 0, // 高精度:近距离
medium: radius * 5, // 中精度:5 倍半径
low: radius * 15, // 低精度:15 倍半径
lowest: radius * 30 // 最低精度:30 倍半径
};
}
// 为不同尺寸的对象设置 LOD
function setupLOD(mesh, radius) {
const lod = new THREE.LOD();
const distances = calculateLODDistances(radius);
lod.addLevel(createHighDetail(radius), distances.high);
lod.addLevel(createMediumDetail(radius), distances.medium);
lod.addLevel(createLowDetail(radius), distances.low);
return lod;
}Q3: 如何优化大场景性能?
分层优化策略:
javascript
class LargeSceneOptimizer {
constructor() {
this.quadtree = new Quadtree();
this.lodSystem = new DynamicLODSystem();
this.cullingManager = new FrustumCullingManager();
}
optimize(scene, camera) {
// 1. 空间分区
const visibleRegions = this.quadtree.query(camera.frustum);
// 2. 视锥剔除
this.cullingManager.update(camera);
visibleRegions.forEach(region => {
this.cullingManager.cullObjects(region.objects);
});
// 3. LOD 更新
this.lodSystem.update(camera);
// 4. 动态加载/卸载
this.updateDynamicLoading(visibleRegions);
}
updateDynamicLoading(visibleRegions) {
// 卸载不可见区域
this.loadedRegions.forEach(region => {
if (!visibleRegions.includes(region)) {
region.unload();
}
});
// 加载可见区域
visibleRegions.forEach(region => {
if (!region.isLoaded) {
region.load();
}
});
}
}Q4: 移动端渲染优化建议?
移动端配置:
javascript
// 移动端优化配置
const mobileConfig = {
// 渲染器设置
pixelRatio: Math.min(window.devicePixelRatio, 1.5),
antialias: false,
shadows: false,
// 几何体限制
maxTriangles: 500000,
maxDrawCalls: 500,
// 纹理限制
maxTextureSize: 1024,
textureFormat: THREE.RGBA_S3TC_DXT5_Format,
// 材质优化
preferBasicMaterial: true,
disablePBR: true
};
// 应用配置
function applyMobileOptimizations(renderer, scene) {
renderer.setPixelRatio(mobileConfig.pixelRatio);
renderer.shadowMap.enabled = mobileConfig.shadows;
// 遍历场景并优化
scene.traverse(obj => {
if (obj.isMesh) {
// 简化材质
if (mobileConfig.preferBasicMaterial && obj.material.isMeshStandardMaterial) {
obj.material = new THREE.MeshBasicMaterial({
map: obj.material.map,
color: obj.material.color
});
}
}
if (obj.isTexture) {
// 限制纹理尺寸
if (obj.image.width > mobileConfig.maxTextureSize) {
resizeTexture(obj, mobileConfig.maxTextureSize);
}
}
});
}Q5: 如何诊断 Draw Call 过高?
诊断步骤:
javascript
function diagnoseDrawCalls(renderer, scene) {
const info = renderer.info.render;
const drawCalls = info.calls;
console.log(`总 Draw Calls: ${drawCalls}`);
if (drawCalls > 1000) {
console.warn('Draw Calls 过高,需要优化');
// 分析材质使用
const materialUsage = new Map();
scene.traverse(obj => {
if (obj.isMesh && obj.material) {
const key = obj.material.uuid;
materialUsage.set(key, (materialUsage.get(key) || 0) + 1);
}
});
// 找出重复材质
console.log('材质使用统计:');
materialUsage.forEach((count, key) => {
if (count > 1) {
console.log(`材质 ${key} 被 ${count} 个对象使用`);
}
});
// 建议
console.log('优化建议:');
if (materialUsage.size > 50) {
console.log('- 考虑合并相同材质的对象');
}
// 检查几何体
const geometryCount = new Map();
scene.traverse(obj => {
if (obj.isMesh && obj.geometry) {
const key = obj.geometry.uuid;
geometryCount.set(key, (geometryCount.get(key) || 0) + 1);
}
});
geometryCount.forEach((count, key) => {
if (count > 10) {
console.log(`- 几何体 ${key} 被使用 ${count} 次,考虑使用 InstancedMesh`);
}
});
}
}