(1)
1. 必定生成首个宏定义开启所对应的变体。
Shader中通过#pragma shader_feature A定义了宏A,并在collection中加入了宏A所对应的变体,如图4所示:
图4
此时生成的变体除了collection中已经存在的ForwardBase A外,还会生成变体ForwardBase nokeyword。因为只定义单个宏时,A 为 _ A的简写。实际上首个被定义的宏为nokeyword,故 nokeyword所对应的变体必定会被生成。
同理,以 #pragma shader_feature A B C来定义宏时,即使collection中未添加变体Forward A,这个变体也必定会被生成(当shader的PassType仅有ForwardBase)。
2.Shader中有多个Pass时变体的生成规则
a.读取ShaderVariantCollection中已存在的变体,获取它们的Keywords。
b. 将这些Keywords分别与每个Pass的多组Keywords列表求交集,取交集中Keywords数量最多得那组。
c. 用得到的Keywords与对应的PassType生成ShaderVariant,并添加到ShaderVariantCollection中。
d. 若得到得交集中有新的Keywords,则回到b。 上述过程类似递归。例如: Shader 中有 ForwardBase、ForwardAdd、Normal 三种PassType(以下为了方便简称Base、Add、 Normal)。定义的宏如下:
BaseAddNormal#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中包含的变体是 Base ABC,Add AE。则此时生成的变体为:这三种PassType的默认定义的宏(nokeyword)所对应的变体(3个)以及原先直接包含的Base ABC、Add AE。除此之外Unity还会额外生成Add A、Base A、Normal A、Normal AB、 Base AB、Normal AE这6个变体。
ABC ∩ 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
变体的调用规则
当collection将变体准确生成后,便能在运行时通过修改material中的keywords来实现对不同变体的调用。
假设某collection生成的变体只有Forward ABC,Forward ABE,Forward nokeyword这三种,则此时调用关系如下:
Material中的Keywords调用的变体解释A B CForward A B C正常匹配A BForward nokeyword没有匹配的变体,调用首个被定义的宏 所对应的变体A B C DForward A B C调用交集中keyword数量多的变体
ABCD ∩ ABC = ABC
ABCD ∩ ABE = ABA B C EForward A B C交集中keyword数量相同,在collection中谁在前就调用谁A B E CForward A B C与在material中的定义顺序无关
图5以上均为根据测试结果总结归纳出来的规则,若有错误之处还请严加指正!
shader_feature方案项目具体实现方式
项目中变体的添加
那么项目中是如何确定哪些变体是需要加到collection中的呢?我们的做法是:
a. 遍历每一个Material,提取其shader keywords。
b. 将获得的keywords与shader的每个PassType所包含的宏定义做交集,并将其结果添加到collection中。
举个简单的例子:
Material中的Keywords为A B C D,则shader的PassType、PassType中所定义的宏、需要往collection中添加的变体则如下表所示:
PassType定义的宏需要往collection中添加的变体ForwardBase#pragma shader_feature A
#pragma shader_feature BForward A B
(ABCD ∩ AB = AB)ForwardAdd#pragma shader_feature _ C DAdd C
(ABCD ∩ C = C,ABCD ∩ D = D,但C的定义在D前,故只添加C)Normal#pragma shader_feature _ E FNormal NoKeyword
(ABCD ∩ E = NoKeyword)
(ABCD ∩ F = NoKeyword)
需要说明的是,我们自己的代码里为了降低变体生成逻辑的复杂度、保持collection面板上变体的直观性,不将Unity为我们额外生成的那几个变体添加到collection面板中,但要记得Unity是会为我们生成额外的变体的。
Shader变体收集与打包
https://zhuanlan.zhihu.com/p/68888831
(1)
自己收集 Shader 所有开启的关键字,在 ShaderVariantCollection 中开启,Shder 、 ShaderVariantCollection 必须打包到一个 AssetBundle ,材质可以不用打包到一起
参考
对Shader Variant的研究(概念介绍、生成方式、打包策略)
https://blog.csdn.net/RandomXM/article/details/88642534
Shader变体收集与打包
https://zhuanlan.zhihu.com/p/68888831
Unity3D Shader加载时机和预编译
https://www.cnblogs.com/rexzhao/p/7884905.html
(2)
仅仅添加 Shader 到对应的 ShaderVariantCollection 中,但是材质 、 Shader 和 ShaderVariantCollection 必须打包到一个 AssetBundle 中
参考
ShaderVariantCollection解决shader_feature丢失
https://www.dazhuanlan.com/2019/12/16/5df6a886cf4dd/
(3)
warmup 就是一次性创建 Gpu Program ,否则在使用的时候 Unity 才会自动创建 Gpu Program
ShaderLab在相关shader加入内存时就已经产生,但如果没有被渲染的话不会触发CreateGPUProgram操作,如果提前在ShaderVariantCollection中收集了相关变体并执行了warmup的话,第一次渲染时就不会再CreateGPUProgram,对卡顿会有一定好处。
参考文章:
Shader变体收集与打包
https://zhuanlan.zhihu.com/p/68888831