天天看點

Shader 筆記二 片段着色器 Fragment Shader簡介Vertex And Fragment Shader

簡介

頂點片段着色器,運作于具有可程式設計渲染管線的硬體上,它包括頂點程式Vertex Programs和片段程式Fragment Programs。當在使用頂點程式或片段程式進行渲染的時候,圖形硬體的固定功能管線會關閉,具體來說就是編寫的頂點程式會替換掉固定管線中标準的3D變換,光照,紋理坐标生成等功能,而片段程式會替換掉SetTexture指令中的紋理混合模式。

頂點着色程式與片段着色程式通常是同時存在,互相配合,前者的輸出作為後者的輸入。不過,也可以隻有頂點着色程式。如果隻有頂點着色程式,那麼隻對輸入的頂點進行操作,而頂點内部的點則按照硬體預設的方式自動插值。例如,輸入一個三角面片,頂點着色程式對其進行phong光照計算,隻計算三個頂點的光照顔色,而三角面片内部點的顔色按照硬體預設的算法(Gourand明暗處理或者快速phong明暗處理)進行插值,如果圖形硬體比較先進,預設的處理算法較好(快速phong明暗處理),則效果也會較好;如果圖形硬體使用Gourand明暗處理算法,則會出現馬赫帶效應(條帶化)。

由于GPU對資料進行并行處理,是以每個資料都會執行一次shader程式。即,每個頂點資料都會執行一次頂點程式;每個片段都會執行一次片段程式。

Vertex And Fragment Shader

和之前一樣,我們還是通過一個例子來看,在Project面闆中,右擊Create->Shader->Image Effect Shader,命名為ImageEffectShaderDemo.shader,如下:

Shader "Custom/ImageEffectShaderDemo" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader {
		// No culling or depth
		Cull Off ZWrite Off ZTest Always

		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;
			};

			v2f vert (appdata v) {
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			sampler2D _MainTex;

			fixed4 frag (v2f i) : SV_Target {
				fixed4 col = tex2D(_MainTex, i.uv);
				// just invert the colors
				col = 1 - col;
				return col;
			}
			ENDCG
		}
	}
}
           

前面一些和surface shader相同的内容我們這就不贅述了。關于Cull Off ZWrite Off ZTest Always這塊的内容同樣我們将單獨講解:https://blog.csdn.net/wangjiangrong/article/details/89335208

#pragma

#pragma vertex vert 将函數name的代碼編譯成頂點程式
#pragma fragment frag 将函數name的代碼編譯成片段程式

#include

編寫Shader的時候,我們可以像使用C++中的頭檔案一樣,使用#include預處理指令來包含其他代碼集合。這告訴Unity我們想要目前的Shader使用包含的這些檔案中的代碼。我們這樣做實際上是在相應位置包含了Cg代碼片段。

#include "UnityCG.cginc" 引用UnityCg.cginc頭檔案(Unity安裝目錄 > Editor > Data > CGIncludes下)就可以使用預先設定好的結構體直接使用,他們分别有appdata_base,appdata_tan和appdata_full:

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

struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
};

struct appdata_full {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    float4 texcoord1 : TEXCOORD1;
    fixed4 color : COLOR;
};
           

頂點程式輸入:

#pragma vertex vert 指定了vert 函數為頂點程式,它有一個輸入參數,可以是上面提到的三個CS預定義的輸入參數appdata_base,appdata_tan和appdata_full,也可以我們自定義,例如例子中的appdata。需要注意的是自定義的結構中的屬性,隻能在以下屬性中選擇(具體類型可以不一樣,比如fixed4 color)

float4 vertex:POSITION 頂點位置
float3 normal:NORMAL 頂點法線
float4 texcoord:TEXCOORD0 第一UV坐标
float4 texcoord1:TEXCOORD1 第二UV坐标
float4 color:COLOR   每個頂點(per-vertex)顔色
float4 tangent:TANGENT 切線向量(用在法線貼圖中)

知識點1:頂點坐标和正切線是float4,:這裡它表示是齊次坐标,比如我們這樣表示一個float4(x,y,z,w),當w = 1的時候它表示點(x,y,z),當w= 0的時候它表示一個向量(x,y,z)。差別就在這裡,當W為1時表示點,當W為0時表示向量。

知識點2:副法向量binoraml,它可以通過noraml和tangent計算得出來,公式如下所示:

float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
           

知識點3:texcoord0和texcoord1分别表示兩層UV,有時候我們模型上的貼圖需要多個圖檔一起貼在一處,那麼貼兩層就會有兩層UV。

片段程式輸入:

