第 9 节:性能优化
🎯 学习目标
- 理解 WebGL 性能瓶颈
- 掌握常见的优化技巧
- 学会使用性能分析工具
- 对比原生 WebGL 和 Three.js 的优化策略
📖 理论:性能优化要点
主要性能瓶颈
- Draw Call:每次绘制调用的开销
- Shader 复杂度:片段着色器的计算量
- 纹理大小:纹理内存和带宽
- 顶点数量:几何体复杂度
- 状态切换: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);
};
📝 编程作业
基础作业(⭐)
任务:性能对比测试
要求:
- 创建 100 个立方体
- 使用两种方式渲染:
- 方式1:100 次 Draw Call
- 方式2:合并几何体,1 次 Draw Call
- 对比两种方式的性能
检查清单:
- 两种方式都能正确渲染
- 测量并对比 FPS
- 理解 Draw Call 的影响
进阶作业(⭐⭐)
任务:实现性能优化系统
要求:
- 实现几何体合并
- 实现按材质分组渲染
- 实现 LOD 系统
- 添加性能监控面板
检查清单:
- 性能有明显提升
- 可以实时查看性能数据
- 理解各种优化技巧
挑战作业(⭐⭐⭐)
任务:实现完整的性能优化框架
要求:
- 自动检测性能瓶颈
- 自动应用优化策略
- 提供性能报告和建议
- 支持动态调整优化级别
扩展功能:
- 实现自适应 LOD
- 实现自动纹理压缩
- 实现渲染批处理系统
🎓 本节总结
关键优化技巧
- 减少 Draw Call:合并绘制、使用实例化
- 优化 Shader:减少计算、使用查找表
- 压缩纹理:使用压缩格式、合理使用 Mipmap
- 优化几何体:使用 LOD、简化模型
- 减少状态切换:批量绘制相同状态的对象
性能分析
- 使用工具:Chrome DevTools、WebGL Inspector
- 监控指标:FPS、Draw Call、内存使用
- 定位瓶颈:找出性能瓶颈并优化
最佳实践
- 先测量后优化:不要过早优化
- 关注主要瓶颈:优化影响最大的部分
- 平衡质量和性能:在质量和性能之间找到平衡
- 持续监控:定期检查性能指标
下一步
完成作业后,进入下一节:第 11 节:GPGPU基础与应用