Shader Variant
基础知识
什么是 Shader Variant
在写 Shader 时,往往会在 Shader 中定义多个宏,并在 Shader 代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的 Shader 源码。其中每一种组合就是这个 Shader 的一个 变体
Material Shader Keywords 与 Shader Variant
Material 所包含的 Shader Keywords 表示启用 Shader 中对应的宏,Unity 会调用当前宏组合所对应的变体来为 Material 进行渲染
- 在 Editor 下,可以通过将 Material 的 Inspector 调成 Debug 模式来查看当前 Material 定义的 Keywords,也可以在此模式下直接定义 Keywords,用空格分隔 Keyword
- 在代码中,可用
Material.EnableKeyword()
、Material.DisableKeyword()
、Shader.EnableKeyword()
、Shader.DisableKeyword()
来启用 / 禁用相应的宏Enable
函数应与Disable
函数相对应。若一个宏由Material.EnableKeyword()
开启,则应由Material.DisableKeyword()
关闭,Shader.DisableKeyword()
无法关闭这个宏
multi_compile
与 shader_feature
multi_compile
的用法解析
1 |
|
表示定义了两个变体,OFF
和 ON
。在运行时,其中的一个将被激活,根据材质或者全局着色器关键字(#ifdef OFF
之类的宏命令也可以)来确定激活哪个。若两个关键词都没有启用,那么将默认使用前一个选项,也就是OFF
。
也可以同时创建多个变体:
1 |
|
还可以使用多行指令对变体进行组合,但是这样做的话,会导致变体数量成倍的增长,如果使用下面的代码生成变体,会得到
1 |
|
shader_feature
用法简析
shader_feature
的用法与 multi_compile
大致相同,唯一的区别在于 shader_feature
不会将不用的 Shader 变体添加到程序中去。shader_feature
更适用于材质的关键字,而 multi_compile
更适用于代码的全局关键字
1 |
|
其实
#paragma shader_feature A
是#paragma shader_feature _ A
的简写,下划线表示未定义宏(NoKeyword)。因此此时 Shader 其实对应了两个变体,一个是 NoKeyword,一个是定义了宏A
。而#paragma multi_compile A
并不存在简写这一说,所以 Shader 此时只对应A
这个变体。若要表示未定义任何变体,则应写为#paragma multi_compile _ A
如何控制项目中 Shader 变体的生成
生成方式 | 优点 | 缺点 |
---|---|---|
Shader 与材质打在一个包中 | 变体根据材质中的 keywords 自动生成 |
|
Shader 单独打包,使用 multi_compile 定义全部宏 |
全部变体都被生成,不会发生需要的变体未生成的情况 |
|
Shader 单独打包,shader_feature (需要使用 ShaderVariantCollection 生成变体)与multi_compile (还是会生成所有变体)结合使用 |
能够有效控制 shader_feature 变体数量 |
|
使用 shader_feature
的解决方案:ShaderVariantCollection
ShaderVariantCollection 介绍
ShaderVariantCollection 的其中一个作用就是用来记录 Shader 中使用shader_feature
定义的宏产生的变体。能够设置生成任何变体,从而避免生成不必要的变体;Shader 不必和材质打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的。
在 Unity 中可以通过 Create->Shader->Shader Variant Collection,就可以新建一个 ShaderVariantCollection 文件
ShaderVariantCollection 生成通过 shader_feature
定义的变体规则
必定生成首个宏定义开启所对应的变体
pragma shader_feature A
:除了生成 A 的变体,nokeyword 所对应的变体也会被生成pragma shader_feature A B C
:ShaderVariantCollection 中即使未添加变体 A,这个变体也会被生成
Shader 中又多个 Pass 时变体的生成规则
读取 ShaderVariantCollection 中已存在的变体,获取他们的 keywords
将这些 keywords 分别与每个 Pass 的多组 keywords 列表求交集,取交集中 keywords 数量最多的那组
用得到的 keywords 与对应 PassType 生成 Shader 变体,并添加到 ShaderVariantCollection 中
若得到的交集中有新的 keywords,则回到 2
例如 Shader 中有 ForwardBase、ForwardAdd、Normal 三种 PassType,定义的宏如下:
ForwardBase ForwardAdd Normal #pragma shader_feature A
#pragma shader_feature B
#pragma shader_feature C
#pragma shader_feature A
#pragma shader_feature E
#pragma shader_feature A
#pragma shader_feature B
#pragma shader_feature E
此时若 ShaderVariantCollection 中包含的变体是 ForwardBase ABC,ForwardAdd AE。则此时生成的变体为:这三种 PassType 的默认定义的宏 (nokeyword) 所对应的变体(3 个)以及原先直接包含的 ForwardBase ABC、ForwardAdd AE。除此之外 Unity 还会额外生成 ForwardAdd A、ForwardBase A、Normal A、Normal AB、 ForwardBase AB、Normal AE 这 6 个变体
1
2
3
4
5
6ABC ∩ Add AE -> Add A (A is NewKeyword)
A ∩ Base ABC -> Base A
A ∩ Normal ABE -> Normal A
ABC ∩ Normal ABE -> Normal AB (AB is NewKeyword)
AB ∩ Base ABC -> Base AB
AE ∩ Normal ABE -> Normal AE
变体的调用规则
当 ShaderVariantCollection 将变体准确生成后,便能在运行时通过修改材质中的 keywords 来实现对不同变体的调用。假设某 collection 生成的变体只有 Forward ABC,Forward ABE,Forward nokeyword 这三种,则此时调用关系如下:
Material 中的 Keywords | 调用的变体 | 解释 |
---|---|---|
A B C | Forward A B C | 正常匹配 |
A B | Forward nokeyword | 没有匹配的变体,调用首个被定义的宏 所对应的变体 |
A B C D | Forward A B C | 调用交集中 keyword 数量多的变体 ABCD ∩ ABC = ABC ABCD ∩ ABE = AB |
A B C E | Forward A B C | 交集中 keyword 数量相同,在 collection 中谁在前就调用谁 |
A B E C | Forward A B C | 与在 material 中的定义顺序无关 |
项目中变体的添加
- 遍历每个材质,提取其 Shader keywords
- 将获得的 keywords 与 Shader 的每个 PassType 所包含的宏定义做交集,并将其结果添加到 ShaderVariantCollection 中
变体代码在 Shader 中编写规范
- 建议使用
shader_feature
时将定义语句写成完整模式,并且不要在一个语句中定义多个宏#pragma shader_feature _ A
,不建议写成#pragma shader_feature A
- 若在 Shader 中使用
shader_feature
,请为这个 Shader 指定一个 CustomEditor