前文提到了,頂點程式的輸出即為片段程式的輸入。頂點程式的輸出中必須包含POSITION語義變量,該值不能在片段程式中直接使用,它隻被用于光栅化。自定義資料可以使用TEXCOORD系列的語義詞來表示。

頂點程式:

頂點着色程式從GPU前端子產品(寄存器)中提取圖元資訊(頂點位置、法向量、紋理坐标等),并完成頂點坐标空間轉換、法向量空間轉換、光照計算等操作,最後将計算好的資料傳送到指定寄存器中,供片段程式使用。

v2f vert (appdata v) {
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.uv = v.uv;
	return o;
}
           

例子中v2f即為頂點程式的輸出,同時也是片段程式的輸入,appdata為頂點程式的輸入。頂點程式将輸入的頂點位置處理後賦給了v2f,同時将輸入的uv值賦給了v2f。

内置函數:

mul(M, V) 是表示矩陣M和向量V進行點乘,得到一個向量Z,這個向量Z就是對向量V進行矩陣變換後得到的值
float4 UnityObjectToClipPos(float3 pos) 等價于:mul(UNITY_MATRIX_MVP, float4(pos, 1.0)),但是更有效率。把頂點從模型空間轉換到裁剪空間
float3 UnityObjectToViewPos(float3 pos) 等價于:mul(UNITY_MATRIX_MV, float4(pos, 1.0)),但是更有效率
float3 WorldSpaceViewDir (float4 v) 參數是模型空間下的頂點坐标,取得世界空間下指向錄影機的方向,即視角方向
float3 ObjSpaceViewDir (float4 v) 同上,不過取到的視角方向是在模型空間上的
... ...

内置的矩陣(float4x4):

https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

UNITY_MATRIX_MVP 目前模型觀察投影矩陣,用于将頂點/方向矢量從模型空間變換到裁剪空間
UNITY_MATRIX_MV  目前模型觀察矩陣,用于将頂點/方向矢量從模型空間變換到觀察空間
UNITY_MATRIX_V 目前觀察矩陣,用于将頂點/方向矢量從世界空間變換到觀察空間
UNITY_MATRIX_P 目前的投影矩陣,用于将頂點/方向矢量從觀察空間變換到裁剪空間
UNITY_MATRIX_VP 目前觀察投影矩陣,用于将頂點/方向矢量從世界空間變換到裁剪空間
UNITY_MATRIX_T_MV 模型觀察矩陣的轉置
UNITY_MATRIX_IT_MV 模型觀察矩陣的逆轉置,用于将法線從模型空間變換到觀察空間,也可用于得到UNITY_MATRIX_MV的逆矩陣
unity_ObjectToWorld 目前模型矩陣,用于将頂點/方向矢量從模型空間變換到世界空間
unity_WorldToObject 目前世界矩陣的逆矩陣,用于将頂點/方向矢量從世界空間變換到模型空間

空間:

模型空間(model space / object space) 每個模型都有自己的模型空間,當它旋轉或移動時,模型空間也會随之移動。
世界空間(world space) 宏觀的特殊坐标系,在Unity中模型如果沒有父節點,那麼它就在世界空間内
觀察空間(view space / camera space) 以錄影機為原點
裁剪空間(clip space) 其矩陣為投影矩陣,裁剪圖元,并為投影做準備
螢幕空間 螢幕左下角是(0, 0),右上角是(pixelWidth, pixelHeight)

頂點着色器的最基本任務就是把頂點坐标從模型空間轉換到裁剪空間中,模型空間 -> 世界空間 -> 觀察空間 -> 裁剪空間 -> 螢幕空間。

片段程式:

片段着色程式從頂點程式存放資料的寄存器中擷取需要的資料,通常為“紋理坐标、光照資訊等”,并根據這些資訊以及從應用程式傳遞的紋理資訊(如果有的話)進行每個片段的顔色計算,最後将處理後的資料送光栅操作子產品。

片段就是所有三維頂點在光栅化之後的資料集合,這些資料沒有經過深度值比較,而螢幕顯示的像素是經過深度比較的。

片段着色程式是對每個片段進行獨立的顔色計算,并且算法由自己編寫,不但可控性好,而且可以達到更好的效果。片段程式通常隻輸出一個COLOR(最終顔色)。

fixed4 frag (v2f i) : SV_Target {
	fixed4 col = tex2D(_MainTex, i.uv);
	// just invert the colors
	col = 1 - col;
	return col;
}
           

這段代碼的含義即将貼圖中每個頂點的新顔色 = 1 - 原顔色。即黑的變白的,白的變黑的。得到的效果如下:

Shader 筆記二 片段着色器 Fragment Shader簡介Vertex And Fragment Shader
Shader 筆記二 片段着色器 Fragment Shader簡介Vertex And Fragment Shader