簡介
Unity Shader爲控制渲染過程提供了一層抽象。如果沒有它,開發者需要和很多文件設置打交道,才能讓畫面呈現出想要的效果;而在Unity Shader的幫助下,開發者只需要使用ShaderLab來編寫Unity Shader文件就可以完成所有工作。
在Unity中,所有的Unity Shader都是使用ShaderLab來編寫的。ShaderLab是Unity提供的編寫Unity Shader的一種說明性語言。
一個Unity Shader的基礎結構如下:
Shader "Shader Name"
{
Properties
{
//屬性
}
SubShader
{
//顯卡A使用的子着色器
}
SubShader
{
//顯卡B使用的子着色器
}
FallBack "VertexLit"
}
Unity在背後會根據使用的平臺來把這些結構編譯成真正的代碼和Shader文件,而開發者只需要和Unity Shader打交道即可。
結構
Shader命名
Shader "Custom/MyShader" {}
如上定義了一個名叫“MyShader”且在“Custom”路徑下的UnityShader。路徑名用“/”分隔。
材質面板屬性定義
Properties
{
Name("顯示名字", 屬性類型) = 默認值
//....
}
屬性定義模塊是包含在Shader命名模塊下的。
支持的屬性類型
屬性類型 | 默認值定義語法 | 例子 |
---|---|---|
Int | number | _Int(“Int”, Int) = 2 |
Float | number | _Float(“Float”, Float) = 1.5 |
Range(min,max) | number | _Range(“Range”,Range(0.0, 5.0)) = 3.0 |
Color | (number,number,number,number) | _Color(“Color”, Color) = (1,1,1,1) |
Vector | (number,number,number,number) | _Vector(“Vector”, Vector) = (2,3,6,1) |
2D | “defaulttexture”{} | _2D(“2D”, 2D) = “”{} |
Cube | “defaulttexture”{} | _Cube(“Cube”, Cube) = “white”{} |
3D | “defaulttexture”{} | _3D(“3D”, 3D) = “black”{} |
範例:
Shader "Custom/Shader"
{
Properties
{
_Int("Int", Int) = 0
_Float("Float", Float) = 0.0
_Range("Range", Range(0,1)) = 0.0
_Color("Color", Color) = (1,1,1,1)
_Vector("Vector", Vector) = (0,0,0,0)
_2D("2D", 2D) = ""{}
_Cube("Cube", Cube) = "white"{}
_3D("3D", 3D) = "black"{}
}
Fallback "Diffuse"
}
屬性面板顯示如下:
Unity允許重載默認的材質編輯面板,以提供更多自定義數據類型。
即使不在Properties語義塊中聲明這些屬性,也可以在Cg代碼片中定義變量。
SubShader
SubShader
{
//可選
[Tags]
//可選
[RenderSetup]
Pass
{
}
//其他Pass
}
每一個Unity Shader文件可以包含多個SubShader語義塊,但最少要有一個。當Unity需要加載這個Unity Shader時,Unity會掃描所有的SubShader語義塊,然後選擇第一個能夠在目標平臺上運行的SubShader。如果不支持的話,Unity就會使用Fallback語義指定的Unity Shader。
SubShader中定義了一系列Pass以及可選的狀態([RenderSetup])和標籤([Tags])設置。每個Pass定義了一個完整的渲染流程,但如果Pass的數目過多,往往會造成渲染性能的下降。所以應儘量使用最小數目的Pass。
狀態設置
常見的渲染狀態設置選項
狀態名稱 | 設置指令 | 解釋 |
---|---|---|
Cull | Cull Back/Font/Off | 設置剔除模式:剔除背面/正面/關閉剔除 |
ZTest | ZTest Less Greater/LEqual/GEqual/Equal/NotEqual/Always | 設置深度測試時使用的函數 |
ZWrite | ZWrite On/Off | 開啓/關閉深度寫入 |
Blend | Blend SrcFactor DstFactor | 開啓並設置混合模式 |
當在SubShader塊中設置了上述渲染狀態時,將會應用到所有的Pass。如果不想這樣,可以單獨在Pass塊中設置。
SubShader的標籤
Tags{"TagName1" = "Value1" "TagName2" = "Value2"}
SubShader的標籤([Tags])是一個鍵值對(Key/Value Pair),它的鍵和值都是字符串類型。
這些鍵值對是SubShader和渲染引擎之間的溝通橋樑。用來告訴Unity的渲染引擎希望怎樣以及如何渲染這個對象。
SubShader的標籤塊支持的標籤類型如下:
標籤類型 | 說明 | 例子 |
---|---|---|
Queue | 控制渲染順序,指定該物體屬於哪一個渲染隊列,通過這種方式可以保證所有的透明物體可以在所有不透明物體後面被渲染,也可以自定義渲染隊列來控制物體的渲染順序 | Tags{“Queue” = “Transparent”} |
RenderType | 對着色器進行分類,例如這是一個不透明的着色器,或是一個透明的着色器等,這可以被用於着色器替換(Shader replacament)功能 | Tages{“RenderType” = “Opaque”} |
DisableBatching | 一些SubShader在使用Unity的批處理功能時會出現問題,例如使用了模型空間下的座標進行頂點動畫。這時可以通過該標籤來直接指明是否對該SubShader使用批處理 | Tags{“DisableBatching” = “True”} |
ForceNoShadowCasting | 控制使用該SubShader的物體是否會投射陰影 | Tags{“ForceNoShadowCasting” = “True”} |
IgnoreProjector | 控制使用該SubShader的物體是否受Projector的影響 | Tags{“IgnoreProjector” = “True”} |
CanUseSpriteAtlas | 當該SubShader是用於精靈(Sprite)時,將該標籤設置爲“False” | Tags{“CanUseSpriteAtlas” = “False”} |
PreviewType | 指明材質面板將如何預覽該材質。默認情況下,材質將顯示爲一個球形,可以設置爲“Plane”或“SkyBox”來改變預覽類型 | Tags{“PreviewType” = “Plane”} |
上述標籤僅可以在SubShader中聲明,而不可以在Pass塊中聲明。Pass塊雖然也可以定義標籤,但這些標籤是不同於SubShader的標籤類型。
Pass語義塊
Pass
{
[Name]
[Tags]
[RenderSetup]
//其他代碼
}
在Pass中定義該Pass的名稱,如下:
Name "MyPassName"
通過這個名稱,可以是使用ShaderLab的UsePass命令來直接使用其他Unity Shader中的Pass。例如:
UsePass "MyShader/MYPASSNAME"
這樣可以提高代碼的複用性。需要注意的是,Unity內部會把所有Pass的名稱轉換成大寫字母表示,因此,在使用UsePass命令時必須使用大寫形式的名字。
Pass也可以設置渲染狀態。SubShader的狀態設置同樣適用與Pass。
Pass也可以設置標籤,但它的標籤不同於SubShader的標籤,這些標籤也是告訴渲染引擎應該怎樣來渲染物體。
Pass中使用的標籤類型如下:
標籤類型 | 說明 | 例子 |
---|---|---|
LightMode | 定義該Pass在Unity的渲染流水線中的角色 | Tags{“LightMode” = “ForwardBase”} |
RequireOptions | 用於定義當滿足某些條件時才能渲染該Pass,它的值是一個由空格分隔的字符串。目前,Unity支持的選項有SoftVegetation。 | Tags{“RequireOptions” = “SoftVegetation”} |
除了上面普通的Pass定義外,Unity Shader還支持一些特殊的Pass,以便進行代碼複用或實現更復雜的效果。
- UsePass:該命令用來複用其他Unity Shader中的Pass
- GrabPass:該Pass負責抓取屏幕並將結果存儲在一張紋理中,以用於後續的Pass處理。
Fallback
Fallback "ShaderName"
//或
Fallback Off
緊跟在各個SubShader語義塊後面,用於告訴Unity,當上面所有的SubShader在這塊顯卡上都不能運行時,就用Fallback指定的這個Shader。
Fallback會影響到陰影的投射,因此正確設置是非常重要的。
Unity Shader的形式
必要的結構如下:
Shader "MyShader"
{
Properties
{
//所需的各種屬性
}
SubShader
{
//真正意義上的Shader代碼會出現在這裏
//表面着色器(Surface Shader)或者
//頂點/片元着色器(Vertex/Fragment Shader)或者
//固定函數着色器(Fixed Function Shader)
}
SubShader
{
//和上一個SubShader類似
}
}
表面着色器
表面着色器是Unity自己創造的一種着色器代碼類型。它需要的代碼少,Unity在背後做了很多工作,但渲染的代價比較大。
在背後Unity任然把它轉換成對應的頂點/片元着色器。可以理解成,表面着色器是Unity對頂點/片元着色器的更高一層的抽象。它存在的價值在於,Unity爲我們處理了很多光照細節,使我們不需要在操行這些。
簡單範例:
Shader "Custom/Simple Surface Shader"
{
SubShader
{
Tags {"RenderType" = "Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
float4 color : Color;
};
void Surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = 1;
}
ENDCG
}
Fallback "Diffuse"
}
表面着色器被定義在SubShader語義塊(而非Pass語義塊)中CGPROGRAM和ENDCG之間。
表面着色器不需要關心使用多少個Pass、每個Pass如何渲染等問題,Unity會在背後處理好。
CGPROGRAM和ENDCG之間的代碼使用Cg/HLSL編寫的,語法幾乎和標準的一樣,但有些原生的函數並沒有提供支持。
頂點/片元着色器
在Unity中可以使用Cg/HLSL語言來編寫頂點/片元着色器,它們更加複雜,但更加靈活。
簡單範例如下:
Shader "Custom/Simple VertexFragment Shader"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert (float4 v : POSITION) : SV_POSITION
{
return mul (UNITY_MATRIX_MVP, v);
}
fixed4 frag () : SV_Target
{
return fixed4 (1.0, 0.0, 0.0, 1.0);
}
ENDCG
}
}
}
頂點/片元着色器是寫在Pass語義內的,而非SubShader內。
固定函數着色器
上面兩種都使用的可編程管線,但對於一些老舊設備,它們並不支持可編程管線着色器,因此就要使用到固定函數着色器,但往往只可以完成一些非常簡單的效果。
簡單範例如下:
Shader "Custom/Basic"
{
Properties
{
_Color ("Main Color", Color) = (1, 0.5, 0.5, 1)
}
SubShader
{
Pass
{
Material
{
Diffuse [_Color]
}
Lighting On
}
}
}
對於它來說,需要完全使用ShaderLab的語法來編寫,而非使用Cg/HLSL。
其他
Unity Shader不是真正的Shader
在Unity Shader中,可以做的事情遠多於一個傳統意義上的Shader。
傳統Shader | Unity Shader |
---|---|
僅可編寫特定類型的Shader,例如:頂點着色器、片元着色器等 | 可以在同一個文件中同時包含頂點着色器和片元着色器 |
無法設置一些渲染設置,例如:是否開啓混合、深度測試等。這些是開發者在另外的代碼中設置的 | 通過一行特定的指令就可以完成這些設置 |
需要編寫冗長的代碼來設置着色器的輸入和輸出,要小心的處理這些輸入和輸出的位置對應關係等 | 只需要在特定語句塊中聲明一些屬性,就可以依靠改變材質來方便的改變這些屬性。而且對於模型自帶的數據(如頂點位置、紋理座標、法線等),Unity Shader也提供了直接訪問的方法,不需要開發者自行編碼來傳給着色器 |
高度封裝,可編寫的Shader類型和語法被限制 | |
對於一些類型的Shader,如曲面細分着色器、幾何着色器等的支持相對較差 | |
一些高級的Shader語法不支持 |
Unity Shader和Cg/HLSL之間的關係
通常,Cg的代碼片段是位於Pass語義內部的,如下:
Pass
{
//Pass的標籤和狀態設置
CGPROGRAM
//編譯指令,例如
#pragma vertex vert
#pragma fragment frag
//Cg代碼
ENDCG
//其他一些設置
}
從本質上講Unity中只存在頂點/片元着色器,表面着色器和固定函數着色器會在背後轉化爲頂點/片元着色器。