1. 程式人生 > >《Unity Shader入門精要》總結 #第五章 開始Unity Shader學習

《Unity Shader入門精要》總結 #第五章 開始Unity Shader學習

1、最簡單的頂點/片元著色器

1.1 頂點/片元的基本結構

包含Shader、Properties、SubShader、Fallback等語義塊

Shader "MyShaderName"{
    Properties{
        //屬性
    }
    SubShader{
        //針對顯示卡A的SubShader
        Pass{
            //設定渲染狀態和標籤

            //開始CG程式碼片段
            CGPROGRAM
            //該程式碼片段的編譯指令
            #pragma vertex vert
            #pragma fragment frag

            //CG程式碼寫在這裡
            ENDCG
            //其他設定
        }
        //其他需要的Pass
    }
    SubShader{
        //針對顯示卡B的SubShader
    }

    //上述SubShader都失敗後用於回撥的Unity Shader
    Fallback "VertexLit"
}

最重要的是Pass語義塊

Shader "Unity Shaders Book/Chapter5/SimpleShader" {
	SubShader{
		Pass{
			CGPROGRAM
            //以下兩條告訴Unity哪個函式包含頂點著色器程式碼,哪個函式包含片元著色器程式碼
            //#pragma vertex name
            //#pragma fragment name
			#pragma vertex vert
			#pragma fragment frag

            //v包含頂點位置,通過POSITION語義定義,返回值float4,POSITION和SV_POSITION均為                    
            //CG/HLSL語義。如POSTION告訴Unity將模型頂點座標填充到引數v中,SV_POSITION告訴         
            //Unity頂點著色器的輸出是裁剪空間的頂點座標
			float4 vert(float4 v:POSITION) : SV_POSITION{
				return UnityObjectToClipPos (v);
			}

            //SV_Target是HLSL的語義,告訴渲染器使用者的輸出顏色儲存到一個渲染目標中,這裡輸出到 
            //預設的幀快取中,返回一個表示白色的fixed4型別。
			fixed4 frag() : SV_TARGET{
				return fixed4(1.0,1.0,1.0,1.0);
			}

			ENDCG
		}
	}
}

1.2 模型資料來源

上面例子中POSITION得到了模型頂點位置,當需要模型上每個頂點的紋理座標和法線方向時。紋理座標訪問紋理,法線用於計算光照。這次新的引數將為一個結構體。

Shader "Unity Shaders Book/Chapter5/SimpleShader" {
	SubShader{
		Pass{
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
            //使用結構體來定義頂點著色器的輸入
			struct a2v{
                //POSITION告訴Unity,用模型空間頂點座標填充vertex變數
				float4 vertex : POSITION;

                //NORMAL告訴Unity,用模型空間的法線方向填充normal變數
				float3 normal : NORMAL;

                //TEXCOORD0語義告訴Unity,用模型的第一套紋理座標填充texcoord變數
				float4 texcoord : TEXCOORD0;
			};
			float4 vert(a2v v) : SV_POSITION{
                //使用v.vertex訪問模型空間頂點座標
				return UnityObjectToClipPos (v.vertex);
			}
			fixed4 frag() : SV_TARGET{
				return fixed4(1.0,1.0,1.0,1.0);
			}

			ENDCG
		}
	}
}

對於頂點著色器的輸出,語義有:POSITION、TANGENT、NORMAL、TEXCOORD0、TEXCOORD1、TEXCOORD2、TEXCOORD3、COLOR,在Unity中由使用該材質的Mesh Render組建提供,在每幀Draw Call呼叫時傳送。

1.3 頂點著色器和片元著色器之間如何通訊

Shader "Unity Shaders Book/Chapter5/SimpleShader" {
	SubShader{
		Pass{
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
    
            //結構體定義頂點著色器輸出
			struct v2f{
                //SV_POSITION語義告訴Unity,pos裡包含了頂點在裁剪空間中的位置資訊
				float4 pos : SV_POSITION;
                //COLOR0可以用於儲存顏色資訊
				fixed3 color : COLOR0;
			};

			v2f vert(a2v v) : SV_POSITION{
                //宣告輸出結構
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                //v.normal包含了頂點的法線方向,其分量範圍在[-1.0, 1.0]
                //程式碼將分量範圍對映到[0.0, 1.0]
                //儲存到o.color中傳遞給片元著色器
				o.color = v.normal * 0.5 + fixed3(0.5,0.5,0.5);
				return o;
			}
			fixed4 frag(v2f i) : SV_TARGET{
                //插值後的i.color顯示到螢幕上
				return fixed4(i.color,1.0);
			}

			ENDCG
		}
	}
}

