筆記一 ——Unity Shader概念篇
學習教材:《UnityShader入門精要》——馮樂樂
部分計算圖例為《UnityShader入門精要》書中截圖
程式碼和例項截圖均為實際操作執行結果
渲染流水線
渲染流水線從概念部分分為三個部分:
應用階段
應用階段為開發者完全控制部分,主要提供渲染所需要的渲染資料,輸出為渲染圖元,該階段可以細分為:
- 載入渲染資料(HDD-->RAM-->VRAM)
- 設定渲染狀態(決定場景中的網格(圖元)以怎樣的方式渲染,使用什麼著色器,光照,材質)
- 呼叫DrawCall命令(指定需要渲染的圖元列表,發起方為CPU,接收方為GPU)
幾何階段
幾何階段的部分過程可以由開發者控制和配置,幾何階段主要將接收到的圖元資訊進行逐頂點,逐多邊形操作,將頂點座標變換到螢幕空間,同時記錄頂點的光照,深度,著色資訊。該階段可以細分為:
- 頂點著色器(該階段可程式設計階段,將頂點座標從模型空間轉換到齊次裁剪空間,頂點著色,紋理座標輸出也在該過程完成)
- 曲面細分著色器(可選著色器,細分圖元)
- 幾何著色器(可選著色器,進行逐圖元操作,或者被用於產生更多圖元)
- 裁剪(將不在攝像機視野中的頂點裁剪掉)
- 螢幕對映(將圖元的頂點轉換到螢幕空間座標系,該過程不可程式設計或配置)
光柵化階段
該階段接收上一階段的頂點資訊,並對頂點所圍成的網格覆蓋的畫素進行逐畫素的處理,輸出最終的渲染影象,該過程可以細分為:
- 三角形設定(該階段將得到的頂點進行計算,得到頂點圍成的三角網格的邊上的畫素座標)
- 三角形遍歷(根據上一步的計算結果,判斷哪些畫素點在網格內,並對覆蓋的畫素點進行插值,這裡由於每個畫素點上除了顏色資訊,還包括光照,深度,紋理座標等資訊,將帶有資訊的畫素點成為片元,這一過程的輸出為片元序列)
- 片元著色器(該階段為可程式設計著色階段,輸入為上一階段的頂點資訊的插值結果,許多較為重要的渲染技術在該階段完成,如紋理取樣,該階段若要進行紋理取樣,那麼在頂點著色器階段輸出每個頂點對應的紋理座標)
- 逐片元操作(該過程為渲染流水線的最後階段,也被稱作合併輸出階段,主要是對每個片元進行模板,深度測試,混合操作等,通過測試的可以選擇是否與顏色緩衝區的顏色進行混合,從而決定片元的可見度以及最終的顏色)
對應流程圖(書中截圖):
shader是渲染流水線中的一部分可高度程式設計的階段,Unity中的Shader主要對 幾何階段的頂點著色器和光柵化階段的片元著色器進行程式設計操作。
Shader Lab
在Unity中,所有的Unity Shader都是使用ShaderLab來編寫的。Shader Lab是Unity提供編寫Unity Shader的一種說明性語言,使用巢狀在花括號內部的語義來描述Unity Shader檔案的結構。
一個Unity Shader的基礎結構:
Shader "ShaderName"{
properties{
//屬性
}
SubShader{
//顯示卡A使用的子著色器
}
SubShader{
//顯示卡B使用的子著色器
}
FallBack "..."
}
Unity Shader和通用的Shader不太一樣,Unity在背後根據使用的平臺將這些結構編譯成正真意義上的Shader程式碼和檔案,Unity開發者不必太關心底層的渲染,只用使用Unity Shader Lab即可。
Unity Shader結構及語義
Properties Properties語義塊中包括一系列的屬性(Property), 這些屬性會出現在材質面板中,Properties語義塊定義:
Properties{
_name("display name",PropertyType)=DefaultValue
_name("display name",PropertyType)=DefaultValue
//更多屬性
}
屬性的宣告可以使我們很方便的在材質面板中看到這些屬性,並對這些屬性進行調節,display name是該屬性在材質面板中的顯示的名字,若要在後續的CG程式碼中使用這些屬性,則是通過**_name**進行訪問,每種屬性在宣告時,需要指定屬性的型別,並給附上預設值。完整的屬性及型別和預設值賦值方式為:
Sahder "ShaderLabProperties"{
Properties{
_Int("Int",Int)=2
_Float("Float",Float)=1.5
_Range("Range",Range(0.0,5.0))=3.0
//
_Color("Color",Color)=(1,1,1,1)
_Vector("Vector",Vector)=(2,3,6,1)
//
_2D("2D",2D)=""{}
_Cube("Cube",Cube)="white"{}
_3D("3D",3D)="black"{}
}
}
值得注意的是,在Shader Lab的語義塊中,每行語句結尾是沒有;的,對於2D,3D,Cube這3種紋理型別,預設值的定義通過一個字串後跟一對{}來完成,其中,字串要麼為空,要麼為內建紋理名稱,如"gray","red","bump"等。Properties語義塊的作用只是將定義的屬性顯示到材質面板中, 後續的Shader程式碼中若要訪問這些屬性,需要在CG程式碼片段中定義和這些屬性相匹配的變數。
SubShader
每一個Unity Shader檔案可以包含1個或多個SubShader 語義塊,這是由於不同的顯示卡具有不同的渲染能力,多個 SubShader 對應著多個顯示卡,這樣在不同能力的顯示卡上進行不同複雜度的渲染計算。
SubShader語義塊通常結構如下:
SubShader{
//可選
[Tags]
//可選
[RenderSetup]
Pass{
}
//Other Pass
}
需要注意的地方:
- SubShader包括一系列Pass,可選的Tags,可選的RenderSetup,每一個Pass為一次完整的渲染流程
- Tags為可選的,可以在SubShader和Pass內宣告,SubShader內宣告的Tags是特定的
- RenderSetup為可選的,可以在SubShader和Pass內宣告,非特定的,即在SubShader和Pass內可通用
- Tags和RenderSetup若在SubShader中進行了設定,會應用到所有的Pass中去
Tags是一個鍵值對,其結構為:
Tags{"TagsName1"="Value1" "TagsName2"="Value2"}
Pass語義塊
Pass語義塊為SubShader的一部分,語義結構如下:
Pass{
[Name]
[Tags]
[RenderSetup]
//Other Code
}
在Pass語義塊中,可以定義該段Pass的名字,如:
[FirstPass]
定義了Pass的名字後,可以通過
UsePass "FirstShader/FIRSTPASS"
來使用其他Unity Shader的Pass,提高複用性。由於Unity Shader的內部會將所有的Pass的名稱轉換成大寫形式,因此在使用UsePass時,使用大寫的Pass名稱。
Pass語義塊中,可以設定Tags,與SubShader中的不同,Pass中可用的Tags有LightMode,RequireOptions
Pass語義塊中,可以設定RenderSetup,與SubShader中的設定通用,應用於當前的Pass
Unity Shader支援一些特殊的Pass,以實現程式碼複用和更為複雜的功能,如:
- **UsePass:**使用該命令複用其他Unity Shader的Pass
- **GrabPass:**該Pass負責抓取螢幕將結果儲存在一張紋理當中,用於後續Pass的處理
Fallback
在最後一個SubShader的語義塊後面,有一個Fallback指令,如果所有的SubShader都不能被當前顯示卡執行,那就使用Fallback指定的Shader,如:
Fallback "VertexLit"
在Unity Shader中,真正意義上的Shader程式碼都會寫在SubShader語義塊中:
Shader "FirstShader"{
Properties{
//所需各種屬性
}
SubShader{
//真正意義上的Shader程式碼會寫在這部分中
//表面著色器(Surface Shader)或者
//頂點/片元著色器(Vertex/Fragment Shader)或者
//固定函式著色器(Fixed Function Shader)
}
SubShader{
}
}
相關參考
《UnityShader入門精要》 馮樂樂