1. 程式人生 > >C for Graphic:語言(三)

C for Graphic:語言(三)

       之前我們通過觀察一個最簡單的CG shader,詳細的學習了CG shader關鍵程式碼的執行機制,這一篇我們就要通過unity引擎的shader上下文環境,觀察並理解其他細節程式碼的作用。

        首先貼上CG shader程式碼,這一篇主要圍繞這個程式碼進行學習,如下:

Shader "Unlit/TextureUnlitShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

         上面的CG shader只完成一個簡單的圖片渲染顯示功能,但是細節很值得學習,接下來我就逐一講解結合unity CG上下文的shader每一句話都是什麼意思。

         首先說一下,nvidia是CG的締造者,其他各個三維引擎或者工具都必須按照CG的基本規範去實現自己的CG編譯器以及類似程式設計語法,比如我最愛的unity3d和rendermonkey(我不愛ue4的原因是ue4實在是太慢了,儲存umap慢,編譯構建慢等,之前用ue4的日子旁邊都是放了一本書,凡是ue4在那操作阻塞著,我就看書玩,實在愛不起來)。那麼我們現在用unity寫CG shader,就得按照unity的那一套語法編譯規範寫程式碼了,上面的程式碼就是一個很標準的語法對照了,只要我們完全理解程式碼的每個詞彙,就達到目標了。

         so,我們開始咬文嚼字吧:

         ①.Shader{}包體。

Shader "Unlit/TextureUnlitShader"
{
     //do something
}

     類似c++或c#的class,將具體的shader程式碼封裝,Shader讓CG編譯器識別這是一個shader程式碼的起始標籤,“Unlit/TextureUnlitShader”就是shader名稱的標識。

        ②.Properties{}屬性欄位

Properties
{
	_MainTex ("Texture", 2D) = "white" {}
}

         類似c++的public:修飾字段,如下:

class Texture
{
	//這是一個貼圖類
};

class MainTest
{
public:
	float cg_float;
	Texture* cg_MainTex;

};

        這是一段模擬程式碼,和CG程式碼沒有任何關聯,我為什麼用c++/c#作為演示程式碼呢,首先就是我們大概接觸最早的就是c/c++了,因為學校裡面最開始學習就是從c開始,其次我們作為unity開發,c#當然也是很熟悉啦。最後就算我用很詳細的文字講解程式碼意義,有的時候還是不如來一段演示程式碼讓人更能理解其中的含義。

          Properties{}就類似Public:,在其中定義我們需要的屬性欄位,可以供外部訪問,比如我們定義一張texture2D。

          _MainTex ("Texture", 2D) = "white" {},此時unity CG編譯器在識別出Properties後,緊接著解析其中封裝的屬性欄位,當解析到這段程式碼後,其中程式碼意義分解如下:

                 ①._MainTex標識了該texture2D在shader中的欄位名稱。

                 ②.("Texture", 2D)標識了該texture2D在unity 編輯面板中ui顯示字串"Texture",2D則標識型別為texture2D。

                 ③.= "white" {}標識了該texture2D預設為白色,當然我們可以改成red紅色。

         ps:②中我說了unity編輯面板ui顯示,這裡可以通過官網下載的內建著色器中editor中的StandardShaderGUI.cs檔案檢視具體顯示細節。

          當然,Properties不僅僅只能定義一個texture2D,還能定義其他的,比如_MainFloat("Float",float) = 0.5,定義一個float值,當然還有其它的,我們以後用到的時候再看。

     ③.SubShader{}包體

SubShader
{
    //do something
}

        官方對SubShader的解釋是封裝一系列渲染pass和所有pass通用的標籤和狀態的包體,一個主Shader可以包含多個SubShader,為什麼主Shader可以包含多個SubShader的原因是,前面我們說了Profile的概念,也就是說因為實際GPU硬體種類太多,為了保證該主Shader可以在多種多樣的GPU上執行,所以可能需要寫多個可以執行的SubShader,然後實際runtime執行時按照順序查詢到一個能執行SubShader去執行相應的渲染效果,假如都不能執行,就Fallback "Diffuse",意思就是回滾到最基本的Diffuse渲染,保證至少不出問題。

     ④.SubShader中標籤和狀態