頂點著色器的輸出結構中必須包含一個變數,其語義是SV_POSITION,否則渲染器將無法得到裁剪空間中的頂點座標,也就無法把頂點渲染到螢幕上。COLOR0語義中的資料可由使用者自行定義,但一般為儲存顏色,如逐頂點漫反射顏色或逐頂點的高光反射顏色。類似語義還有COLOR1等。

vertex shader逐頂點呼叫,fragment shader逐片呼叫。FS的輸入是VS的輸出進行插值後的結果

1.4 如何使用屬性

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter5/SimpleShader" {
	Properties{
		_Color("Color Tint", Color) = (1.0,1.0,1.0,1.0)
	}
	SubShader{
		Pass{
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

            //在CG程式碼中,我們需要定義一個與屬性名稱和型別都匹配的變數
			fixed4 _Color;

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				fixed3 color : COLOR0;
			};

			v2f vert(a2v v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.color = v.normal * 0.5 + fixed3(0.5,0.5,0.5);
				return o;
			}

			fixed4 frag(v2f i) : SV_TARGET{
				fixed3 c = i.color;
                
                //使用_Color屬性控制輸出顏色,即color tint
				c*=_Color.rgb;
				return fixed4(c,1.0);
			}

			ENDCG
		}
	}
}

結果:

Color,初始值白色。

ShaderLab中屬性型別和CG中變數型別間匹配關係:

ShaderLab屬性型別 CG變數型別
Color, Vector float4, half4, fixed4
Range, Float float, half, fixed
2D sampler2D
Cube samplerCUbe
3D sampler3D
uniform fixed4 _Color;

用於提供關於該變數的初始值是如何指定和儲存的相關資訊,可省略

3、Unity提供的內建檔案和變數

CGPROGRAM
#include "UnityCG.cginc"
ENDCG

CGIncludes中主要的包含檔案及其主要用處

檔名 描述
UnityCG.cginc 包含了最常使用的幫助函式、巨集和結構體等
UnityShaderVariables.cginc 在編譯Unity Shader時,會被自動包含進來,包含了許多內建的全域性變數,UNITY_MATRIX_MVP等
Lighting.cginc 包含了各種內建光照模型,如果編寫Surface Shader會自動包含
HLSLSupport.cginc 在編譯Unity Shader時會自動包含進來,聲明瞭很多用於跨平臺編譯的巨集和定義

還有UnityStandardBRDF.cginc、UnityStandardCore.cginc等,用於實現基於物理的渲染

可以直接使用UnityCG.cginc中預定義的結構體作為頂點著色器的輸入和輸出

常用結構體

名稱 描述 包含的變數
appdata_base 用於頂點著色器的輸入 頂點位置、頂點法線、第一組紋理座標
appdata_tan 用於頂點著色器的輸入 頂點位置、頂點切線、頂點法線、第一組紋理座標
appdata_full 用於頂點著色器的輸入 頂點位置、頂點切線、頂點法線、四組(或更多)紋理座標
appdata_img 用於頂點著色器的輸入 頂點位置、第一組紋理座標
v2f_img 用於頂點著色器的輸出 裁剪空間中的位置、紋理座標

幫助函式

函式名 描述
float3 WorldSpaceViewDir(float4 v) 輸入模型空間的頂點位置,返回世界空間中從該點到攝像機的觀察方向
float3 ObjSpaceViewDir(float4 v) 輸入模型空間中的頂點位置,返回模型空間中該點到攝像機的觀察方向
float3 WorldSpaceLightDir(float4 v) 僅用於向前渲染。輸入模型空間中頂點位置,返回世界空間中從該點到光源的光照方向。未被歸一化。
float3 ObjSpaceLightDir(float4 v) 僅用於向前渲染。輸入模型空間中頂點位置,返回模型空間中從該點到光源的光照方向。未被歸一化。
float3 UnityObjectToWorldNormal(float3 norm) 法線方向從模型空間轉換到世界空間
float3 UnityObjectToWorldDir(in float3 dir) 方向向量從模型空間變換到世界空間
float3 UnityWorldToObjectDir(float3 dir) 方向向量從世界空間變換到模型空間

