Cg頂點程序必須在結構中傳遞頂點數據。幾種常用的頂點結構定義在文件UnityCG.cginc中,有如下三種結構體:
1、appdata_base: 包含頂點位置,法線和一個紋理坐標。
2、appdata_tan:包含頂點位置,切線,法線和一個紋理坐標。
3、appdata_full:包含位置、法線、切線、頂點色和兩個紋理坐標。
struct appdata_base { float4 vertex : POSITION; //頂點坐標 float3 normal : NORMAL;//法線 float4 texcoord : TEXCOORD0;//UV }; 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;//第一層UV float4 texcoord1 : TEXCOORD1; //第二層UV fixed4 color : COLOR; //顏色 };
注:頂點坐標和正切線為什么是float4,這有點意思,因為這里它表示是齊次坐標,比如我們這樣表示一個float4(x,y,z,w),當w = 1的時候它表示點(x,y,z),當w= 0的時候它表示一個向量(x,y,z)。區別就在這里,當W為1時表示點,當W為0時表示向量。
texcoord0和texcoord1分別表示兩層UV,有時候我們模型上的貼圖需要多個圖片一起貼在一處,那么貼兩層就會有兩層UV。
以上三種類型為Unity內置的頂點Shader傳入結構體,如果想自定義也是可以的,但是自定義結構體里面的屬性,必須是基於appdata_full的,這樣才能識別出來,也就是自定義的結構里的屬性必須到appdata_full里的屬性里去選。
下面給一個簡單的頂點/片元 shader 事例:
Shader "Custom/Example" { Properties { _MainTex ("Texture", 2D) = "white" { } //引號里面的"Texture"則是Unity檢視面板中對應顯示的屬性名稱 } SubShader { pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; //_MainTex_ST的ST應該是SamplerTexture的意思 ,就是聲明_MainTex是一張采樣圖,也就是會進行UV運算。 //如果沒有這句話,是不能進行TRANSFORM_TEX的運算的。_MainTex_ST.xy為 圖中的Tiling,zw為圖中的offset. float4 _MainTex_ST; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; } ; v2f vert (appdata_base v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP,v.vertex); //MVP矩陣變換,將裁剪空間坐標轉換為相對屏幕位置的UV坐標 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); //TRANSFORM_TEX的作用是用頂點的UV v.texcoord和材質球的采樣圖片_MainTex做運算,確保頂點材質球里的縮放和偏移是正確的。等價於o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; return o; } float4 frag (v2f i) : COLOR { float4 texCol = tex2D(_MainTex,i.uv); float4 outp = texCol; return outp; } ENDCG } } }
其中SV_POSITION,SV_前綴的變量代表system value,在DX10以后的語義綁定中被使用代表特殊的意義,和POSITION用法並無不同。唯一區別是 SV_POSTION一旦被作為vertex shader的輸出語義,那么這個最終的頂點位置就被固定了(不能tensellate,不能再被后續改變它的空間位置?),直接進入光柵化處理,如果作為fragment shader的輸入語義那么和POSITION是一樣的,代表着每個像素點在屏幕上的位置(這個說法其實並不准確,事實是fragment 在 view space空間中的位置,但直觀的感受是如括號之前所述一般)
最后這個回答者說了,在DX10版本之前沒有引入SV_的預定義語義,POSITION被用作vertex shader的輸入,輸出,fragment shader的輸入參數。但DX10之后就推薦使用SV_POSITION作為vertex shader的輸出和fragment shader的輸入了,注意vertex shader的輸入還是使用POSITION!切記。但是DX10以后的代碼依舊兼容POSITION作為全程表達,估計編譯器會自動判斷並替換的吧。
另外,給一點編寫shader的建議:
1、只計算需要計算的東西;
2、通常,需要渲染的像素比頂點數多,而頂點數又比物體數多很多。所以如果可以,盡量將運算從PS移到VS,或直接通過script來設置某些固定值;
3、在使用Surface Shader時,可以通過一些指令讓shader優化很多。
通常情況下,Surface shader的很多默認選項都是開啟的,以適應大多數情況,但是很多時候,你可以關閉其中的一些選項,從而讓你的shader運行的更快:
(1) approxview 對於使用了view direction的shader,該選項會讓view dir的normalize操作per-vertex進行,而不是per-pixel。這個優化通常效果明顯。
(2) halfasview 可以讓Specular shader變得快一些,使用一個介於光照方向和觀察方向之間的half vector來代替真正的觀察方向viewDir來計算光照函數。
(3) noforwardadd Forward Render時,完全只支持一盞方向光的per-pixel渲染,其余的光照全部按照per-vertex或SH渲染。這樣可以確保shader在一個pass里渲染完成。
(4) noambient 禁掉ambient lighting和SH lighting,可以讓shader快一點兒。
4、浮點數精度相關:
float:最高精度,通常32位
half:中等精度,通常16位,-60000到60000,
fixed:最低精度,通常11位,-2.0到2.0,1/256的精度。
盡量使用低精度。對於color和unit length vectors,使用fixed,其他情況,根據取值范圍盡量使用half,實在不夠則使用float。
在移動平台,關鍵是在fragment shader中盡可能多的使用低精度數據。另外,對於多數移動GPU,在低精度和高精度之間轉換是非常耗的,在fixed上做swizzle操作也是很費事的。
5、Alpha Test
Alpha test和clip()函數,在不同平台有不同的性能開銷。
通常使用它來cull那些完全透明的像素。
但是,在ios和一些android上使用的PowerVR GPUs上面,alpha test非常的昂貴。
6、Color Mask
在移動設備上,Color Mask也是非常昂貴的,所以盡量別使用它,除非真的是需要。
最后推薦兩篇不錯的博客,