type
status
date
slug
summary
tags
category
icon
password
我们先来看ue的一段BRDF:
除了传统的#if SHADING_PATH_MOBILE
这种宏分支外,我们还能看到if (bHasAnisotropy) 以及 BRANCH 关键字
这两者为什么怎么用?
在shader内少使用if 动态分支是每个TA都知道,但是往往忽略了 相干性(Coherence)
我们看这句:
如果这个材质开启了各向异性(Anisotropy),那么这个 Draw Call 绘制的所有像素,其
bHasAnisotropy 几乎都是 true。再说个通俗的例子:如果if(guffer.roughness>0.8) 这个动态分支
如果确实roughness要么是0.9 要么是0.7 那么他的消耗其实是很低的 (一致性分支)
最怕的就是这个roughness是较为随机的,这才是if 动态分支真正害怕
这种分支会让 GPU 的并行线程「分化」,一部分走 if,一部分走 else,严重降低并行效率,是性能杀手
用动态分支减少变体数量
那么为什么不直接用宏来实现bHasAnisotropy的分支?
比如定义一个这个的宏 然后#define HASANISOTROPY ?
这是因为:减少变体
每增加一个宏开关,Shader 的变体数量就会翻倍(2的n)
UE 将一些不是极其昂贵或者逻辑复杂的特性,放在运行时用
BRANCH 来处理。这样多个材质可以共享同一个编译出来的二进制文件(PSO),只是运行时的逻辑路径不同BRANCH关键字
Branch 是DX11 + 的 HLSL 高级编译指令,是给 GPU 的分支执行「明确的指令」,作用是告诉 GPU:这个 if/else 分支,按「动态分支」的方式执行。
如果不写这个关键字 就是默认模式flatten
使用if else 是真的会把两个代码都跑一遍 然后丢弃不需要的代码
那 一致性条件 起到了什么作用?
既然 [branch] 是跳转,为什么大家还要强调 一致性 呢?这就涉及到 GPU 的 Warp(线程束) 机制了:
- 如果条件一致(比如 0 或 1): 整个 Warp(比如 32 个像素)都要跳。这时候 GPU 效率极高,因为它直接让这 32 个线程一起“飞”过不用的代码段。这就是最理想的动态分支。
- 如果条件不一致(分歧): 如果 Warp 里有 16 个像素想走 if,16 个想走 else。 这时 [branch] 就会变慢:它必须先带着 16 个线程跑 if(另外 16 个发呆),再带着剩下的 16 个跑 else(前 16 个发呆)。 注意: 即使这种情况下,它依然比 flatten 好,因为它至少在某一时刻只跑了一半的指令量,而 flatten 是全量跑
总结:BRANCH 和 条件一致性
好了 就这么简单 BRANCH 和 一致性的条件 形成了黄金组合