{T}

渲染优化

渲染优化是提升 Three.js 应用性能的核心环节,通过减少 Draw Calls、优化几何体、使用 LOD 等技术显著提升帧率,为用户提供流畅的交互体验。

概述

渲染优化主要关注减少 GPU 负载,包括减少 Draw Calls、优化几何体、合理使用材质等。优化的核心目标是:

  1. 降低 GPU 负载:减少每帧需要处理的数据量
  2. 减少状态切换:最小化材质和 Shader 的切换
  3. 智能剔除:避免渲染不可见的对象
  4. 合理降级:根据距离调整细节层次

优化效果预期

优化技术性能提升实施难度适用场景
几何体合并50-90%静态场景
InstancedMesh70-95%大量相同几何体
LOD30-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

几何体合并配置参数

参数类型默认值说明
geometriesArray-要合并的几何体数组
useGroupsBooleanfalse是否使用材质组
更新顶点Booleantrue是否更新顶点数据
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 配置参数

参数类型说明
geometryBufferGeometry实例使用的几何体
materialMaterial实例使用的材质
countNumber实例数量
instanceMatrixInstancedBufferAttribute实例变换矩阵
instanceColorInstancedBufferAttribute实例颜色属性

InstancedMesh 性能对比

对象数量普通网格InstancedMesh性能提升
100100 Draw Calls1 Draw Call99%
1,0001,000 Draw Calls1 Draw Call99.9%
10,00010,000 Draw Calls1 Draw Call99.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);

简化配置参数

参数类型说明
geometryBufferGeometry要简化的几何体
countNumber要移除的顶点数量

顶点优化

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简单低精度对象
MeshStandardMaterialPBR一般对象
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阴影贴图宽度5121024-2048
mapSize.height阴影贴图高度5121024-2048
camera.near近裁剪面0.5根据场景
camera.far远裁剪面500根据场景
radius阴影模糊半径22-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');
}

纹理尺寸配置表

距离范围推荐尺寸内存占用适用场景
< 5m2048x2048~16MB近距离特写
5-10m1024x1024~4MB中等距离
10-20m512x512~1MB较远距离
> 20m256x256~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获取实例颜色
instanceMatrixInstancedBufferAttribute实例矩阵属性
instanceColorInstancedBufferAttribute实例颜色属性
countNumber实例数量

LOD API

方法/属性参数说明
addLevel(object, distance)object: Object3D, distance: Number添加细节层次
getCurrentLevel()-获取当前层级
getObjectForDistance(distance)distance: Number获取指定距离的对象
update(camera)camera: Camera更新 LOD 状态
levelsArray所有层级数组
autoUpdateBoolean是否自动更新

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`);
      }
    });
  }
}

相关链接