1. 程式人生 > >筆記一 ——Unity Shader概念篇

筆記一 ——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入門精要》 馮樂樂

相關學習連結