Unity3D學習筆記3——Unity Shader的初步使用

charlee44發表於2021-08-01

1. 概述

在上一篇文章《Unity3D學習筆記2——繪製一個帶紋理的面》中介紹瞭如何繪製一個帶紋理材質的面,並且通過調整光照,使得材質生效(變亮)。不過,上篇文章隱藏了一個很重要的細節——Unity Shader。Shader(著色器)是渲染管線中可被使用者程式設計的階段,依靠著色器可以控制渲染管線的細節。現代影像渲染技術,都把Shader封裝成與Material(材質)相關的元件。所以這篇文章,我們就初步學習下在Unity中使用Shader。

2. 詳論

2.1. 建立材質

在上一章中,材質、以及材質相關的資源是在Unity3D編輯器中建立,在C#指令碼中直接引用的。這裡為了學習使用Shader,我們使用自定義的Shader,可以在C#指令碼中建立材質。修改上一章程式碼的材質部分:

Shader shader = Shader.Find("Custom/MainShader");
Material material = new Material(shader);
        
Texture2D texture = Resources.Load<Texture2D>("ImageDemo");
material.mainTexture = texture;

MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>();      
meshRenderer.material = material;

可以看到,要建立一個Material,首先得建立一個Shader。我們在Project檢視中右鍵選單->Create->Standard Surface Shader,建立一個標準表面著色器MainShader:

imagelink1

雙擊開啟這個Shader,可以看到這個Shader的具體內容。標準著色器很複雜,我們清空裡面的內容,填入我們這個更簡單的著色器示例:

Shader "Custom/MainShader"
{
    Properties
    {       
        _MainTex ("Texture", 2D) = "white" {}      
    }
    SubShader
    {
        Tags{"Queue" = "Geometry"}
		
		Cull Back

		Pass
		{
		    CGPROGRAM
    
			#pragma vertex vert	
			#pragma fragment frag

			sampler2D _MainTex;

			//頂點著色器輸入
			struct a2v
			{
				float4  position : POSITION;
				float3  normal: NORMAL;
				float2  texcoord : TEXCOORD0;	
 			};

			//頂點著色器輸出
			struct v2f
			{
				float4 position: SV_POSITION;
				float2 texcoord: TEXCOORD0;
			};

			v2f vert(a2v v)
			{
				v2f o;	
				o.position = mul(UNITY_MATRIX_MVP, v.position);									
				o.texcoord = v.texcoord;

				return o;
			}

			fixed4 frag(v2f i) : SV_Target 
			{
				return tex2D(_MainTex, i.texcoord);			
			}
   
			ENDCG
		}
    }
    FallBack "Diffuse"
}

2.2. 著色器

Unity使用的著色器語言叫做ShaderLab,它是圖形渲染中Shader(例如GLSL,HLSL以及CG)的更高階更抽象一級的封裝。ShaderLab是個非常簡單的說明性描述語言,通過巢狀在花括號中的語義來描述Unity Shader檔案。

2.2.1. 名稱

通過Shader語義指定Unity Shader的名稱:

Shader "Custom/MainShader"
{

}

這個名稱非常重要,在Unity編輯器中需要通過這個名字來引用Shader。

2.2.2. 屬性

Shader語義塊的第一個語義塊是Properties語義塊,它連線著材質和Unity3d編輯器,設定了這個屬性就能夠通過材質皮膚調整材質,調整材質的本質就是調整Shader。Properties的定義通常描述如下:

Properties {
	Name ("display name",PropertyType) = DefaultValue
}

Name指的是在Shader中使用的名稱,display name指的是顯示在材質皮膚的名稱。PropertyType則有點容易混淆,它指的是顯示在材質皮膚中的屬性型別,借用一下《Unity Shader入門精要》的圖表:

imagelink2

2.2.3. SubShader

每個Unity Shader都至少包含一個SubShader語義塊,Unity會優先選擇第一個能夠在當前平臺下執行的SubShader作為最終渲染效果的Shader。

這個語義塊下面又會包含三個語義塊:

2.2.3.1. 標籤(Tags)

