Unity5的ShaderVariants使用详解
发表于2018-11-12
ShaderVariants(下文用shader变种描述)是unity中用来合并组织shader的一个方式之一,在shader中的使用类似宏定义。这篇内容就给大家介绍下ShaderVariants的使用,其中前两节是基础部分,看官方文档也可以了解,只是通过实验来加强理解。第三节shader变种的打包是重点描述的,比较重要。
一、生成shader变种机制
为了做实验,制作shader如下:
Shader "Custom/Color"
{
SubShader
{
Pass
{
Cull Off
ZWrite Off
Lighting Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma shader_feature RED GREEN BLUE
//#pragma shader_feature GREEN
//#pragma shader_feature BLUE
//#pragma multi_compile RED GREEN BLUE
//#pragma multi_compile __ GREEN
struct v2f
{
fixed4 pos : SV_POSITION;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : COLOR
{
fixed4 c = fixed4(0,0,0,1);
#ifdef RED
c += fixed4(1,0,0,1);
#endif
#ifdef GREEN
c += fixed4(0,1,0,1);
#endif
#ifdef BLUE
c += fixed4(0,0,1,1);
#endif
return c;
}
ENDCG
}
}
CustomEditor "ColorsGUI"
}
shader的变种数量可以通过shader面板上面查看到,点击Show按钮可以看到都有哪些变种

做如下实验对比shader_feature:
单一行命令
#pragma shader_feature RED
两个变种: __, RED
#pragma shader_feature RED GREEN
两个变种:RED,GREEN
#pragma shader_feature RED GREEN BLUE
三个变种:RED,GREEN,BLUE
#pragma multi_compile RED
一个变种:RED
#pragma multi_compile RED GREEN
两个变种:RED,GREEN
#pragma multi_compile RED GREEN BLUE
三个变种:RED,GREEN,BLUE
分析:shader_feature 和multi_compile 在Keyword 数量大于1时,生成变种的机制是一样的,都是一个keyword一个变种;当keyword只有1个时,shader_feature 会增加一个none变种。再来做个实验:
#pragma shader_feature __ RED
两个变种: none, RED
可见,当shader_feature 的keyword数量是1时不论是否有__符号,都会增加一个空keyword(__),除了这个在生成变种的机制上和multi_compile都是一致的。
多行命令
#pragma multi_compile __ RED
#pragma multi_compile __ GREEN
四个变种:__,RED,GREEN,RED GREEN
分析:多行命令就是单行命令的乘法组合,shader_feature和multi_compile除了单一keyword时是否补__之外,在多行命令中也是一致的。
二、匹配shader变种机制
为了实验shader变种的匹配,做一个方便定义keyword的shader界面,代码如下:
public class ColorsGUI: ShaderGUI {
private static bool bRed = false;
private static bool bGreen = false;
private static bool bBlue = false;
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
// render the default gui
base.OnGUI(materialEditor, properties);
Material targetMat = materialEditor.target as Material;
bRed = Array.IndexOf(targetMat.shaderKeywords, "RED") != -1;
bGreen = Array.IndexOf(targetMat.shaderKeywords, "GREEN") != -1;
bBlue = Array.IndexOf(targetMat.shaderKeywords, "BLUE") != -1;
EditorGUI.BeginChangeCheck();
bRed = EditorGUILayout.Toggle("红", bRed);
bGreen = EditorGUILayout.Toggle("绿", bGreen);
bBlue = EditorGUILayout.Toggle("蓝", bBlue);
if (EditorGUI.EndChangeCheck())
{
if (bRed)
targetMat.EnableKeyword("RED");
else
targetMat.DisableKeyword("RED");
if (bGreen)
targetMat.EnableKeyword("GREEN");
else
targetMat.DisableKeyword("GREEN");
if (bBlue)
targetMat.EnableKeyword("BLUE");
else
targetMat.DisableKeyword("BLUE");
}
}
}
shader界面如下:

