unity ShaderVariantCollection与shader变体

原文链接:https://blog.csdn.net/RandomXM/article/details/88642534

 

基础知识介绍
什么是ShaderVariant
在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的shader源码。其中每一种组合就是这个shader的一个变体(Variant)。

Material ShaderKeywords与ShaderVariant
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()无法关闭这个宏。Material中定义的Keywords由Material的函数进行设置。

multi_compile与shader_feature
multi_compile与shader_feature可在shader中定义宏。两者区别如下图所示:

multi_compile    shader_feature
定义方式    #pragma multi_compile A    #pragma shader_feature A
宏的适用范围    大多数shader    一般仅针对shader自身
变体的生成    生成所有的变体    可自定义生成何种变体
默认定义的宏    默认定义首个宏    默认定义首个宏
定义方式
定义方式中值得注意的是,#pragma shader_feature A其实是 #pragma shader_feature _ A的简写,下划线表示未定义宏(nokeyword)。因此此时shader其实对应了两个变体,一个是nokeyword,一个是定义了宏A的。
而#pragma multi_compile A并不存在简写这一说,所以shader此时只对应A这个变体。若要表示未定义任何变体,则应写为 #pragma multi_compile __ A。

宏的适用范围
multi_compile定义的宏,如#pragma multi_compile_fog,#pragma multi_compile_fwdbase等,基本上适用于大部分shader,与shader自身所带的属性无关。
shader_feature定义的宏多用于针对shader自身的属性。比如shader中有_NormalMap这个属性(Property),便可通过#pragma shader_feature _NormalMap来定义宏,用来实现这个shader在material有无_NormalMap时可进行不同的处理。

变体的生成
#pragma multi_compile A B C
#pragma multi_compile D E
则此时会生成 A D、A E、B D、B E、C D、C E这6中变体。
shader_feature要生成何种变体可用shader variant collection进行自定义设置。

默认定义的宏
当material中的keywords无法对应shader所生成的变体时,Unity便会默认定义宏定义语句中的首个宏,并运行相应的变体来为这个material进行渲染。
multi_compile与shader_feature都默认定义首个宏,如下表所示:

宏定义语句    默认定义的宏
#pragma shader_feature A    nokeyword(存在简写问题)
#pragma shader_feature A B C    A
#pragma multi_compile A    A
#pragma multi _compile A B C    A
如何控制项目中Shader变体的生成
项目中shader的生成方式主要有三种,其优缺点如下表所示:

生成方式    优点    缺点
shader与material打在一个包中    变体根据material中的keywords自动生成    多个不同的material包中可能存在相同的shader变体,造成资源冗余
若在程序运行时动态改变material的keyword其变体可能并没有被生成
Shader单独打包,使用multi_compile定义全部宏    全部变体都被生成,不会发生需要的变体未生成的情况    生成的变体数量庞大,严重浪费资源
Shader单独打包,shader_feature与multi_compile结合使用    能够有效控制变体数量    如何确定哪些变体需要生成
容易遗漏需要生成的变体
而我们希望的结果是在保证渲染效果正确的情况下,要尽可能的控制项目中shader的变体数量,避免产生冗余资源。幸运的是,Unity已经为我们准备好了解决方案:ShaderVariantCollection。

解决方案:ShaderVariantCollection
ShaderVariantCollection介绍
Shader Variant Collection是用来记录shader中哪些变体是实际使用的。其优点主要有:在shader_feature与multi_compile结合使用时,能够设置生成何种变体,从而避免生成不必要的变体;shader不必和material打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的。

在Unity中可以通过Create->Shader-> Shader Variant Collection,就可以新建一个shader variant collection文件,shader variant collection 的使用如下图所示:

点击增加变体后,会出现变体选择窗口

配置好需要生成的变体后,将collection与shader打在同一个包中,便能准确生成面板中所配置的shader变体。

ShaderVariantCollection生成变体规则
除了在collection中配置的变体会被生成外,Unity还在后台为我们多生成了几个变体,这几个变体是“隐藏的”,并未在collection面板中显示。

必定生成首个宏定义开启所对应的变体。
Shader中通过#pragma shader_feature A定义了宏A,并在collection中加入了宏A所对应的变体,如下图所示:

此时生成的变体除了collection中已经存在的ForwardBase A外,还会生成变体ForwardBase nokeyword。因为只定义单个宏时,A 为 _ A的简写。实际上首个被定义的宏为nokeyword,故 nokeyword所对应的变体必定会被生成。
同理,以 #pragma shader_feature A B C来定义宏时,即使collection中未添加变体Forward A,这个变体也必定会被生成(当shader的PassType仅有ForwardBase)。

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)。定义的宏如下:

Base    Add    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中包含的变体是 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 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 C E    Forward A B C    与在material中的定义顺序无关

以上规则均为根据测试总结归纳出来的规则,若有错误之处还请严加指正!

项目中对Shader Variant的管理
项目中变体的添加
那么项目中是如何确定哪些变体是需要加到collection中的呢?我们的做法是:

遍历每一个Material,提取其shader keywords。
将获得的keywords与shader的每个PassType所包含的宏定义做交集,并将其结果添加到collection中。
举个简单的例子,Material中的Keywords为A B C D,则shader的PassType、PassType中所定义的宏、需要往collection中添加的变体则如下表所示:

PassType    定义的宏    需要往collection中添加的变体
ForwardBase    #pragma shader_feature A
#pragma shader_feature B    Forward A B
((ABCD ∩ AB = AB))
ForwardAdd    #pragma shader_feature _ C D    Add C
(ABCD ∩ C = C,ABCD ∩ D = D,但C的定义在D前,故只添加C)
Normal    #pragma shader_feature _ E F    Normal NoKeyword
(ABCD ∩ E = NoKeyword)
(ABCD ∩ F = NoKeyword)
需要说明的是,我们自己的代码里为了降低变体生成逻辑的复杂度、保持collection面板上变体的直观性,不将Unity为我们额外生成的那几个变体添加到collection面板中,但要记得Unity是会为我们生成额外的变体的。

Shader编写规范
建议使用shader_feature时将定义语句写成完整模式,并且不要在一个语句中定义多个宏。
完整模式:#pragma shader_feature _ A,不建议写成#pragma shader_feature A。
不建议在一个语句中定义多个宏,如: #pragma shader_feature _ A B C,若一定要定义多个宏,请务必将其写成完整模式,不使用完整模式在切换shader时可能会与想要的效果不一致,具体原因尚未测得。

若在shader中使用shader_feature,请为这个shader指定一个CustomEditor
每个使用shader_feature来定义Keyword的shader都需要再末尾加个 CusomEditor “xxxx”,并在代码中实现类xxxx(需继承自UnityEditor.ShaderGUI),用来对Keywords定义进行设定。
这么做是因为Material中的部分Keyword是由shader中的属性(Properties)所控制的。比如shader中含有_NormalMap的属性并且定义了与_NormalMap相关的Keyword,这个Keyword需要在Material含有NormalMap时添加,不含NormalMap时移除。这个功能可由自定义的CustomEidtor实现。
具体如何写这个CustomEditor类可参考Unity builtin_shaders\Editor\StandardShaderGUI.cs。该文件可去Unity官网下载,下载时选择内置着色器即可。


如果需要在代码中开关宏,请使用multi_compile来定义这个宏,以免变体丢失。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity中的Shader变体是指在编译阶段根据当前渲染平台和材质属性的不同而生成的多个不同的着色器程序。Shader变体会根据平台的不同动态地作出适配,以实现在不同平台下的最佳性能和视觉效果。 在编写Shader时,我们可以使用多种预处理指令和变量来定义Shader变体。预处理指令比如#pragma multi_compile和#pragma shader_feature可以用来指示编译器在编译时根据条件来包含或排除某些代码块。通过这样的方式,我们可以根据不同情况选择不同的代码路径。 Shader变体的生成是基于Shader的特性和特定的材质属性。特性是用来定义一组可选功能的开关,可以在材质中进行开关的切换。材质属性是指材质上的一些自定义属性,比如颜色、纹理等。因此,Shader变体的生成是根据这些特性和属性的组合来确定的。 Shader变体的生成会带来一定的开销,因为每个变体都需要编译和存储。为了减少这种开销,Unity使用了渐变的方式生成变体。即在编译过程中,Unity会根据之前生成的变体生成新的变体,以便在之后的编译过程中尽可能重用已经生成的变体,从而减少重复的工作。 对于Shader变体的管理,Unity提供了几种优化的方式。首先,我们可以使用ShaderVariantCollection来存储和管理常用的变体,从而减少编译时间和内存占用。其次,可以使用ShaderKeywords来动态地切换Shader的特性,以实现更精细的控制和优化。 总之,UnityShader变体功能可以根据不同平台和材质属性生成多个不同的着色器程序,以实现最佳的性能和视觉效果。合理的使用Shader变体管理和优化可以大大提升游戏的性能和兼容性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值