Shader大體上可以分為兩類,簡單來說
1.表面着色器(Surface Shader) - 為你做了大部分的工作,只需要簡單的技巧即可實現很多不錯的效果。類比卡片機,上手以后不太需要很多努力就能拍出不錯的效果。
2.片段着色器(Fragment Shader) - 可以做的事情更多,但是也比較難寫。使用片段着色器的主要目的是可以在比較低的層級上進行更復雜(或者針對目標設備更高效)的開發。
結構:
//指定shader名字 Shader "Custom/Diffuse Texture" { //屬性定義 Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } //子着色器 SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex : POSITION(語義,參數當做POSITION類型參數); }; void surf (Input IN, inout SurfaceOutput o) :COLOR0(語義:返回值當做顏色處理) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } //回滾 FallBack "Diffuse" }
屬性
在Properties{}中定義着色器屬性,在這里定義的屬性將被作為輸入提供給所有的子着色器。每一條屬性的定義的語法是這樣的:
_Name("Display Name", type) = defaultValue[{options}]
_Name - 屬性的名字,簡單說就是變量名,在之后整個Shader代碼中將使用這個名字來獲取該屬性的內容
Display Name - 這個字符串將顯示在Unity的材質編輯器中作為Shader的使用者可讀的內容
type - 這個屬性的類型,可能的type所表示的內容有以下幾種:
Color - 一種顏色,由RGBA(紅綠藍和透明度)四個量來定義;
2D - 一張2的階數大小(256,512之類)的貼圖。這張貼圖將在采樣后被轉為對應基於模型UV的每個像素的顏色,最終被顯示出來;
Rect - 一個非2階數大小的貼圖;
Cube - 即Cube map texture(立方體紋理),簡單說就是6張有聯系的2D貼圖的組合,主要用來做反射效果(比如天空盒和動態反射),也會被轉換為對應點的采樣;
Range(min, max) - 一個介於最小值和最大值之間的浮點數,一般用來當作調整Shader某些特性的參數(比如透明度渲染的截止值可以是從0至1的值等);
Float - 任意一個浮點數;
Vector - 一個四維數;
defaultValue 定義了這個屬性的默認值,通過輸入一個符合格式的默認值來指定對應屬性的初始值(某些效果可能需要某些特定的參數值來達到需要的效果,雖然這些值可以在之后在進行調整,但是如果默認就指定為想要的值的話就省去了一個個調整的時間,方便很多)。
Color - 以0~1定義的rgba顏色,比如(1,1,1,1);
2D/Rect/Cube - 對於貼圖來說,默認值可以為一個代表默認tint顏色的字符串,可以是空字符串或者”white”,”black”,”gray”,”bump”中的一個
Float,Range - 某個指定的浮點數
Vector - 一個4維數,寫為 (x,y,z,w)
另外還有一個{option},它只對2D,Rect或者Cube貼圖有關,在寫輸入時我們最少要在貼圖之后寫一對什么都不含的空白的{},當我們需要打開特定選項時可以把其寫在這對花括號內。如果需要同時打開多個選項,可以使用空白分隔。可能的選擇有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一個,這些都是OpenGL中TexGen的模式,具體的留到后面有機會再說。
數據類型:
有3種基本數值類型:float、half和fixed。
這3種基本數值類型可以再組成vector和matrix,比如half3是由3個half組成、float4x4是由16個float組成。
float:32位高精度浮點數。
half:16位中精度浮點數。范圍是[-6萬, +6萬],能精確到十進制的小數點后3.3位。
fixed:11位低精度浮點數。范圍是[-2, 2],精度是1/256。
int,32 位整形數據,有些 profile 會將 int 類型作為 float 類型使用。
bool,布爾數據,通常用於 if 和條件操作符(?:)。
string,字符類型。
sampler*,紋理對象的句柄(the handle to a texture object),分為 6 類: sampler, sampler1D, sampler2D, sampler3D, samplerCUBE,和 samplerRECT。
除了上面的基本數據類型外,Cg 還提供了內置的向量數據類型(built-in vector data types),內置的向量數據類型基 於基礎數據類型。例如:float4,表示 float 類型的 4 元向量;bool4,表示 bool 類型 4 元向量。 注意:向量最長不能超過 4 元,即在 Cg 程序中可以聲明 float1、float2、float3、 float4 類型的數組變量,但是不能聲明超過 4 元的向量,例如:
float5 array;//編譯報錯
向量初始化方式一般為: float4 array = float4(1.0, 2.0, 3.0, 4.0); 較長的向量還可以通過較短的向量進行構建: float2 a = float2(1.0, 1.0); float4 b = float4(a, 0.0, 0.0);
此外,Cg 還提供矩陣數據類型,不過最大的維數不能超過 4*4 階。例如:
float1x1 matrix1;//等價於 float matirx1; x 是字符,並不是乘號!
float2x3 matrix2;// 表示 2*3 階矩陣,包含 6 個 float 類型數據
float4x2 matrix3;// 表示 4*2 階矩陣,包含 8 個 float 類型數據
float4x4 matrix4;//表示 4*4 階矩陣,這是最大的維數
矩陣的初始化方式為: float2x3 matrix5 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
數組數據類型在 Cg 程序中的作用是:作為函數的形參,用於大 量數據的轉遞。 Cg 中聲明數組變量的方式和 C 語言類似:例如:
float a[10];//聲明了一個數組,包含 10 個 float 類型數據
float4 b[10];//聲明了一個數組,包含 10 個 float4 類型向量數據
對數組進行初始化的方式為: float a[4] = {1.0, 2.0, 3.0, 4.0}; //初始化一個數組
要獲取數組長度,可以調用“.length”,例如: float a[10]; //聲明一個數組 int length = a.length;//獲取數組長度
聲明多維數組以及初始化的方式如下所示: 56 float b[2][3] = {{0.0, 0.0, 0.0},{1.0, 1.0, 1.0}};
數組和矩陣有些類似,但是並不是相同。 例如 4*4 階數組的的聲明方式為: float M[4][4];4 階矩陣的聲明方式為:float4x4 M。前者是一個數據結構,包含 16 個 float 類型數據,后者是一個 4 階矩陣數據。float4x4 M[4],表示一個數組,包 含 4 個 4 階矩陣數據。 進行數組變量聲明時,一定要指定數組長度。
數據類型影響性能
精度夠用就好。
顏色和單位向量,使用fixed
其他情況,盡量使用half(即范圍在[-6萬, +6萬]內、精確到小數點后3.3位);否則才使用float。
Tags
表面着色器可以被若干的標簽(tags)所修飾,而硬件將通過判定這些標簽來決定什么時候調用該着色器。比如我們的例子中SubShader的第一句
Tags { "RenderType"="Opaque" "Queue" = "Background" "IgnoreProjector"="True"}(可以多個)
告訴了系統應該在渲染非透明物體時調用我們。Unity定義了一些列這樣的渲染過程,與RenderType是Opaque相對應的顯而易見的是"RenderType" = "Transparent",表示渲染含有透明效果的物體時調用。在這里Tags其實暗示了你的Shader輸出的是什么,如果輸出中都是非透明物體,那寫在Opaque里;如果想渲染透明或者半透明的像素,那應該寫在Transparent中。Tree和grass指定了樹和草的一些標簽。RenderType可以用於shader切換。
另外比較有用的標簽還有"IgnoreProjector"="True"(不被Projectors投影影響),"ForceNoShadowCasting"="True"(從不產生陰影)以及"Queue"="xxx"(指定渲染順序隊列)。這里想要着重說一下的是Queue這個標簽,如果你使用Unity做過一些透明和不透明物體的混合的話,很可能已經遇到過不透明物體無法呈現在透明物體之后的情況。這種情況很可能是由於Shader的渲染順序不正確導致的。Queue指定了物體的渲染順序,預定義的Queue有:
Background - 最早被調用的渲染,用來渲染天空盒或者背景
Geometry - 這是默認值,用來渲染非透明物體(普通情況下,場景中的絕大多數物體應該是非透明的)
AlphaTest - 用來渲染經過Alpha Test的像素,單獨為AlphaTest設定一個Queue是出於對效率的考慮
Transparent - 以從后往前的順序渲染透明物體,絕大部分透明的物體、包括粒子特效都使用這個;
Overlay - 用來渲染疊加的效果,是渲染的最后階段(比如鏡頭光暈等特效)
這些預定義的值本質上是一組定義整數,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最后Overlay = 4000。在我們實際設置Queue值時,不僅能使用上面的幾個預定義值,我們也可以指定自己的Queue值,寫成類似這樣:"Queue"="Transparent+100",表示一個在Transparent之后100的Queue上進行調用。通過調整Queue值,我們可以確保某些物體一定在另一些物體之前或者之后渲染,這個技巧有時候很有用處。
PASS Tags:
RenderState:
Cull Off 剔除某一面
ZWrite Off 深度測試
AlphaTest Less[0.5] 透明度測試,符合條件的才顯示
Blend SrcAlpha OneMinusSrcAlpha 顏色混合,原色*原色系數 + 目標色*目標色混合系數
ColorMask R(過濾R以外的顏色)
ZTest:深度測試:默認是小等於的時候顯示。混合:透過有色玻璃看東西。原始顏色和目標顏色混合。
LOD
LOD很簡單,它是Level of Detail的縮寫,在這里例子里我們指定了其為200(其實這是Unity的內建Diffuse着色器的設定值)。這個數值決定了我們能用什么樣的Shader。在Unity的Quality Settings中我們可以設定允許的最大LOD,當設定的LOD小於SubShader所指定的LOD時,這個SubShader將不可用。Unity內建Shader定義了一組LOD的數值,我們在實現自己的Shader的時候可以將其作為參考來設定自己的LOD數值,這樣在之后調整根據設備圖形性能來調整畫質時可以進行比較精確的控制。
VertexLit及其系列 = 100
Decal, Reflective VertexLit = 150
Diffuse = 200
Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
Bumped, Specular = 300
Bumped Specular = 400
Parallax = 500
Parallax Specular = 600
CGPROGRAM。這是一個開始標記,表明從這里開始是一段CG程序(我們在寫Unity的Shader時用的是Cg/HLSL語言)。最后一行的ENDCG與CGPROGRAM是對應的,表明CG程序到此結束。
接下來是是一個編譯指令:#pragma surface surf Lambert,它聲明了我們要寫一個表面Shader,並指定了光照模型。它的寫法是這樣的
#pragma surface surfaceFunction lightModel [optionalparams]
surface - 聲明的是一個表面着色器
surfaceFunction - 着色器代碼的方法的名字
lightModel - 使用的光照模型。
在我們的例子中,我們聲明了一個表面着色器,實際的代碼在surf函數中(在下面能找到該函數),使用Lambert(也就是普通的diffuse)作為光照模型。
在CG中,sampler2D是和texture所綁定的一個數據容器接口,簡單理解的話,所謂加載以后的texture(貼圖)說白了不過是一塊內存存儲的,使用了RGB(也許還有A)通道,且每個通道8bits的數據。而具體地想知道像素與坐標的對應關系,以及獲取這些數據,我們總不能一次一次去自己計算內存地址或者偏移,因此可以通過sampler2D來對貼圖進行操作。更簡單地理解,sampler2D就是GLSL中的2D貼圖的類型,相應的,還有sampler1D,sampler3D,samplerCube等等格式。
我們用來實例的這個shader其實是由兩個相對獨立的塊組成的,外層的屬性聲明,回滾等等是Unity可以直接使用和編譯的ShaderLab;而現在我們是在CGPROGRAM...ENDCG這樣一個代碼塊中,這是一段CG程序。對於這段CG程序,要想訪問在Properties中所定義的變量的話,必須使用和之前變量相同的名字進行聲明。於是其實sampler2D _MainTex;做的事情就是再次聲明並鏈接了_MainTex,使得接下來的CG程序能夠使用這個變量。
接下來是一個struct結構體。作為輸入的結構體必須命名為Input。float2,表示浮點數的float后面緊跟一個數字2,float和vec都可以在之后加入一個2到4的數字,來表示被打包在一起的2到4個同類型數。在訪問這些值時,我們即可以只使用名稱來獲得整組值,也可以使用下標的方式(比如.xyzw,.rgba或它們的部分比如.x等等)來獲得某個值。在這個例子里,我們聲明了一個叫做uv_MainTex的包含兩個浮點數的變量。UV mapping的作用是將一個2D貼圖上的點按照一定規則映射到3D模型上,是3D渲染中最常見的一種頂點處理手段。在CG程序中,我們有這樣的約定,在一個貼圖變量(在我們例子中是_MainTex)之前加上uv兩個字母,就代表提取它的uv值(其實就是兩個代表貼圖上點的二維坐標 )。我們之后就可以在surf程序中直接通過訪問uv_MainTex來取得這張貼圖當前需要計算的點的坐標值了。
surf函數。上面的#pragma段已經指出了我們的着色器代碼的方法的名字叫做surf,那沒跑兒了,就是這段代碼是我們的着色器的工作核心。我們已經說過不止一次,着色器就是給定了輸入,然后給出輸出進行着色的代碼。CG規定了聲明為表面着色器的方法(就是我們這里的surf)的參數類型和名字,因此我們沒有權利決定surf的輸入輸出參數的類型,只能按照規定寫。這個規定就是第一個參數是一個Input結構,第二個參數是一個inout的SurfaceOutput結構。input其實是需要我們去定義的結構,這給我們提供了一個機會,可以把所需要參與計算的數據都放到這個Input結構中,傳入surf函數使用;SurfaceOutput是已經定義好了里面類型輸出結構,但是一開始的時候內容暫時是空白的,我們需要向里面填寫輸出,這樣就可以完成着色了。
一個SubShader(渲染方案)是由一個個Pass塊來執行的。每個Pass都會消耗對應的一個DrawCall。在滿足渲染效果的情況下盡可能地減少Pass的數量。
和SubShader有自己專屬的Tag類似,Pass也有Pass專屬的Tag。
其中最重要Tag是 "LightMode",指定Pass和Unity的哪一種渲染路徑(“Rendering Path”)搭配使用。除最重要的ForwardBase、ForwardAdd外,這里需額外提醒的Tag取值可包括:
Always,永遠都渲染,但不處理光照
ShadowCaster,用於渲染產生陰影的物體
ShadowCollector,用於收集物體陰影到屏幕坐標Buff里。
其他渲染路徑相關的Tag詳見下面章節“Unity渲染路徑種類”。
具體所有Tag取值,可參考ShaderLab syntax: Pass Tags。
語義詞:
下面這組綁定語義關鍵字被Cg 語言的所有vertex profile 所支持
POSITION 表示該參數中的數據是的頂點位置坐標(通常位於模型空間)
BLENDWEIGHT
NORMAL 表示該參數中的數據是頂點法向量坐標(通常位於模型空間)
TANGENT 頂點切線
BINORMAL
PSIZE
BLENDINDICES
TEXCOORD0---TEXCOORD7 頂點紋理坐標
COLOR 頂點顏色值
下面這些語義詞適用於所有的Cg vertex profiles作為輸出語義和Cg fragment profiles的輸入語義:
POSITION
PSIZE
FOG
COLOR0-COLOR1
TEXCOORD0-TEXCOORD7
如:
struct appdata_base { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; UNITY_INSTANCE_ID }; struct appdata_tan { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; UNITY_INSTANCE_ID }; struct appdata_full { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; #if defined(SHADER_API_XBOX360) half4 texcoord4 : TEXCOORD4; half4 texcoord5 : TEXCOORD5; #endif fixed4 color : COLOR; UNITY_INSTANCE_ID };
動畫:unity會每幀調用vert等入口函數,通過在函數中改變坐標或顏色值形成動畫(常用_Time屬性)