【Unity Shader入門精要】— 開始Unity Shader之旅
內容會持續更新,有錯誤的地方歡迎指正,謝謝!
建立Shader
1.右鍵建立shader(如果想寫頂點片元著色器就選Unlit Shader,如果想寫表面著色器就選Standard Surface Shader,如果想寫屏幕後處理著色器就選Image Effect Shader)
2.再建立材質Material,並將shader拖給材質
3.把材質拖給物體
簡單的頂點/片元著色器
頂點/片元著色器的基本結構
Shader "Custom/Simple Shader" { SubShader { Pass { //CGPROGRAM,ENDCG包圍的為需要編輯的CG程式碼片段 CGPROGRAM //下面兩行告訴Unity,哪個函式包含了頂點著色器的程式碼,哪個函式包含了片元著色器的程式碼; //編譯指令:#pragma vertex name,name為指定的函式名,一般用vert,frag #pragma vertex vert #pragma fragment frag //v包含了這個頂點的位置,這是通過POSITION語義指定。 //vert函式的返回值是一個float4型別的變數,它是該頂點在裁剪空間的位置。 //POSITION,SV_POSITION都是Cg/HLSL中的語義,用以限定輸入輸出引數 //如POSITION告訴Unity,把模型的頂點座標填充到v中,SV_POSITION告訴Unity,輸出是裁剪空間的頂點座標 float4 vert(float4 v : POSITION) : SV_POSITION { // MVP變換:從模型座標變化到裁剪座標 //Unity5.x版本(記不清具體版本了)以後的都改成了用UnityObjectToClipPos (v.vertex)代替 return mul(UNITY_MATRIX_MVP, v.vertex); } //SV_Target告訴渲染器,把使用者的輸出顏色儲存到一個渲染目標中 fixed4 frag() : SV_Target { return fixed4(1.0, 1.0, 1.0, 1.0); } ENDCG } } }
POSITION和SV_POSITION是CG/HLSL的語義(用於限定輸入、輸出),在這裡是不可以省略的,它們告訴系統需要哪種輸入值,以及輸出的是什麼。例如POSITION說明輸入的是頂點座標,SV_POSITION說明輸出的是裁剪空間的頂點座標。如果沒有語義,渲染器就不知道使用者輸入輸出是什麼。
定義模型資料:如何向頂點函式中輸入更多的模型的頂點資料a2v
上面程式碼,我們用POSITION語義得到了模型的頂點位置。但我們時常還需要頂點的紋理座標,法線,切線等資料,所以一個單一的輸入引數肯定不行,就需要一個結構體a2v了。我們修改上面的程式碼如下:
Shader "Custom/Simple Shader" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag //增加一個結構體,a表示應用(Application),v表示頂點著色器(vertex shader) //a2v意思是把資料從應用階段傳遞到頂點著色器,這樣我們可以在頂點著色器中訪問模型資料 struct a2v { //POSITION、NORMAL、TEXCOORD0等來自Mesh Render元件 //POSITION語義:用模型空間頂點座標填充vertex變數 float4 vertex : POSITION; //NORMAL語義:模型空間的法線填充normal變數 float3 normal : NORMAL; //TEXCOORD0語義:用模型的第一套紋理座標填充texcoord變數 float4 texcoord : TEXCOORD0; }; float4 vert(a2v v) : SV_POSITION { //使用v.vertex來訪問模型空間的頂點座標 //並利用MVP矩陣將其從模型空間轉換到裁剪空間裡 return mul(UNITY_MATRIX_MVP, v.vertex); } fixed4 frag() : SV_Target { return fixed4(1.0, 1.0, 1.0, 1.0); } ENDCG } } }
頂點和片元著色器之間的通訊v2f
在渲染流水線中,頂點資料經過頂點著色器的一系列變換,輸出的資料要先光柵化,再由片元著色器進行上色。我們上面說的模型頂點座標,法線,紋理座標等資料是怎麼傳給片元著色器的呢,我們還需要定義一個結構體v2f,對上面的程式碼改動如下:
Shader "Custom/Simple Shader" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; }; //使用一個結構體來定義頂點著色器的輸出和片元著色器的輸入 struct v2f { //SV_POSITION語義:pos裡包含了頂點在裁剪空間中的位置 float4 pos : SV_POSITION; //COLOR0語義:可以用於儲存顏色資訊 fixed3 color : COLOR0; }; v2f vert(a2v v) { //宣告輸出結構v2f v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); //這是把v.normal(法線資料)轉換成顏色資訊存在o.color中,並傳遞給片元著色器 o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5); //v.normal包含法線方向,分量範圍在[-1.0,1.0] return o; } //v2f結構作為引數傳遞給片元著色器 fixed4 frag(v2f i) : SV_Target { //將插值後的i.color顯示到螢幕上 return fixed4(i.color, 1.0); } ENDCG } } }
v2f中每個變數都需要語義,而且至少要包含SV_POSITION。否則渲染器得不到裁剪空間的座標,也就無法顯示在螢幕上。上例中我們把法線資料轉化成顏色了,法線範圍是[-1, 1],顏色範圍是[0, 1],所以上面用先乘0.5,再加0.5的辦法進行了轉換。最後把轉換後的顏色插值後顯示在了螢幕上。
如何使用屬性
例如:
新增Properties語義塊,_Color屬性就宣告在其中,然後在CG中我們也要定義這個變數_Color,對應的型別是float4/half4/fixed4。
ShaderLab中屬性的型別和CG中變數型別之間匹配關係如下:
ShaderLab中屬性型別 | CG中變數型別 |
---|---|
Color, Vector | float4,half4,fixed4 |
Range, Float | float,half,fixed |
2D | sampler2D |
3D | sampler3D |
Cube | samplerCube |
挺簡單哈~
Unity Shader內建檔案、變數和語義
頂點/片元著色器的複雜之處在於很多事情都要我們親力而為,例如:自己轉換法線方向、自己處理光照等。但Unity提供了很多內建檔案,這些檔案包含了很多提前定義好了的函式、變數和巨集。
使用#include指令可以把這些檔案包含進來,這樣,我們就可以使用Unity為我們提供的一些有用的變數和函數了~
例子:#include "UnityCG.cginc"
UnityCG.cginc是最常接觸的包含檔案。我們常用的結構體和函式基本都在裡面。例如,我們可以直接使用其中預定義的結構體作為頂點著色器的輸入和輸出,如下:
另外,有一些檔案是會被自動包含進來的,不需要使用#include指令,比如UnityShaderVariables.cginc和HLSLSupport.cginc。=>UnityShaderVariables.cginc裡的UNITY_MATRIX_MVP,我們可以直接用。
其實,這些東西我們都可以自己實現的,但Unity封裝好了,就直接用吧,不僅能提高我們的開發效率,還能提高程式碼的複用率。
Unity提供的Cg/HLSL語義
語義分為三類:
- 從應用階段傳遞模型資料給頂點著色器時Unity支援的常用語義;
- 從頂點著色器傳遞資料給片元著色器時Unity使用的常用語義;
- 片元著色器輸出時Unity支援的常用語義。
Shader整潔之道
- float、half還是fixed
根據實際情況(根據你遊戲要跑的平臺、美術效果的要求、效能限制等等因素)選擇精度,最好制訂一套變數型別的規則~ - 避免不必要的計算
原則1:儘量使用預計算:也就是把大量計算放在CPU中計算,再把計算結果從應用階段傳遞到幾何階段。
原則2:在片元著色器中的計算越少越好:也就是能在幾何階段進行的運算就別放到光柵化階段。 - 慎用分支和迴圈
能不用就別用~