33 · 性能与兼容性验证
1. 程序变体比你写的材质数量更多
同一个内置材质在不同灯光数、阴影、几何特征和编译开关下可能使用不同程序。自定义结构选项再增加组合,会让首次显示时的编译成本和程序内存增长。
不要把时间、鼠标位置、颜色值加入 customProgramCacheKey。它们应是 uniform。真正决定 GLSL 结构的选项才进入键,例如算法模式、固定数组容量或是否声明一套额外 varying。
// 正确方向:两个离散结构模式;时间保留在 uniform。
material.customProgramCacheKey = () => `my-effect/v2/mode=${mode}`;
// mode 改变并导致源码改变时:material.needsUpdate = true。
键稳定不代表功能免费:一个固定源码里同时计算三套昂贵算法再 mix,可能比少量有控制的变体更慢。应该根据切换频率、编译成本和每帧工作量选择。
2. 片元覆盖面积决定许多效果的成本
一个 8 次循环的噪声若只在 1000 个顶点执行,和在每帧几百万片元执行,代价完全不同。透明重叠、水面全屏覆盖、高 DPR 会放大片元工作量。
使用插值可以把适合线性插值的计算移到顶点阶段,但非线性函数不一定保持结果。仿射 UV 变换适合提前执行,尖锐遮罩、法线归一化和高光响应则需要考虑插值误差。
对纹理混合,颜色、roughness、normal 三套双层采样可能远多于你最初想象的“两张图”。先统计实际采样次数和条件,再考虑合并数据通道、降低分辨率或按距离简化。normal 和颜色不能使用同样的色彩解码规则。
3. 动态循环与分支需要实测
WebGL2 的 GLSL ES 3.00 并非禁止所有 uniform 控制的循环,但驱动、上界、动态索引及 Three.js 循环展开器有不同约束。固定容量加有效数量适合可控上限的效果;若循环内部有很贵的采样,盲目去掉分支反而可能增加工作量。
分支对整个 draw call 一致与在相邻片元间变化不同。一个统一的模式 uniform 通常比噪声阈值导致的细粒度分歧更容易执行,但具体耗时仍由设备和编译结果决定。
4. 阴影会重复执行几何和裁切工作
主材质的顶点变形如果同步到深度和距离材质,意味着它还会在阴影通道执行。点光源可能需要多个方向的阴影渲染,因此不能只看主相机的一次 draw call。
动画对象的阴影若设成手动更新,需要在变化时请求更新;反之,静态灯光和静态对象可以降低更新频率。水面高细分、风吹植被和透明裁切阴影都要把阴影分辨率与通道次数计入预算。
Physical 的 transmission 还可能涉及额外场景捕获和采样。清漆、各向异性、虹彩等扩展会增加着色工作,不能只因为它们是内置属性就认为没有成本。
5. 运行时预热与观察指标
// scene、camera、renderer 已配置,灯光与材质特征已确定。
await renderer.compileAsync(scene, camera);
console.log(renderer.info.programs.length);
预热可以减少部分首次可见时的编译等待,但不会自动覆盖未来所有灯光配置、材质模式和阴影通道。最终仍需实际渲染要支持的组合。不要每帧调用编译预热。
观察 renderer.info.programs.length 是否在反复切换后无界增长;记录 CPU 帧时间、绘制数量、可用时的 GPU 时间以及视觉质量。程序数量不是 GPU 耗时,renderer.info.memory 也不是完整显存字节统计。VSync 限制下都显示 60 FPS,不代表两个方案成本相同。
测试时固定视口、DPR、相机、对象数、灯光和动画时间。预热后采样一段时间,避免把首次编译峰值混同于稳定运行成本。
6. 兼容性与版本边界
本系列针对 r185 WebGLRenderer 的 WebGL2 路径。ShaderChunk 属于内部实现,升级后应检查锚点是否仍存在、变量含义是否改变、宏条件是否调整,以及阴影编码与颜色输出路径是否变化。
GPU 支持的纹理单位、varying、uniform 容量都有限。添加几个大数组和多层贴图可能在桌面可行、在另一台设备上链接失败。应查询能力、限制配置上界,并用目标设备验证;查询到支持不代表性能足够。
数学误差也可能来自精度。世界坐标非常大时,用它直接计算高频 sin 或噪声会抖动;可改为相对原点坐标、缩小输入尺度或采用更稳定的局部分块方案。长时间递增的 float 时间也需要周期与生命周期管理。
7. 建议的验证矩阵
至少覆盖桌面与窄屏、无贴图与有贴图、阴影开关、不同材质模式、克隆对象、重复切换、窗口缩放及资源销毁。若声称支持实例化、骨骼或 morph,必须增加对应样例。
yarn build 能验证工程打包,但 Markdown 里的 GLSL 不会自动被构建器编译。需要在浏览器中真正创建材质、触发各通道并检查日志。数值测试则负责验证波面偏导、坐标变换等数学关系,这三种检查不能互相替代。
练习(不附答案)
- 制作两个程序变体并反复切换,记录程序数量与首次切换耗时,解释正常复用与无界增长的区别。
- 将画布 DPR 从 1 改为 2,比较全屏噪声与低面数顶点动画的性能变化。
- 分别使用方向光与点光阴影运行风吹几何,统计实际绘制工作并观察性能。
- 为一款需要移动端运行的纹理混合材质设计高低质量配置,说明每个降级项减少了什么计算。