訪問時間、光照、霧效和環境光等變數大位於UnityShaerVariables.cginc,與光照有關的還會位於LIghting.cginc、AutoLight.cginc等檔案中

4、Unity提供的CG/HLSL語義

4.1 什麼是語義

賦給Shader輸入和輸出的字串,讓Shader知道在哪裡讀取資料並輸出到哪裡。通常情況下輸入輸出變數不需要特別的意義,可自行決定變數的用途。上面的例子利用COLOR0語義描述color但變數本身儲存什麼Shader並不關心

語義出現位置不同含義也不同。如,TEXCOORD0既可用於描述頂點著色器輸入結構a2f也可用於描述輸出結構v2f。a2f中把模型第一組紋理座標儲存在該變數裡,在v2f中TEXCOORD0修飾的變數含義由我們來定義

系統數值語義以SV開頭。pos包含了可用於光柵化的變換後的頂點座標,用這些語義描述的變數不可隨意賦值。SV_POSITION修飾的變數經過光柵化後顯示在螢幕上。在某些特定平臺上必須使用SV_POSITION、COLOR和SV_Target,為了更好跨平臺性,有特殊含義的變數最好以SV開頭的語義進行修飾。

4.2 Unity支援的語義

應用階段傳遞模型資料給頂點著色器時Unity支援的常用語義

語義 描述
POSITION 模型空間頂點位置,通常float4
NORMAL 頂點法線,通常float3
TANGENT 頂點切線,通常float4
TEXCOORDn 頂點紋理座標,TEXCOORD0表示第一組,通常float2/float4
COLOR 頂點顏色,通常float4/fixed4

TEXCOORDn中n數目是Shader Model有關,如Shader Model 2/3中n=8(預設編譯到2版本),在Shader Model 4/5中n=16。通常情況下一個模型紋理座標組數不超過2,往往使用TEXCOORD0/1。在appdata_full中最多使用六個。

頂點著色器傳遞資料到片元著色器階段常用語義

語義 描述
SV_POSITION 裁剪空間中的頂點座標,結構體中必須包含用該語義修飾的變數,等同於DirectX 9中的POSITION,最好用SV_POSITION
COLOR0 輸出第一組頂點顏色,非必需
COLOR1 輸出第二組頂點顏色,非必需
TEXCOORD0~TEXCOORD7 輸出紋理座標,非必需

自定義資料從頂點著色器到片元著色器一般用TEXCOORD0等。只有SV_POSITION有特殊含義。

片元著色器輸出常用語義

語義 描述
SV_Target 輸出值將會儲存到渲染目標中,等同於DirectX 9中的COLOR語義,但最好使用SV_Target

4.3 定義複雜的變數型別

利用結構體定義。

一個語義可以使用的暫存器只能處理4個浮點值,若想要定義矩陣型別,就講這些變數拆分成多個變數,比如float4*4矩陣型別就可以拆分成4個float4型別變數,每個變數儲存矩陣中的一行資料

4.4 Debug建議

-使用假色彩影象

將除錯的變數對映到[0,1]之間。顏色中大於一的數值會被設定為1,任何小於0的數值會被設定為0。

利用假色彩影象方式視覺化法線、切線、紋理、座標、頂點顏色。

