Unity的multi_compile和shader_feature编译指示符、shader variant和asset bundle
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
multi_compile编译指示符
multi_compile编译指示符的文档在这里。假如有以下的shader示例语句:
#pragma multi_compile _USE_SEMITRANSPARENT _USE_OPAQUE
#pragma multi_compile _MULTI_RED _MULTI_GREEN _MULTI_BLU
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
#if _MULTI_RED
col = col * fixed4(1,0,0,1);
#elif _MULTI_GREEN
col = col * fixed4(0,1,0,1);
#elif _MULTI_BLUE
col = col * fixed4(0,0,1,1);
#endif
#if _USE_SEMITRANSPARENT
col.a = 0.5;
#elif _USE_OPAQUE
col.a = 1.0;
#endif
return col;
}
那么根据排列组合,就会有 $ 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 。如下代码段所示:
private void OnGUI()
{
if (GUI.Button(new Rect(0,0,150,50),"Red"))
{
var mat = m_Renderer.material;
mat.EnableKeyword("_MULTI_RED");
mat.DisableKeyword("_MULTI_GREEN");
mat.DisableKeyword("_MULTI_BLUE");
m_Renderer.material = mat;
}
if (GUI.Button(new Rect(170, 0, 150, 50), "Green"))
{
var mat = m_Renderer.material;
mat.DisableKeyword("_MULTI_RED");
mat.EnableKeyword("_MULTI_GREEN");
mat.DisableKeyword("_MULTI_BLUE");
m_Renderer.material = mat;
}
if (GUI.Button(new Rect(340, 0, 150, 50), "Blue"))
{
var mat = m_Renderer.material;
mat.DisableKeyword("_MULTI_RED");
mat.DisableKeyword("_MULTI_GREEN");
mat.EnableKeyword("_MULTI_BLUE");
m_Renderer.material = mat;
}
if (GUI.Button(new Rect(510, 0, 150, 50), "SemiTransparent"))
{
var mat = m_Renderer.material;
mat.EnableKeyword("_USE_SEMITRANSPARENT");
mat.DisableKeyword("_USE_OPAQUE");
m_Renderer.material = mat;
}
if (GUI.Button(new Rect(680, 0, 150, 50), "Opaque"))
{
var mat = m_Renderer.material;
mat.DisableKeyword("_USE_SEMITRANSPARENT");
mat.EnableKeyword("_USE_OPAQUE");
m_Renderer.material = mat;
}
}
从上面的代码可以看出,凡是属于同一组的,且为互斥关系的各个keyword,在启用某一个keyword的时候,需要同时把其他的keyword给禁用掉。
shader_feature编译指示符
shader_feature编译指示符的文档在这里。假如有以下的shader示例语句:
// 在界面选项中可以决定此keyword是否开启或者关闭,如果开启的话
// 会将此keyword的开启记录在使用了该shader的材质球文件中。如果
// 关闭的话则不会编译这段shader代码进去
#pragma shader_feature _IS_PURPLE
...
#if _IS_PURPLE
uniform fixed _IsPurple;
#endif
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
#if _IS_PURPLE
col = col * fixed4(0.5,0,0.5,1);
#endif
return col;
}
从上面的代码中可以看出, 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?