Unity Shader 中的 if 分支:真相与最佳实践

Unity Shader 中的 if 分支:真相与最佳实践

1. if 分支的本质

  • GPU 在执行像素着色时,会把多个像素线程打包成一个批次并行执行(PC 端常叫 warp,移动端常叫 wavefront 或者“执行束”)。

  • 一个批次内的所有线程必须执行相同的指令
    如果 if 条件在同一个批次里不一致,就会产生 分支发散(divergence),导致 GPU 分别执行两个分支并合并结果 → 性能下降。


2. 什么时候 if 很便宜

  • 条件是 uniform(运行时常量)
    来自材质属性、C# SetFloatMaterialPropertyBlock 等,每个物体绘制时该值一致。

  • 批次内像素的条件一致
    即使不是 uniform,只要这个批次里的像素条件值相同,就不会发散。

  • 分支内是重逻辑
    如果条件大部分为 false,可以避免大量无用计算,if 反而更省性能。

  • 移动端也一样
    Mali、Adreno、PowerVR 等移动 GPU 也是 SIMD/SIMT 架构,原理一致。


3. 什么时候 if 会变慢

  • 条件来自 per-pixel 数据(如贴图、屏幕空间计算结果),warp 内的像素条件可能不同。

  • 分支两边都很轻,发散造成的执行两遍比直接算两边再 lerp 还慢。

  • 分支嵌套复杂,引发额外的寄存器压力和指令调度开销。


4. 常见的三种条件来源

条件来源 warp 内一致性 性能表现 典型场景
编译期常量#if 宏) 永远一致 最快 平台差异、功能开关
uniform 常量(材质属性、C# 传值) 每物体一致 高效 材质开关、效果启用/禁用
per-pixel 动态值(贴图、varying) 可能不一致 易发散 mask 控制、屏幕空间条件

5. 优化策略

  1. 条件尽量 uniform 化
    能在 CPU 端提前判断的逻辑,不要放到每像素去判断。

  2. 效果分材质
    需要开启的效果直接用不同材质,而不是一个材质里用贴图 mask 去分支。

  3. 重逻辑用 if,轻逻辑用 lerp

    • 重逻辑:避免不必要计算,用分支跳过。

    • 轻逻辑:直接两边算再 lerp,免得发散。

  4. 分支层级浅化
    避免深层 if 嵌套带来的寄存器和调度开销。


6. 误区与澄清

  • 误区 1:「GPU if 分支很耗性能,要避免」
    ✔ 真相:耗性能的是 分支发散,不是 if 本身。条件一致时 if 很便宜。

  • 误区 2:「材质面板的属性不是常量」
    ✔ 真相:对 GPU 一个 draw call 来说,它就是 uniform 常量。

  • 误区 3:「PC 和移动端差别很大」
    ✔ 真相:SIMD/SIMT 分支执行原理一致,区别在硬件规模和调度策略。


7. 简短结论

如果条件在一个 warp 内一致,if 分支几乎没有额外开销,反而能节省重逻辑计算;
如果条件在 warp 内不一致,就会引发分支发散,性能下降,轻逻辑建议用 lerp 替代。

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容