简介
本文总结Unity变体与Shader打包相关内容。基于Unity 2020.3和Built-in管线。
1.宏的定义
首先说明,本文中的宏不包含由#define定义的宏。
Unity提供了两种定义宏的方法:
- multi_compile
- shader_feature
以及相应的局部版本(2019引入):
- multi_compile_local
- shader_feature_local
另外还有只在特定阶段起作用的宏定义方法(2020.3引入),如:
- multi_compile_fragment
- shader_feature_local_vertex
实际使用中multi_compile与shader_feature基本没有区别。他们主要有两个区别。第一个是在shader_feature后面只跟了一个宏的时候,会生成两个变体,一个是不包含该宏,另一个是包含该宏。另一个区别是打包时的表现。在打包的时候shader_feature不会包含没有使用的变体,而multi_compile会排列组合所有变体。
2.变体
Unity在编译shader时,不同的宏组合会生成独立的shader程序,这些独立的shader程序就叫做变体。
实际项目中,宏的使用非常复杂:有些宏是静态的,有些宏是动态的。因此Unity很难帮我们正确的收集所有变体:不是缺少,就是冗余。因此,Unity提供了两个操作变体的工具:Shader Variant Collection和IPreprocessShaders.OnProcessShader。
2.1 Shader Variant Collection
Shader Variant Collection是一种包含shader变体列表的文件。在Project窗口右键依次点击[Create] - [Shader] - [Shader Variant Collection]可以创建它。或者在Graphics Setting里可以直接获得当前编辑器中使用到的变体集。该文件如下图所示:
利用Shader Variant Collection我们可以手动的将需要的变体加入其中。
加入的时候需要设置Pass Type和宏组合。其中Pass Type就是shader中在pass的Tags里设置的LightMode。具体见参考资料。
2.2 IPreprocessShaders.OnProcessShader
我们可以通过继承IPreprocessShaders以及实现OnProcessShader接口来实现剔除变体的目的。简易的代码如下:
public class ShaderProcess : IPreprocessShaders
{
public int callbackOrder => 0;
public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
{
for (int i = data.Count - 1; i >= 0; --i)
{
if (data[i].graphicsTier != UnityEngine.Rendering.GraphicsTier.Tier1)
{
data.RemoveAt(i);
}
}
}
}
通过实现这个接口,我们可以更深入的理解Unity变体的编译过程,Unity是以shader阶段为单位进行编译的,如:编译VS时调用一次该接口,编译PS时再调用一次该接口。因此Unity建议在定义宏时加上阶段后缀,这样可以减少shader的编译时间。另外在Build-in管线中,宏是区分Tier的,即使并没有使用Tier相关宏。Tier的使用比较少,而且在SRP中已经废除了Tier机制,建议直接把Tier2和Tier3的变体剔除掉。
3.总结
宏的使用要小心谨慎。使用不当可能会导致变体爆炸,极大的拖慢打包速度,以及增大内存。而缺变体会导致效果出错。Unity提供的两种变体操作方法,一种是手动增加变体,一种是手动减少变体。或许这两种方法正好对应着shader_feature和multi_compile。在实际使用中,笔者发现这两种方法同时使用有重复用功的嫌疑。比如如果完整的手动收集了变体,那么可能并不需要再剔除了。那么是不是可以只使用其中一种方法呢?比如所有宏都使用shader_feature定义,然后通过shader variant collection手动收集所有需要的变体。再或者所有宏都使用multi_compile定义,然后通过IPreprocessShaders.OnProcessShader剔除掉所有不可能出现的宏组合。具体怎么处理还有待实践验证。
参考资料:
[1]: https://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html
[2]: https://docs.unity3d.com/Manual/shader-variant-collections.html
[3]: https://docs.unity3d.com/ScriptReference/Rendering.PassType.html
[4]: https://docs.unity3d.com/Manual/shader-predefined-pass-tags-built-in.html
[5]: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@11.0/manual/urp-shaders/urp-shaderlab-pass-tags.html#urp-pass-tags-lightmode