这样,勾选一个颜色,就会enable一个keyword,通过查看结果颜色就能知道匹配到了哪个shader变种,实验如下:
#pragma multi_compile RED GREEN(两个变种:RED, GREEN)
材质keyword为RED : 显示红色(匹配RED)
材质keyword为GREEN : 显示绿色(匹配GREEN)
材质keyword为__ : 显示红色(匹配RED)
材质keyword为RED GREEN: 显示红色(匹配RED)
分析:当keyword存在正好匹配的变种时直接匹配、当keyword不存在匹配变种时取第一个变种
三、shader变种打包
打包的代码如下:
[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
List<AssetBundleBuild> maps = new List<AssetBundleBuild>();
maps.Clear();
//资源打包
string[] files = {
"Assets/ShaderVariants/Resources/red.prefab",
};
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = "ShaderVariantsPrefab";
build.assetNames = files;
maps.Add(build);
string[] file2s = {
"Assets/ShaderVariants/Resources/Shader/Colors.shader",
};
AssetBundleBuild build2 = new AssetBundleBuild();
build2.assetBundleName = "ShaderVariantsShader";
build2.assetNames = file2s;
maps.Add(build2);
BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle;
BuildPipeline.BuildAssetBundles("Assets/StreamingAssets", maps.ToArray(), options, BuildTarget.StandaloneWindows);
AssetDatabase.Refresh();
}
读包并实例化的代码如下:
public class BundleLoader : MonoBehaviour
{
void Start () {
load();
}
public void load()
{
StartCoroutine(LoadMainGameObject("file://" + Application.dataPath + "/StreamingAssets/" + "ShaderVariantsShader"));
StartCoroutine(LoadMainGameObject("file://" + Application.dataPath + "/StreamingAssets/" + "ShaderVariantsPrefab"));
}
private IEnumerator LoadMainGameObject(string path)
{
WWW bundle = new WWW(path);
yield return bundle;
if (bundle.url.Contains("ShaderVariantsShader"))
{
//依赖shader包
bundle.assetBundle.LoadAllAssets();
}
else
{
UnityEngine.Object obj = bundle.assetBundle.LoadAsset("assets/ShaderVariants/resources/red.prefab");
Instantiate(obj);
bundle.assetBundle.Unload(false);
}
}
}
为了对比shader_feature 和multi_compile 以及shader依赖和非依赖打包,做如下实验:
#pragma shader_feature RED GREEN BLUE
将选中RED关键字的prefab打包,加载bundle和其中的prefab,显示了红色,此时改变此材质的keyword为GREEN或者BLUE,没有效果
#pragma multi_compile RED GREEN BLUE
将选中RED关键字的prefab打包,加载bundle和其中的prefab,显示了红色,此时改变此材质的keyword为GREEN或者BLUE,可以显示绿色和蓝色
分析:shader_feature声明变种时,打包只会打包被资源引用的keyword变种,multi_compile声明变种时,打包会把所有变种都打进去
#pragma shader_feature RED GREEN BLUE
将选中RED关键字的prefab和shader依赖打包,加载bundle和其中的prefab,显示了异常粉红,任何变种都没有生效
分析:shader_feature标记的shader单独依赖打包时,任何变种都不会打进去,分析原因估计是unity认为单包中shader没有被引用过
总结:unity5中新出的shader_feature可以只将引用过的shader变种打进包里面,听起来很有用,可是大部分项目中为了节省冗余shader的内存,shader都是作为依赖包单独成一包的,此时没有任何shader变种被打进包中;更何况即使shader没有依赖打包,如果计划代码中动态修改shader的变种而不是记录在材质里面,此时也不能用shader_feature。基本上我们的项目中shader_feature可以废弃了……
以上代码地址:https://github.com/liuxq/UnityForBlog
自己的开源游戏demo:https://github.com/liuxq/StriveGame
来自:http://bbs.591arvr.com/thread-1444-1-1.html
