0x00 寫在前面
之前一直在閱讀 The Book of Shaders 一書,為什麼會開始寫 Unity Shader 呢?一方面,因為該書目前尚未完結,寫下此文時已閱讀到該書的最新章節;另一方面,也需要通過一些實踐來檢驗以及鞏固所學的知識。Unity 引擎提供的環境正好是一個不錯的媒介。
本文沒有完整可執行的 Shader 程式碼,只是簡單梳理一下 Unity Shader 的基本結構,為之後學習做鋪墊。
0x01 基本框架
一段 Unity Shader 的基本結構如下:
// 路徑及名字
Shader "Path/ShaderName"
{
// 材質屬性
Properties
{
}
// 子著色器
SubShader
{
[Tags]
[RenderSetup]
Pass
{
Name "PassName"
[Tags]
[RenderSetup]
// ...
// 頂點/片元著色器
}
// 更多 Pass 可選
}
// 更多 SubShader 可選
Fallback "OtherShaderName" // 可選
}
0x02 名字
Path/ShaderName 決定該 Shader 在選擇皮膚上所處的位置,很像檔案的全路徑。例如,Shader "Path0/Path1/CustomShader" 將位於 Path0 → Path1 → CustomShader:
0x03 屬性
Properties 括號中定義的屬性將在材質皮膚上展示,可以方便地進行調節。
Properties
{
Name("Display Name", Type) = DefaultValue
// Other Properties
}
定義一個屬性,就像在 C 語言中定義一個變數一樣,只是格式稍有不同。在上面的程式碼中,Name 是屬性的名字,Display Name 是用於展示在材質皮膚上的名字,Type 是屬性的型別,DefaultValue 是屬性的預設值。ps:其中 Name 常常以下劃線開頭,例如 _PropertiesName。
下面列出一些常用的屬性:
數字
_Number0("Number 0", Range(-2.0, 2.0)) = 0.0
_Number1("Number 1", Float) = 0.618
_Number2("Number 2", Int) = 3
其中,Range 將在皮膚上展示一個滑動條,它的兩個引數依次表示可以滑動的最小值和最大值。Float 是浮點數,Int 是整數。
顏色和向量
_Color("Color", Color) = (1, 1, 1, 1)
_Vector("Vector", Vector) = (1, 1, 1, 1)
顏色 Color 的四個值分別表示 RGBA,在皮膚上將會展示修改按鈕以及吸取按鈕。向量 Vector 為四維向量,在皮膚上展示為 XYZW 四個格子。
紋理
_2DTexture("2D Texture", 2D) = "defaulttexture" {}
_Cube("Cube", Cube) = "defaulttexture" {}
_3DTexture("3D Texture", 3D) = "defaulttexture" {}
2D 表示 2D 紋理貼圖,Cube 表示立方體貼圖,3D 表示 3D 紋理貼圖。它們都有 Tiling 和 Offset。
在 Properties 中定義的屬性可以在 SubShader 的程式碼塊中使用,只需要在相應的地方定義與這些屬性名字相同、型別匹配的變數即可。SubShader 中的型別與 Properties 中的稍有不同,下面列出兩者對應的匹配關係:
- Color 和 Vector 可對應 float4、half4、fixed4
- Range 和 Float 可對應 float、half、fixed
- 2D 對應 sampler2D
- Cube 對應 samplerCUBE
- 3D 對應 sampler3D
SubShader 程式碼中,有的屬性也可以不在 Properties 中定義,兩者都可以在執行時通過 C# 指令碼動態地進行修改(如,使用方法 Material.SetFloat 修改浮點屬性)。定義在 Properties 中的屬性在材質皮膚上修改後可以被儲存。
0x04 SubShader
每個 Unity Shader 中都可以包含多個 SubShader,當 Unity 展示一個 Mesh 時,將會從這些 SubShader 中找到第一個可以執行的來執行。因為不同的硬體對 Shader 的支援不同,這裡可以理解為
0x05 Tags
Tags 是 kv 結構,用來指定 Shader 怎樣以及何時渲染物件。
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }
關於 Tags 的詳細說明可以檢視:ShaderLab: SubShader Tags。
0X06 RenderSetup
RenderSetup 用來設定顯示卡的渲染狀態。比如,設定剔除模式。
Cull Back | Front | Off
關於渲染狀態的詳細設定可以檢視:ShaderLab: Pass。
0x07 Pass
在 SubShader 中指定的 RenderSetup 將會應用到所有 Pass 中,我們也可以在 Pass 中單獨指定。Pass 中的 Tags 與 SubShader 中的有所不同,主要用於控制該 Pass 中的環境光、頂點照明等,詳見:ShaderLab: Pass Tags。
Pass
{
Name "PassName" // 可選
[Tags]
[RenderSetup]
// ...
// 頂點/片元著色器
}
當用 Name 為 Pass 指定名字後,可以在其它地方複用這個 Pass。其中,Pass 名需要大寫。用法如下:
UsePass "ShaderName/PASSNAME"
一個 SubShader 中可以有多個 Pass,這些 Pass 將會被依次執行。
0x08 Fallback
如果所有 SubShader 都無法使用時,將會嘗試使用 Fallback 指定的著色器。如:
Fallback "Diffuse"
0X09 總結
本文簡單梳理了一下 Unity Shader 的基本結構,因為了解不深,部分內容只能粗略帶過。這些內容在以後有需要時會再深入研究。
最後,總結一下 Unity Shader 的基本結構:
- 每個 Shader 都需要定義名字,格式為:Shader "Path/ShaderName" {}。
- 在 Shader 的括號中,如果有需要,我們可以在 Properties 語義塊中定義材質的屬性。
- 每個 Shader 都包含多個子著色器 SubShader。使用多個 SubShader 是因為不同的硬體對 Shader 的支援有所不同。當例項執行時,引擎會幫我們找到第一個可以執行的 SubShader 來執行。當所有的 SubShader 都不能執行時,則使用 Fallback 指定的 Shader。
- 每個 SubShader 包含多個 Pass,每個 Pass 表示一個渲染流程。因為有的效果需要幾個渲染才能表現出來,這時候就需要使用多個 Pass 了。
- SubShader 和 Pass 都可以通過一些語義 (Tags 和 RenderType) 來設定渲染狀態,Pass 還可以通過:Name "PassName" 來指定 Pass 的名字。
參考資料:
- [1] Shaders Overview
- [2] Unity Shader入門精要