第一個簡單的頂點vert/片元frag着色器
1)打開Unity 5.6編輯器,新建一個場景后ctrl+s保存命名為Scene_5。默認創建的場景是包含了一攝像機,一平行光,且場景背景是一天空盒而非純色。在這里菜單中選擇 Window->lighting->settings,會彈出一個光照選項設置框如下圖:

點擊箭頭處選擇“None”資源即可去掉天空盒,看到一個純色背景。
2)右鍵create一C# script,命名為shader_5,放置腳本到shader文件夾。(相當於定義了如何“穿衣服”)
3)右鍵新建shader/Standard Surface Shader,命名為shaderMat_5,放置到Material文件夾。(相當於定了一“外表衣服”) 然后把2)中的shader文件賦給它(注意:這里我們的shader文件全部都放到shader文件夾統一管理,那每個shader代碼編寫命名要“shader/xxx”,如下圖)。


4)右鍵create一個Capsule柱體,把第3)的材質拖到這個Sphere球體,渲染這個柱體的時候就能運行自定義的shader代碼。(相當於告訴對象,按照shader定義的‘穿衣方式’給對象穿上材質衣服)
5)使用standard shader原有默認代碼,渲染的效果如下:

復制粘貼以下的代碼到shader文件,
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "shader/shader_5" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v : POSITION) : SV_POSITION { return UnityObjectToClipPos(v); } fixed4 frag() : SV_Target { return fixed4(1.0, 0, 0, 0); } ENDCG } } }
運行看到的效果如下:

下面詳細分析,代碼第一行通過“../文件夾名/shader文件名”形式定義了每個u shader的名字,有利於為材質球選擇shader時右鍵快速找到自定義的shader文件(“/”控制shader文件在材質面板中出現的位置)。在Unity ShaderLab結構中,主要是Properties,SubShader,Fallback等。
<1>Properties,顧名思義即“屬性”,通俗點說就是提供在編輯器材質面板上讓開發者輸入接收屬性調整各種材質屬性的直接調試的機會。 因需要在shader程序中訪問這些屬性賦值,所以每個需要有一唯一的名字(Name),這個Name的用途僅是在shader程序中做區分。在Unity中,屬性名字通常由一下划線開始。顯示的名稱(display name)則是出現在材質面板上的名字,顯然要對其指定類型(PropertyType)。為了第一次把Unity shader賦給某材質時,材質面板上能顯示初始值,所以需要為每個屬性指定一個默認值。

