前提:有個需求為切換頂點動畫樣式,例如有sin波動、cos波動、cos(θ+x)波動等等...
這時候你會怎么做?
是不是腳本中設置各種case,每個case加載對應的shader。這種方法可以用,但在實際開發中,根本不可取。因為可能會有大量Shader被編譯,造成內存的大量占用。那么該怎么做呢?
常用的兩種做法
1.使用multi_complie或shader_feature來定義宏,根據不同的宏指令編譯出多套Shader,Unity內建shader大體也是這么做的。
2.有外部傳入參數,在shader內部if判斷,選擇執行哪部分運算。
因為在shader種使用if、for很影響效率,所以第二種方法使用較少,用於case較少的時候。
接下來主要談一下第一種方法的利與弊。
使用multi_complie
該指令可以達到上述效果,但他會無腦的進行組合編譯,如果宏指令太多,會產生非常多的variant。
當你使用
#pragma multi_compile Red Green Blue
會產生三個variant,因為你定義了三個宏
當你使用
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
會產生6個variant(RedPink,RedYellow,GreenPink,GreenYellow,BluePink,BlueYellow),因為他們之間會兩兩組合。
當你使用
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
#pragma multi_complie Brown Purple Black
會產生323個variant。可見這個產生變體的數量規模是很大的。
如何查看一個shader產生的變體數量?
選中shader文件,點擊compile and show code右邊的小箭頭就可以看到。
優勢也很明顯:
打ab包的時候,Unity會把shader中所有的multi_complie定義的變體全部打包,shader可以正常顯示。但shader_feature就不那么盡人意了。
需要注意:
其中指定的第一個關鍵字是默認生效的。
使用multi_compile聲明的都為全局關鍵字,一個項目種全局關鍵字最多只能有256個,內建shder大約已經用了60個。
注意FallBack最好不要FallBack內建shader,也會增加很多變體。
使用shader_feature
該指令的效果和用法基本都與上面的一樣。同時它就是為了multi_compile打包時的爆炸編譯的問題。
實現:
shader代碼
Shader "Custom/NewSurfaceShader" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
pass
{
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
//#pragma surface surf Standard fullforwardshadows
#include "UnityCG.cginc"
//#pragma shader_feature Red Green Blue
#pragma multi_complie_local __ Red Green Blue
//#pragma multi_compile __ Pink */
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 2.0
#pragma vertex vert
#pragma fragment frag
struct v2f
{
float4 vertex:POSITION;
fixed4 color:COLOR;
};
v2f vert(appdata_base i)
{
v2f o;
o.vertex=UnityObjectToClipPos(i.vertex);
o.color=fixed4(1,1,1,1);
return o;
}
fixed4 frag(v2f i):SV_Target
{
#if Red
return i.color=fixed4(1,0,0,1);
#elif Blue
return i.color=fixed4(0,0,1,1);
#else
return i.color=fixed4(0,1,0,1);
#endif
}
ENDCG
}
}
//FallBack "Diffuse"
}
C#代碼進行外部控制:
/*
author:@
Last modified data:
funtion todo:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shader_featureTest : MonoBehaviour {
public enum ColorMode
{
Red,
Blue,
Green
}
public ColorMode colorMode = ColorMode.Green;
Material mt;
// Use this for initialization
void Start () {
mt = GetComponent<MeshRenderer>().material;
}
// Update is called once per frame
void Update () {
print(colorMode.ToString());
switch (colorMode)
{
case ColorMode.Red:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
case ColorMode.Blue:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
case ColorMode.Green:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
default:
break;
}
}
}
把材質賦予隨便一個物體上,運行時切換就可以達到效果。在multi_complie下使用EnableKeywords和DisableKeywords有事會失敗,這里建議直接使用shaderKeywords屬性來設置(希望有大神可以告訴為什么那兩個方法會失敗,很是疑惑
看到這么一條,還沒有嘗試:
在腳本這么用
Material.EnableKeyword 和 DisableKeyword
Shader.EnableKeyword 和 DisableKeyword 控制keyword起效
注意:
5.4中用Shader.EnableKeyword設置了全局使用默認的key, 用Material.EnableKeywor設置單個不使用默認值無效
用Shader.EnableKeyword設置了全局不使用默認的key, 用Material.EnableKeywor設置單個使用默認值起效
EnableKeyword 和 DisableKeyword 最好組合使用 比如一組有三個,必須寫1個enable2個disable
不管有多少個關鍵字,總會產生一個variant,大大降低了編譯出來的shader變體。
但是打ab包的時候,如果shader和所使用的材質不在一個ab包內,那么材質的shader中所有的變體都不會進入ab包中。在打包后代碼中動態切換keywords,要把使用到的shader變體也打入包中才可以。
為了讓變體在運行時可以更好的處理,Unity5.x之后引入了一個shader變體集合:ShaderVariantCollection,ShaderVariantCollection的使用之后會講,今天簡單給大家介紹一下這兩種方式。