分支语句在 Shader 中并非一律昂贵。理解 GPU 执行模型,才能准确判断何时可以放心使用 if,何时需要替代方案。

01  GPU 执行模型:先理解 Warp

GPU 不像 CPU 那样逐线程独立运行,而是将若干线程捆绑为一个 Warp(NVIDIA)/ Wavefront(AMD),由同一条指令流批量驱动。对 Fragment Shader 来说,这意味着一个 Warp 内的所有像素共用同一套指令序列。

正是这种锁步机制,让分支的代价与 "条件是否在 Warp 内一致" 直接挂钩。

02  Uniform 分支:几乎零开销

当 if 的判断条件对整个 Draw Call 的 所有 线程都相同时,称为 Uniform 分支。典型案例是从 Properties 传入的开关(Toggle)或全局宏。

// Properties 中声明的 Toggle
float _EnableFeature; // 0 或 1,整个 DC 固定
half4 Fragment(Varyings input) : SV_Target
{
    if (_EnableFeature > 0.5)          // ← Uniform 条件
    {
        // 整个 Warp 要么全走这里,要么全不走
        color += ApplyRimLight(input.normalWS);
    }
    return color;
}

GPU 在 着色器编译阶段或指令分派阶段即可判定条件值,直接跳过不执行的分支整个指令块,不存在任何线程的计算浪费。

结论

Uniform 条件的 if 等同于静态路径切换,GPU 驱动或编译器可将不执行的分支整体剔除,开销近乎为零

常见 Uniform 条件来源

// 方式 A:直接 float toggle(常用于简单开关)
float _EnableRim;      // 0.0 / 1.0
float _EnableOutline;  // 0.0 / 1.0
// 方式 B:Shader Keyword(编译期剔除更彻底)
#pragma shader_feature_local _RIM_ON
#pragma multi_compile _ OUTLINE_ON
// 使用方式
#if defined(_RIM_ON)
    color += RimLight(N, V);
#endif

建议

对于功能级别的开关,优先使用 shader_feature_local 关键字,编译器会为每条路径生成独立 Variant,运行时 字面上不存在 被剔除的代码,比 Uniform float 更干净。

03  动态分支:真实开销从何而来

当条件依赖每像素不同的变量(如 uv、法线、采样结果),Warp 内不同线程的条件结果可能不一致,这就是 动态分支,也是真正需要小心的场景。

half4 Fragment(Varyings input) : SV_Target
{
    half4 mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, input.uv);
    if (mask.r > 0.5)  // ← 动态条件:每像素不同!
    {
        color = ExpensivePathA(input); // 路径 A
    }
    else
    {
        color = ExpensivePathB(input); // 路径 B
    }
    return color;
}

Warp 分歧(Warp Divergence)

当 Warp 内的线程因条件不同而需要走不同路径时,GPU 无法让它们真正"分叉"。实际做法是: 先执行路径 A(路径 B 的线程被屏蔽),再执行路径 B(路径 A 的线程被屏蔽),最后合并结果。两条路径的指令都被执行了一遍,代价是真实存在的。

注意

动态分支的真实开销 = 两条路径之和,而非较慢的那条。越昂贵的分支内容(多次纹理采样、复杂数学),动态 if 的代价越高。

04  横向对比

维度 Uniform 分支 动态分支
条件来源 Properties、CBuffer、宏定义等
(所有线程相同)
逐像素变量:UV、贴图采样、normalWSpositionWS 等
Warp 内状态 所有线程条件一致 部分线程 TRUE,部分 FALSE → 分歧
GPU 行为 提前剔除不执行分支 串行执行两条路径,屏蔽对应线程
实际开销 极低  约等于无 较高  ≈ 两条路径之和
Warp 利用率 100% 50% ~ 100%(取决于分歧比例)
典型场景 功能开关、质量等级、平台差异 Mask 区域判断、像素距离判断、噪声阈值
推荐写法 shader_feature 或 float _Flag 优先用 lerp / step 消除分支

05  实践:用数学消除动态分支

对于简单的动态分支,通常可以用 lerpstepsaturate 等数学函数改写为无分支形式,让所有线程执行相同指令序列,规避 Warp 分歧。

示例:Mask 区域混合

half4 color;
if (mask.r > 0.5)
    color = colorA;
else
    color = colorB;
// step(edge, x) 当 x >= edge 时返回 1,否则 0
half t = step(0.5, mask.r);  // 0 或 1,无分支
half4 color = lerp(colorB, colorA, t); // 线性混合

示例:复杂路径的权重混合

// 注意:两条路径都会被执行,适合分支内容轻量的场景
half4 resultA = PathA(input);  // 始终执行
half4 resultB = PathB(input);  // 始终执行
half  weight  = step(0.5, mask.r);
half4 color   = lerp(resultB, resultA, weight);

权衡点

无分支写法让所有线程执行相同指令,消除了 Warp 分歧,但两条路径都会被计算。若某条路径极昂贵(如多次纹理采样),改为无分支后整体开销反而可能上升。需要根据路径复杂度具体判断。

什么时候动态分支仍然合适?

当分支体内计算量 相差悬殊,且屏幕上大多数像素只走轻量路径时,保留动态 if 可以避免所有线程都被拖到重路径的开销。例如:

// 角色区域(mask 极小),背景区域(mask 极大)
// Warp 大部分时候不分歧 → 动态 if 反而更省
if (characterMask.r > 0.5)
{
    color = ExpensiveSSS(input); // 次表面散射,仅角色像素
}

06  URP 中的额外注意事项

移动端差异

移动端 GPU(如 Mali、Adreno)的 Warp 尺寸和分歧惩罚与桌面端不同。部分低端 GPU 对动态分支的支持较弱,甚至会将整个分支展开(Scalar / Vector 寄存器压力倍增)。URP 的默认做法是 在 Mobile 质量档位下尽量不使用动态分支

Shader Keyword 与 Variant 爆炸

虽然 shader_feature 是消除运行时分支的最优解,但 Keyword 过多会引发 Variant 数量爆炸,导致编译时间和包体暴增。合理控制 Keyword 数量,对低优先级功能仍可退而求其次用 Uniform float 分支。

// _local 限定在材质范围,减少全局污染,推荐优先使用
#pragma shader_feature_local _RIM_LIGHT_ON
#pragma shader_feature_local _DETAIL_MAP_ON
// multi_compile 会生成所有组合,keyword 数量多时谨慎
#pragma multi_compile _ _FEATURE_A _FEATURE_B _FEATURE_C

总结 · 一图记忆

Uniform 分支 — 放心用
  • 条件来自 Properties / CBuffer
  • 同一 Draw Call 所有线程条件一致
  • GPU 提前剔除不执行路径
  • 开销近乎为零
  • 最优:改为 shader_feature 关键字
动态分支 — 谨慎用
  • 条件依赖逐像素变量
  • Warp 内线程条件可能不一致
  • 两条路径串行执行,产生 Warp 分歧
  • 开销 ≈ 路径 A + 路径 B
  • 优先改为 lerp / step 无分支形式
决策树
  • 条件是 Uniform?→ 直接用 if,或换成 Keyword
  • 条件是动态 + 分支体轻量?→ 用 lerp/step 消除
  • 条件是动态 + 分支体重量不对称?→ 保留 if,测量实际 GPU 耗时
移动端额外建议
  • 默认避免动态分支,尤其 Fragment Shader
  • 用 Frame Debugger + GPU Profile 验证实测数据
  • Keyword 数量控制在每个 Pass ≤ 4 个独立开关
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