Tags { "RenderType"="Opaque" }
LOD 100
Cull back

     這些標籤和狀態鍵值對,被CG編譯器識別,主要作用是標識後續渲染pass該什麼時候並且怎麼樣被CG runtime渲染。

     比如上面三句程式碼含義如下:

             ①.Tags { "RenderType"="Opaque" } 標識了渲染型別為Opaque,什麼意思呢?就是代表這個渲染型別定義為不透明著色器,unity CG庫提供了多種適用於不同環境的渲染型別,具體的後面會談到。

              ②.LOD 100,LOD全稱level of detail,,設定一個100是個什麼意思呢?這裡代表一種shader渲染細節技術,假如我們的主Shader包含多個SubShader,每個SubShader的LOD 為不同的值,假如為100、200、300,那麼我們在c#程式碼中通過Shader.globalMaximumLOD = 100;控制全域性LOD最大值,來顯示不同LOD shader的渲染。有興趣的小夥伴可以自己測試下,我就不貼圖了。

             ③.Cull back,Cull也是渲染剔除的意思,設定back代表背面剔除渲染,那樣的話實際上我們的shader只渲染了一個正面,節省一半的資源,當然我們也可以設定front剔除正面,或者off關閉,直接渲染雙面。

             ps:當然還有其他一些不同功能的tag和state,後面具體是用到再講還是直接一次性列出待定,不過這裡我們明白這些tag和state的語法意義就行了。

 ⑤.Pass{}渲染通道

      從這裡開始就是正式開始進入CG渲染了,Pass{}封裝當前SubShader著色器具體的渲染邏輯,我們的頂點函式,片段函式,表面光照函式就是在其中具體實現,下面開始逐一分解每一句程式碼的具體意義:

           ①.CGPROGRAM開始和ENDCG結束,這個只是CG編譯器的語法規範之一,代表開始編譯或解析CG程式碼到結束,類似#region #endregion程式碼塊。

           ②.#pragma vertex vert和#pragma fragment frag(當然還包含#pragma surface surf YangLightModel)這裡就是預編譯頂點函式和片段函式(表面光照函式),代表後面的vert和frag為我們要去具體開發實現的頂點和片段函式。

           ③.#include "UnityCG.cginc"和c包含標頭檔案一樣,這裡引入了UnityCG.cginc這個檔案(這個檔案可以在下載的內建著色器CGIncludes資料夾中找到),因為我們需要用到unity CG runtime給我們提供的GPU硬體資源資料和各種圖形處理函式,都在這個檔案可以看到,這裡我也不貼程式碼了,小夥伴們務必自己開啟看一下。

            ④.appdata和v2f結構體,如果小夥伴們是按照順序看我的部落格,那麼對這兩個結構體及其包含的語義繫結欄位有詳細的理解。額外要說的另外一個語義TEXCOORD0,前面的TEXCOORD代表了紋理座標,0則代表了第0通道紋理座標,大部分情況下顯示卡支援4套紋理通道給我們開發使用,語義分別是TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,我們可以對紋理座標進行位移操作並儲存到任意0-3的TEXCOORD。SV_POSITION則等價於POSITION,SV_字首代表system value,我查到的解釋是,當使用SV_POSITION繫結vertex頂點函式輸出時,那麼頂點座標在vertex頂點函式return後就被固定了,直接進入光柵處理階段,不可更改。

                by the way,順便畫圖描敘一下TEXCOORD作為紋理座標語義輸入時的uv:貼圖座標系,如下圖:

                

                可以認為,小貓貼圖的uv就是從(0,0)到(1,1),(m,n)為其中一個取樣顏色color值,繫結TEXCOORD語義輸入時,float2 uv值就被傳入(0,0)插值到(1,1)。

           ⑤.sampler2D _MainTex;,這句程式碼要和Properties{}對應,是定義獲取_MainTex這個texture2D物件,當然CG程式碼中型別名為sampler2D,定義好後sampler2D物件後,提供後續函式使用。

           ⑥.float4 _MainTex_ST;,這句程式碼可能讓小夥伴們迷惑,咱們又沒在Properties{}中定義_MainTex_ST這個外部變數,怎麼突然就蹦出來了這個東西?實際上這是和_MainTex相關的變數,代表了_MainTex的Scale和Translate,這是我從官方看到的解釋,Scale縮放則對應unity編輯器ui面板上的Tiling,Translate位移則對應其Offset,屬於unity CG編譯器為我們提供的texture2D預設附帶的變數欄位。

           ⑦.vert頂點函式,我直接在程式碼中進行註釋講解 ,如下:

v2f vert (appdata v)
{
	v2f o;
	//將unity CG底層提供的模型頂點座標源資料變換到裁剪空間
	//UnityObjectToClipPos這個cg內建函式相當於mul(UNITY_MATRIX_MVP,v.vertex);
	o.vertex = UnityObjectToClipPos(v.vertex);
	// Transforms 2D UV by scale/bias property
    // #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
	//上面是UnityCG.cginc的TRANSFORM_TEX巨集函式定義,實際上使用到了_MainTex_ST欄位,意思就是將_MainTex_ST帶入紋理uv的運算
	o.uv = TRANSFORM_TEX(v.uv, _MainTex);
	return o;
}

            ⑧.frag片段函式,解釋如下:

fixed4 frag (v2f i) : SV_Target
{
	//tex2D為取樣函式,具體意思就是根據uv值對一張紋理進行逐點顏色color值獲取,並在片段函式返回並渲染
	fixed4 col = tex2D(_MainTex, i.uv);
	return col;
}

           上面那張小貓圖,其中動點(m,n)進行tex2D取樣的話,就是返回了不同座標下小貓圖的畫素顏色值,並傳遞給col且return渲染。

          ⑨.Fallback "Diffuse",這就是unity CG編譯器就行的容錯處理了,假如我們的CG shader執行在一個很低端古老的的GPU而顯示不出來,就回滾為Diffuse著色器,反正所有CPU都支援這個。

       以上就是對一個unity CG shader非常詳細的講解,目的就是為了大家能輕鬆理解後續的複雜的高階著色器。

       最後還要重複羅嗦一句,學習CG shader必須按照圖形流水線上下文來觀察理解,比如流水線頂點到光柵到片段處理順序流程,同時頂點函式和片段函式處理的輸入輸出的GPU硬體資源資料是動態變化的,比如模型頂點源座標和紋理座標等動態變化的資料,我們心裡要有這些概念才能學好CG shader。

       so,接下來繼續深入。