本节摘要:这一节把前面各章埋下的性能伏笔兑换成招法清单——分支、精度、采样、discard 各有确定收益的改法;再交给孩子气的秘密武器"把中间量涂在屏幕上",配上帧捕获工具,让着色器调试从撞大运变成走流程。
读完本节,你应当能够:按收益确定性排出优化动作的优先级;用颜色编码法可视化任意中间变量;描述用帧捕获工具定位一次着色器错误的完整流程。
着色器优化招法满天飞,但"收益确定、代价可控"的就这几条,按优先级排列。
**第一条:砍掉发散分支。**第 2 章讲过原理,这里给行动版:全册代码搜一遍逐线程 if,能用 step 加 mix 改写的全部改写。一条典型换算——某个片元分支在组内五五开时,等于两条路径都全额执行;无分支化后只付一条路径的钱。
// 改前:两条路径都烧 if (v > 0.5) { c = hotPath(c); } else { c = coldPath(c); } // 改后:只按权重付钱 float k = step(0.5, v); c = mix(coldPath(c), hotPath(c), k);
注意改写有前提:两条路径都必须无副作用(纯计算)。涉及 discard 或写深度的分支不可这样合并。
**第二条:精度降档。**移动端片元着色器把颜色类计算降到 mediump,寄存器压力与功耗同步下降,收益稳定。位置与矩阵保持 highp——精度省错地方是负优化(UV 抖动的排查又得耗一晚)。
**第三条:采样做减法。**每次 texture 调用都可能是缓存缺失,是片元阶段最常见的隐性大头。合并思路:把多张小图打进一张图集用偏移 UV 采样;一张 RGBA 图替代四张单通道图;能复用采样结果就不要在循环里重复采样同一点。
**第四条:数学等价改写。**乘法加法优先、少用三角与除法;向量运算写整体(a * b + c 让编译器合并成乘加指令);saturate、clamp 免费使用;公共子表达式提出来算一次。单条收益小,胜在零风险,适合收尾阶段顺手做。
**第五条:守护 early-z。**第 4 章的 discard、主动写深度,以及任何可能引起驱动保守化的写法,只在确有必要时使用。半透明走混合,镂空才 discard——这条纪律在移动端值好几帧。
**第六条:让几何低头。**顶点受限时,LOD(远处换低模)、屏幕覆盖率剔除、实例化合并绘制,都是引擎层开关。着色器工程师要做的只是别在顶点着色器里塞重活——顶点着色器每帧执行次数可能比片元还多,一行矩阵乘法的节省也会被放大百万倍。
💡 关键直觉:优化顺序从上往下打,每打一条测一次帧时间。前两条通常是主力,后四条是修边——反过来先做数学等价改写,往往是把精力花在噪声里。
着色器没有断点没有打印,但它有一个 CPU 世界没有的豪华待遇:输出本身就是一幅图。任何中间变量都能映射成颜色直接输出,错误瞬间现形:
// 调试工具:数值映射成蓝到红的色带 vec3 debugHeat(float v) { v = clamp(v, 0.0, 1.0); return mix(vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), v); } void main() { float ndl = max(dot(N, L), 0.0); // FragColor = vec4(finalColor, 1.0); // 正式输出,暂时注释 FragColor = vec4(debugHeat(ndl), 1.0); // 调试输出:光照权重肉眼可见 }
怀疑法线有问题就输出 N * 0.5 + 0.5(向量转色带的标配映射,法线贴图本身就是这么编码的);怀疑 UV 错乱就输出 vec3(vUv, 0.0);怀疑深度异常就输出 debugHeat(gl_FragCoord.z)。屏幕变成仪表盘,哪个环节黑了、花了、越界了,一眼定位到"输入侧"还是"计算侧"。
黑屏排查的完整纪律值得背下来:逐级短路。先把片元输出钉死成纯色——屏幕黑说明问题在管线配置(没绑定、没绘制、深度全挡)而非着色逻辑;纯色可见后,再换成第一个中间量;再不对,把该中间量的输入拆开逐个可视化。每步只改一处,两三步内必能圈定肇事行。这个方法与第 3 章"逐空间排查矩阵"是同一套哲学:把黑盒切成半黑盒。
⚠️ 常见坑:NaN 会污染一切——一个除零产生的 NaN 参与插值后会让整片像素消失。视觉症状是"诡异的一块黑且怎么改公式都黑"。快速验证:输出前用 isnan 检测并染成品红色,NaN 无处遁形。
手工纪律之上,工具能把你从"猜"升级到"看"。
帧捕获调试器(RenderDoc 一类):抓一帧完整现场,能看到每次 draw call 的管线状态、每张纹理的内容、每个 pass 的耗时,还能对着任意像素重放它的着色器执行、检查每个输入值。第 9.1 节的三桶定位,用它做是最快的——顶点数、绘制数、填充率直接读表。
引擎内置帧调试器:各大引擎都有帧分析窗口,与帧捕获工具思路相同但集成度更高,还能直接跳转到触发绘制的代码位置。查"这次绘制为什么提交了两次"这类引擎层问题,比裸工具顺手。
离线编译校验:把着色器编译纳入构建(第 8 章的中间层工具链),语法错误在提交前就被拦截。着色器错误最贵的形态是"只在某台机器上运行时才炸",离线校验专治这种。
平台分析器:需要压榨极限时,厂商级分析工具能给出指令级的吞吐、占用与缓存命中率。入门阶段用不上,知道有这层即可。
一条使用心得收尾:工具回答"哪里慢、哪里错",但不回答"该怎么改"。把工具结论翻译成改进方案,靠的仍然是本教程从头到尾在建立的东西——对管线执行模型的直觉。
正篇到此收官。回到导读的知识地图:从管线筑基到工程收尾,这条主线你已经走完——接下来该把它压进自己的项目里了。