SubShader的標籤用於用於標識何時以何種方式被渲染到渲染引擎,它由一系列鍵值對組成。Queue是最常用的標籤,用於標識渲染物體在渲染佇列中的位置:
imagelink3

我們這裡,把這個渲染物體放到Geometry佇列中,這個位置通常放置不透明物體的渲染:

Tags{"Queue" = "Geometry"}

2.2.3.2. 渲染狀態(RenderSetup)

渲染狀態用於設定圖形硬體的各種狀態,例如是否應開啟 Alpha 混合或是否應使用深度測試等。在像OpenGL這樣的圖形介面中,通常是以函式的形式進行呼叫的,Unity3d將其放在Shader裡面,也有一定的道理。

這裡的渲染狀態設定成將背面裁剪掉:

Cull Back

2.2.3.3. 通道(Pass)

在Pass語義塊中,才是像OpenGL/DirectX中使用的Shader。OpenGL使用的著色器語言叫做GLSL,DirectX使用的著色器語言叫做HLSL,Unity3D則推薦使用Cg語言,這是一種類C語言,與HLSL非常相似。Cg語言程式碼段在Pass語義塊中被包裹在CGPROGRAM和ENDCG之間:

CGPROGRAM
//...
ENDCG

2.2.4. 回退(FallBack)

FallBack定義了一種退化策略,由於不同機器支援的效能特性不同,如果之前的子著色器都不生效,那麼就使用這個著色器,通常這個著色器是內建的:

FallBack "Diffuse"

2.3. 渲染管線

圖形渲染引擎的渲染管線其實是個內涵非常豐富的概念,再次借用《Unity Shader入門精要》的插圖,渲染管線的描述大致如下:

imagelink4

當然只看這個圖是不夠的,但是我們可以直接從程式碼層面去了解它。鑲嵌在CGPROGRAM和ENDCG之間的CG程式碼,體現的正是渲染管線的思維。

首先,通過編譯指令,分別指定頂點著色器程式和片元著色器程式:

#pragma vertex vert	
#pragma fragment frag

vert就是頂點著色器的函式,在這個著色器程式中指定了計算了頂點座標和紋理座標:

v2f vert(a2v v)
{
	v2f o;	
	o.position = mul(UNITY_MATRIX_MVP, v.position);									
	o.texcoord = v.texcoord;

	return o;
}

傳入引數是一個結構體,POSITION,NORMAL,TEXCOORD0是Unity Shader中固定的語義,分別代表這位置、法向量以及紋理座標,他們也被稱為頂點屬性。還記得在上一篇文章《Unity3D學習筆記2——繪製一個帶紋理的面》中建立Mesh時給Mesh建立的成員變數vertices、uv和normals吧?給他們傳入的資料正是在這裡用到了。

//頂點著色器輸入
struct a2v
{
	float4  position : POSITION;
	float3  normal: NORMAL;
	float2  texcoord : TEXCOORD0;	
};

傳出引數則是另外一個結構體:

//頂點著色器輸出
struct v2f
{
	float4 position: SV_POSITION;
	float2 texcoord: TEXCOORD0;
};

SV_POSITION表示的是裁剪空間座標,也就是在頂點著色器中計算的頂點值。這個計算內容的內涵也挺豐富的,簡單來說,建立Mesh時的頂點座標,經過一個模型變換(Model)、檢視變換(View)、投影變換(Projection),最終變成了裁剪空間座標系中的座標,體現在著色器中,就是內建的MVP矩陣UNITY_MATRIX_MVP。

剩下的就是片元著色器函式的部分了。在這個著色器中,_MainTex也就是我們先前建立的,並且傳遞到材質中的紋理,通過將頂點著色器中傳遞過來的紋理座標進行取樣,得到具體的片元顏色:

sampler2D _MainTex;

fixed4 frag(v2f i) : SV_Target 
{
	return tex2D(_MainTex, i.texcoord);			
}

最終顯示的效果如下:
imagelink5

可以看到這裡顯示的就是圖片本身的顏色,這是因為在著色器中只是取樣了圖片的顏色,並沒有光照計算的參與。也就是在圖形引擎中,任何效果的設定只是表象,任何效果的實現都會歸結到著色器中。

相關文章