如在上述代碼中添加屬性Property,
Shader "shader/shader_5" { Properties { _testInt("fuckInt",Int) = 888 _testFloat("fuckFloat",Float) = 1.68 _testRange("fuckRange",Range(0.0, 10.0)) = 6.8 _testColor("fuckColor",Color) = (0.0, 1.0, 0.0, 0.0) _testVector("fuckVector",Vector) = (9,5,2,7) //定義2D貼圖 2的階數大小(256*318)之類的貼圖,這張貼圖在采樣后被轉為對應基於模型UV的每個像素的顏色最終顯示 _test2D("2D貼圖",2D) = ""{} //定義立方體貼圖 簡單說就是6張有聯系的貼圖的組合,主要用來做反射效果(比如天空盒動態反射),也會被轉換為對應點的采樣 _testCube("Cube貼圖",Cube) = "white"{} //_test3D("fuck3D",3D) = "black"{} } SubShader { Pass { CGPROGRAM fixed4 _testColor;//注意要使用Properties屬性值時必須先聲明 #pragma vertex vert #pragma fragment frag float4 vert(float4 v : POSITION) : SV_POSITION { return UnityObjectToClipPos(v); } fixed4 frag() : SV_Target { return _testColor; } ENDCG } } }
效果如下圖所示,顏色可直接在材質面板中調整:

<2>SubShader和Pass語義塊,用來說明如何渲染,每個shader文件可以包含多個SubShader語義塊,當Unity加載這個shader文件時會按順序從頭掃描所有SubShader,然后選擇第一個平台支持的SubShader文件,若都不支持則選擇Fallback指定的shader。而每個Pass定義了一次完整的渲染流程,Pass數目較多會導致渲染性能的下降,所以盡量使用最少數量的Pass。 在SubShader和Pass中都可以設置渲染相關的狀態(如是否開啟混合和測試等)和標簽,但各自的標簽內容不同的故不能共用,后面會分開說明。當在SubShader中設置了渲染狀態,則會應用到所有的Pass塊,否則要單獨在Pass中聲明。

SubShader的標簽是字符串組成的鍵值對,用來告訴unity引擎希望怎樣以及何時渲染這個對象,標簽值和Pass的不一樣。編寫格式是: Tags {"tagName" = "value" "tagName2" = "value2"}

Pass語義塊多了個name,用來區分每一個pass塊,並且通過這個名稱,可以使用UsePass命令來直接使用其他Unity Shader中的Pass塊。例如在Pass中添加 Name "MyPassName",那在別的模塊就能通過 UsePass “MyShader/MYPASSNAME" 進行一樣的渲染處理,有利於提高代碼的復用性。 要注意一點,使用UsePass指令時Pass的名稱必須所有字母都是大寫模式!


在HelloWorld工程中不需要任何渲染設置和標簽設置,只需要編寫CG代碼塊片段,由CGPROGRAM開始到ENDCG結束包圍。 首先,是兩條以"#pragma標志的編譯指令,將告訴unity哪個函數包含了頂點着色器的代碼,哪個函數包含了片段着色器代碼。 #pragma vertex name 這里name就是我們指定的函數名,不一定是vert或frag,可以是任何自定義的合法函數名。
重點分析vert函數的定義:
float4 vert(float4 v : POSITION) : SV_POSITION{ return mul(UNITY_MATRIX_MVP, v) }
頂點着色器代碼,是逐頂點執行的。這里POSITION和SV_POSITION都是CG中的語義,是不可忽略的,這些語義告訴unity用戶需要哪些輸入值,以及用戶輸出是什么。如POSITION將告訴Unity,把模型的頂點坐標填充到輸入參數v中;SV_POSITION將告訴Unity頂點着色器的輸出是裁剪空間中的頂點坐標 。如果沒有語義說明,渲染器就完全不知道用戶的輸入輸出是什么,會得到錯誤的效果。 運行程序,會發現unity會自動替換mul()函數為UnityObjectToClipPos,原因在於:所有內建的矩陣名字在Instanced Shader中都是被重定義過的,如果直接使用UNITY_MATRIX_MVP,會引入一個額外的矩陣乘法運算。推薦使用UnityObjectToClipPos / UnityObjectToViewPos函數,會把這一次額外的矩陣乘法優化為向量-矩陣乘法。
同理,frag函數SV_Target語義告訴渲染器,把用戶輸出顏色存儲到一個渲染目標中,這里將默認輸出到默認的幀緩存中。
擴展模型輸入數據
上面的代碼只能通過POSITION語義獲得模型的頂點坐標,要是還想獲得模型每個頂點的紋理坐標和法線方向,該怎么辦呢?因為使用紋理坐標來訪問對紋理采樣,而用法線數據計算光照等都是很正常的需求,這就要求定義一新的輸入參數(如結構體等),而不再是簡單的一個數據類型。同理希望輸出紋理坐標,法線等數據到fragment片元着色器,這也需要結構體。 修改的代碼如下:
SubShader { Pass { CGPROGRAM fixed4 _testColor;//注意要使用Properties屬性值時必須先聲明 #pragma vertex vert #pragma fragment frag struct a2v { //POSITION語義:用模型空間的頂點坐標填充vertex變量 float4 vertex:POSITION; //NORMAL語義:用模型空間的法線方向填充normal變量 float3 normal:NORMAL; //TEXCOORD0語義:用模型的第一套紋理坐標填充texcoord變量 float4 texcoord:TEXCOORD0; }; struct v2f { //SV_POSITION語義:把頂點在裁剪空間中位置信息填充pos變量 float4 pos:SV_POSITION; //COLOR0語義:存儲顏色信息 float3 color:COLOR0; }; float4 vert(a2v v) : SV_POSITION { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.color = v.nonrmal * 0.5 + fixed3(0.5,0.5,0.5) return o } fixed4 frag(v2f i) : SV_Target { return fixed4(i.color, 1.0); } ENDCG } }
其中,a2v表示把數據從應用階段(application)傳遞到頂點着色器中。那填充到POSITION,NORMAL等語義中的數據是從哪來的呢?在Unity中,它們是由使用該材質的Mesh Render組件提供。在每幀調用draw Call的時候,Mesh Render組件會把它負責渲染的模型數據發送給Unity Shader。而一個模型通常包含一組三角面片,每個三角面片由3個頂點構成,每個頂點又包含一些數據,如頂點位置,法線,紋理坐標,頂點顏色等等。
