Skip to main content

第 9 节:性能优化

🎯 学习目标

  • 理解 WebGL 性能瓶颈
  • 掌握常见的优化技巧
  • 学会使用性能分析工具
  • 对比原生 WebGL 和 Three.js 的优化策略

📖 理论:性能优化要点

主要性能瓶颈

  1. Draw Call:每次绘制调用的开销
  2. Shader 复杂度:片段着色器的计算量
  3. 纹理大小:纹理内存和带宽
  4. 顶点数量:几何体复杂度
  5. 状态切换:WebGL 状态改变的开销

优化策略

  • 减少 Draw Call:合并绘制、使用实例化
  • 简化 Shader:减少计算、使用查找表
  • 压缩纹理:使用压缩格式、Mipmap
  • 优化几何体:使用 LOD、简化模型
  • 减少状态切换:批量绘制相同状态的对象

💻 原生 WebGL 优化技巧

1. 减少 Draw Call

// ❌ 不好:多次绘制调用
for (let i = 0; i < 100; i++) {
gl.drawArrays(gl.TRIANGLES, i * 3, 3);
}

// ✅ 好:一次绘制调用
gl.drawArrays(gl.TRIANGLES, 0, 300);

2. 使用索引缓冲区

// ❌ 不好:重复顶点
const vertices = [
// 三角形1
0, 0, 0, 1, 0, 0, 1, 1, 0,
// 三角形2(共享顶点)
0, 0, 0, 1, 1, 0, 0, 1, 0,
];

// ✅ 好:使用索引
const vertices = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0];
const indices = [0, 1, 2, 0, 2, 3];
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

3. 批量状态设置

// ❌ 不好:频繁切换状态
for (let i = 0; i < objects.length; i++) {
gl.useProgram(programs[i]);
gl.bindTexture(gl.TEXTURE_2D, textures[i]);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}

// ✅ 好:按状态分组
const groups = groupByState(objects);
for (const group of groups) {
gl.useProgram(group.program);
gl.bindTexture(gl.TEXTURE_2D, group.texture);
for (const obj of group.objects) {
gl.drawArrays(gl.TRIANGLES, obj.offset, obj.count);
}
}

4. 使用 Uniform Buffer

// ✅ 使用 Uniform Buffer 传递多个 Uniform
const uniformBuffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, uniformBuffer);
gl.bufferData(gl.UNIFORM_BUFFER, uniformData, gl.DYNAMIC_DRAW);
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, uniformBuffer);

5. 纹理优化

// ✅ 使用压缩纹理
gl.compressedTexImage2D(
gl.TEXTURE_2D,
0,
gl.COMPRESSED_RGBA_S3TC_DXT1_EXT,
width,
height,
0,
compressedData
);

// ✅ 生成 Mipmap
if (isPowerOf2(width) && isPowerOf2(height)) {
gl.generateMipmap(gl.TEXTURE_2D);
}

🎨 Three.js 优化技巧

1. 使用 InstancedMesh

// ✅ 使用实例化渲染大量相同物体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial();
const mesh = new THREE.InstancedMesh(geometry, material, 1000);

// 设置每个实例的变换
const matrix = new THREE.Matrix4();
for (let i = 0; i < 1000; i++) {
matrix.setPosition(Math.random() * 10, Math.random() * 10, Math.random() * 10);
mesh.setMatrixAt(i, matrix);
}

2. 使用 Geometry Merging

// ✅ 合并多个几何体减少 Draw Call
const geometries = [geo1, geo2, geo3];
const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);

3. 使用 LOD

// ✅ 使用细节层次
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);
lod.addLevel(mediumDetailMesh, 50);
lod.addLevel(lowDetailMesh, 100);

4. 纹理优化

// ✅ 使用压缩纹理
const texture = textureLoader.load('texture.jpg');
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.generateMipmaps = true;

// ✅ 使用纹理压缩
renderer.setTextureCompression(THREE.S3TCTextureLoader);

5. 渲染优化

// ✅ 使用 Frustum Culling
renderer.sortObjects = true;

// ✅ 使用 Occlusion Culling(需要扩展)
// 使用 THREE.OcclusionCulling 或自定义实现

🔍 性能分析工具

1. Chrome DevTools

// 使用 Performance 面板
// 1. 打开 DevTools
// 2. 切换到 Performance 标签
// 3. 点击录制
// 4. 运行你的代码
// 5. 停止录制并分析

2. WebGL Inspector

// 安装 WebGL Inspector 扩展
// 可以查看:
// - Draw Call 数量
// - 纹理使用情况
// - Shader 性能
// - 状态切换

3. 自定义性能监控

// 监控 FPS
let lastTime = performance.now();
let frameCount = 0;

function monitorPerformance() {
frameCount++;
const currentTime = performance.now();
if (currentTime >= lastTime + 1000) {
const fps = frameCount;
console.log(`FPS: ${fps}`);
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(monitorPerformance);
}

// 监控 Draw Call
let drawCallCount = 0;
const originalDrawArrays = WebGLRenderingContext.prototype.drawArrays;
WebGLRenderingContext.prototype.drawArrays = function (...args) {
drawCallCount++;
return originalDrawArrays.apply(this, args);
};

📝 编程作业

基础作业(⭐)

任务:性能对比测试

要求

  1. 创建 100 个立方体
  2. 使用两种方式渲染:
    • 方式1:100 次 Draw Call
    • 方式2:合并几何体,1 次 Draw Call
  3. 对比两种方式的性能

检查清单

  • 两种方式都能正确渲染
  • 测量并对比 FPS
  • 理解 Draw Call 的影响

进阶作业(⭐⭐)

任务:实现性能优化系统

要求

  1. 实现几何体合并
  2. 实现按材质分组渲染
  3. 实现 LOD 系统
  4. 添加性能监控面板

检查清单

  • 性能有明显提升
  • 可以实时查看性能数据
  • 理解各种优化技巧

挑战作业(⭐⭐⭐)

任务:实现完整的性能优化框架

要求

  1. 自动检测性能瓶颈
  2. 自动应用优化策略
  3. 提供性能报告和建议
  4. 支持动态调整优化级别

扩展功能

  • 实现自适应 LOD
  • 实现自动纹理压缩
  • 实现渲染批处理系统

🎓 本节总结

关键优化技巧

  1. 减少 Draw Call:合并绘制、使用实例化
  2. 优化 Shader:减少计算、使用查找表
  3. 压缩纹理:使用压缩格式、合理使用 Mipmap
  4. 优化几何体:使用 LOD、简化模型
  5. 减少状态切换:批量绘制相同状态的对象

性能分析

  1. 使用工具:Chrome DevTools、WebGL Inspector
  2. 监控指标:FPS、Draw Call、内存使用
  3. 定位瓶颈:找出性能瓶颈并优化

最佳实践

  1. 先测量后优化:不要过早优化
  2. 关注主要瓶颈:优化影响最大的部分
  3. 平衡质量和性能:在质量和性能之间找到平衡
  4. 持续监控:定期检查性能指标

下一步

完成作业后,进入下一节:第 11 节:GPGPU基础与应用


📚 参考资源