Unity的multi_compile和shader_feature编译指示符、shader variant和asset bundle
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
multi_compile编译指示符
multi_compile编译指示符的文档在这里。假如有以下的shader示例语句:
1#pragma multi_compile _USE_SEMITRANSPARENT _USE_OPAQUE
2#pragma multi_compile _MULTI_RED _MULTI_GREEN _MULTI_BLU
3
4...
5
6fixed4 frag (v2f i) : SV_Target
7{
8 fixed4 col = tex2D(_MainTex, i.uv);
9
10 #if _MULTI_RED
11 col = col * fixed4(1,0,0,1);
12 #elif _MULTI_GREEN
13 col = col * fixed4(0,1,0,1);
14 #elif _MULTI_BLUE
15 col = col * fixed4(0,0,1,1);
16 #endif
17
18 #if _USE_SEMITRANSPARENT
19 col.a = 0.5;
20 #elif _USE_OPAQUE
21 col.a = 1.0;
22 #endif
23
24 return col;
25}
那么根据排列组合,就会有 $ 2 \times 3 = 6 $ 种代码段的组合,即分别是 _USE_SEMITRANSPARENT与_MULTI_RED , _USE_SEMITRANSPARENT与_MULTI_GREEN , _USE_SEMITRANSPARENT与_MULTI_BLUE , _USE_OPAQUE与_MULTI_RED , _USE_OPAQUE与_MULTI_GREEN , _USE_OPAQUE与_MULTI_BLUE。总得来说,就是各种排列组合对应编译生成的变体, $\color{#FF0000}{无论有没有被使用上,都会被编译打包到游戏包或者资源包中}$ 。所以 在运行时 ,可以使用 Material.EnableKeyword 和 Material.DisableKeyword 的方式去启用禁用各个 shader variant 。如下代码段所示:
1private void OnGUI()
2{
3 if (GUI.Button(new Rect(0,0,150,50),"Red"))
4 {
5 var mat = m_Renderer.material;
6 mat.EnableKeyword("_MULTI_RED");
7 mat.DisableKeyword("_MULTI_GREEN");
8 mat.DisableKeyword("_MULTI_BLUE");
9 m_Renderer.material = mat;
10 }
11
12 if (GUI.Button(new Rect(170, 0, 150, 50), "Green"))
13 {
14 var mat = m_Renderer.material;
15 mat.DisableKeyword("_MULTI_RED");
16 mat.EnableKeyword("_MULTI_GREEN");
17 mat.DisableKeyword("_MULTI_BLUE");
18 m_Renderer.material = mat;
19 }
20
21 if (GUI.Button(new Rect(340, 0, 150, 50), "Blue"))
22 {
23 var mat = m_Renderer.material;
24 mat.DisableKeyword("_MULTI_RED");
25 mat.DisableKeyword("_MULTI_GREEN");
26 mat.EnableKeyword("_MULTI_BLUE");
27 m_Renderer.material = mat;
28 }
29
30 if (GUI.Button(new Rect(510, 0, 150, 50), "SemiTransparent"))
31 {
32 var mat = m_Renderer.material;
33 mat.EnableKeyword("_USE_SEMITRANSPARENT");
34 mat.DisableKeyword("_USE_OPAQUE");
35 m_Renderer.material = mat;
36 }
37
38 if (GUI.Button(new Rect(680, 0, 150, 50), "Opaque"))
39 {
40 var mat = m_Renderer.material;
41 mat.DisableKeyword("_USE_SEMITRANSPARENT");
42 mat.EnableKeyword("_USE_OPAQUE");
43 m_Renderer.material = mat;
44 }
45}
从上面的代码可以看出,凡是属于同一组的,且为互斥关系的各个keyword,在启用某一个keyword的时候,需要同时把其他的keyword给禁用掉。
shader_feature编译指示符
shader_feature编译指示符的文档在这里。假如有以下的shader示例语句:
1// 在界面选项中可以决定此keyword是否开启或者关闭,如果开启的话
2// 会将此keyword的开启记录在使用了该shader的材质球文件中。如果
3// 关闭的话则不会编译这段shader代码进去
4#pragma shader_feature _IS_PURPLE
5
6...
7
8#if _IS_PURPLE
9 uniform fixed _IsPurple;
10#endif
11 v2f vert (appdata v)
12 {
13 v2f o;
14 o.vertex = UnityObjectToClipPos(v.vertex);
15 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
16 return o;
17 }
18
19 fixed4 frag (v2f i) : SV_Target
20 {
21 fixed4 col = tex2D(_MainTex, i.uv);
22#if _IS_PURPLE
23 col = col * fixed4(0.5,0,0.5,1);
24#endif
25 return col;
26 }
从上面的代码中可以看出, shader_feature 在形式上和 multi_compile 类似。也都是以类似“宏”的方式去控制某个语句是否生效。但通常, shader_feature 中所声明的那个keyword,一般都在材质/shader的inspector面板中,在 预编辑阶段 中指定其启用或者不启用,将其保存在一个材质球文件中。而能否在运行时使用Material.EnableKeyword、Material.DisableKeyword启用禁用某个关键字,则有以下两种情况:
假定有两个材质球文件 UseShaderFeaturePurple.mat , UseShaderFeature.mat。这两个材质球都使用了 同一个 shader文件。UseShaderFeaturePurple材质球在编辑阶段,启用了 _IS_PURPLE keyword,UseShaderFeature材质球没有启用 _IS_PURPLE keyword。那么在运行时:
- 在 编辑器模式 中, 只要有 UseShaderFeaturePurple材质球,那么使用了UseShaderFeature材质球的那个game object,可以在运行时,通过使用 Marterial.EnableKeyword 方法,使得UseShaderFeature材质球的shader代码,也能执行启用了 _IS_PURPLE keyword的代码段。
- 在 非编辑器模式中, 就算 有UseShaderFeaturePurple材质球,只要该材质球未被使用上,就不会被打包进游戏包中,这时候如果使用了UseShaderFeature材质球的那个game object,使用Material.EnableKeyword方法 也是无法启用 这个keyword。
在编辑器和在独立运行包中的差异,可以参阅示例工程。工程的编译好的windows版程序在这里下载
shader variant和assetbundle
从上一节对shader_feature的介绍可以看到,为了尽可能减少shader代码的体积,shader_feature的原则就是能够stripping掉的shader代码,就会stripping掉它。所以在打包带有shader_feature的代码,尤其是使用assetbundle的方式时候,会经常出问题。据Unity3D研发团队介绍,在未引入ShaderVariantCollection机制之前,一个包含了shader_feature编译指示符的shader,要打包在assetbundle里面且被正确的引用,需要include all the materials that use a specific shader in the same AssetBundle.。即如果某材质球用到了这个有shader_feature的shader的话,这shader和这材质球就必须要打在同一个assetbundle里。显然这是不利于分包和资源组织的。Unity3D引入了ShaderVariantCollection解决了此问题。
在引入ShaderVariantCollection之后,可以一定程度上解决这个问题。使用ShaderVariantCollection解决问题的步骤如下:
- 创建一个ShaderVariantCollection文件
- 将包含有shader_featured的shader文件添加到ShaderVariantCollection文件中的集合里
- 向ShaderVariantCollection文件中的集合添加变体标签(variant tags)
- ShaderVariantCollection文件和记录在它里面的shader文件,打包在同一个assetbundle中
Unity3D研发团队提供了一个演示使用ShaderVariantCollection文件的视频,点击下载。 关于更详尽的shader variant及shader打包的规则, Nicholas10128 在github上有一份比较详尽的文档。
参考网页
How To Use Shader Features With Asset Bundles?