Shader "Unity Shaders Book/Chapter5/False Color" {
	SubShader{
		Pass{
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			struct v2f{
				float4 pos : SV_POSITION;
				fixed4 color : COLOR0;
			};

			v2f vert(appdata_full v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

                //視覺化法線方向
				o.color = fixed4(v.normal * 0.5 + fixed3(0.5,0.5,0.5), 1.0);
                //視覺化切線方向
				o.color = fixed4(v.tangent * 0.5 + fixed3(0.5,0.5,0.5), 1.0);
                //視覺化副切線方向
				fixed3 binormal = cross(v.normal, v.tangent.xyz)*v.tangent.w;
				o.color = fixed4(binormal * 0.5 + fixed3(0.5,0.5,0.5), 1.0);
                //視覺化第一組紋理座標
				o.color = fixed4(v.texcoord.xy , 0.0,1.0);
                //視覺化第二組紋理座標
				o.color = fixed4(v.texcoord1.xy , 0.0,1.0);
                //視覺化第一組紋理座標的小數部分
				o.color = frac(v.texcoord);
				if(any(saturate(v.texcoord) - v.texcoord)){
					o.color.b = 0.5;
				}
				o.color.a = 1.0;
                
                //視覺化第二組紋理座標的小數部分
				o.color = frac(v.texcoord1);
				if(any(saturate(v.texcoord1) - v.texcoord1)){
					o.color.b = 0.5;
				}
				o.color.a = 1.0;
                
                //視覺化頂點顏色
				//o.color = v.color;

				return o;
			}
            

			fixed4 frag(v2f i) : SV_Target{
				return i.color;
			}

			ENDCG
		}
	}
}

-Visual Studio

-幀偵錯程式

檢視渲染該幀的各種渲染事件,包含DrawCall序列。單擊Knot的深度圖渲染事件,Game檢視顯示該事件效果,Hierarchy檢視中高亮顯示Knot物件,幀偵錯程式的右側視窗顯示該事件細節

5、渲染平臺的差異

5.1 紋理座標的差異

開啟抗鋸齒後Unity渲染得到螢幕影象,硬體抗鋸齒,得到渲染紋理。但在抗鋸齒下同時需要處理多張渲染影象,如需要同時處理螢幕影象和法線紋理,則在豎直方向朝向可能不同。此時需要自己在頂點著色器中翻轉某些渲染紋理(如深度紋理或其他由指令碼傳遞來的紋理)的縱座標,使之複合DirectX平臺規則,如:

#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
    uv.y = 1 - uv.y;
#endif

UNITY_UV_STARTS_AT_TOP判斷當前平臺是否為DirectX平臺,在這平臺上開啟抗鋸齒後主紋理紋素大小在豎直方向上將變成負值以方便我們對主紋理進行正確取樣,可以通過判斷_MainTex_TexelSize.y是否小於0檢驗是否開啟抗鋸齒。

5.2 Shader的語法差異

float4 v = float4(0.0) -> float4 v = float4(0.0,0.0,0.0,0.0)

需要在頂點著色器中訪問紋理則需要使用tex2Dlod函式,不支援tex2D,無法得到UV偏導,如tex2Dlod(tex,float4(uv,0,0))以及#pragma  target 3.0

5.3 Shader的語義差異

-利用SV_POSITION描述頂點著色器輸出的頂點位置

-使用SV_Target描述片元著色器輸出的顏色

6、Shader整潔之道

6.1 float、half還是fixed

float-32位 half-16位 fixed-11位

儘可能使用精度較低的型別,可以優化Shader效能,使用fixed儲存顏色和單位向量,更大範圍資料可以選擇half型別,最差情況下選擇float。

6.2 規範語法

6.3 避免不必要的計算

Shader內(尤其片元著色器)進行大量計算,會導致臨時暫存器數目或指令數目超過當前可支援數目。不同Shader Target、不同著色器階段可使用的臨時暫存器和指令數目都是不同的。

指令 描述
#pragma target 2.0 預設Shader Target等級,相當於Direct3D 9的Shader Model 2.0
#pargma target 3.0 相當於Direct3D 9的Shader Model 3.0
#pargma target 4.0 相當於Direct3D 10的Shader Model 4.0,目前只在DirectX 11和XboxOne/PS4支援
#pargma target 5.0 相當於Direct3D 11的Shader Model 5.0,目前只在DirectX 11和XboxOne/PS4支援

所有類似OpenGL的平臺支援到Shader Model 3.0。

Shader Model是微軟提供的規範,它們決定了Shader中的各個特性和能力,體現在Shader可以使用的運算指令數目和暫存器個數等。

6.4 慎用分支迴圈

計算流水線上端移動,比如片元著色器到頂點著色器,或在CPU進行預運算再傳遞給Shader。

-判斷分支語句的條件變數最好是常數,即在Shader執行過程中不變化;

-每個分支的操作指令數儘可能少;

-分支的巢狀層數儘可能少;

6.5 不要除以0