在學(xué)習(xí) Unity Shader 前功炮,最好是對(duì) CG Shader 有一定的了解隔缀,起碼要知道 Shader 數(shù)據(jù)類(lèi)型煮剧,語(yǔ)義這些最基本的東西闻镶。(推薦啃 Nvidia 的《The Cg Tutorial》甚脉,當(dāng)然可以找中文版的來(lái)看,叫《可編程實(shí)時(shí)圖形權(quán)威指南》铆农,這本書(shū)現(xiàn)在已經(jīng)基本買(mǎi)不到正版牺氨,看 PDF 版的吧)
在 Unity 中,Shader 的使用要基于物體和材質(zhì)墩剖,我們可以通過(guò)新建一個(gè)材質(zhì)猴凹,然后指定對(duì)應(yīng)的 Shader 文件,最后把材質(zhì)賦予物體進(jìn)行渲染岭皂。
我們先看一個(gè)比較簡(jiǎn)單的 Unity Shader:
Shader "Ojors/Simple Shader"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct VertexOut
{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
float2 uv_MainTex : TEXCOORD0;
};
VertexOut vert(appdata_base i)
{
VertexOut o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5);
o.uv_MainTex = i.texcoord.xy;
return o;
}
float4 frag(VertexOut i) : SV_Target
{
return float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex);
}
ENDCG
}
}
FallBack "Diffuse"
}
下面開(kāi)始分析這串代碼:
- 首先是第一行代碼:
Shader "Ojors/SimpleShader"
這句代碼表示我們的 Shader 目錄郊霎,定義之后,我們可以在 Shader 列表中根據(jù)該目錄找到我們的 Shader 文件爷绘。
- Properties 部分
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
在這里我們可以定義一些可以調(diào)節(jié)的參數(shù)书劝,通過(guò)在 Unity Inspector 面板對(duì)屬性進(jìn)行調(diào)節(jié),在 Scene 面板中實(shí)時(shí)觀察效果土至。
上述代碼中:
_MainTex 表示在 Shader 代碼中定義的屬性名
"Base (RGB)" 表示在 Inspector 面板中顯示的名稱(chēng)
2D 表示該屬性的類(lèi)型购对,在此處為 ShaderLab 2D 貼圖
"white" {} 表示該屬性的默認(rèn)值為了可以在我們的代碼中使用該屬性,我們還需要在我們的CG代碼中定義該變量陶因,該變量的類(lèi)型和命名必須與屬性定義一致骡苞。
sampler2D _MainTex;
有的Shader代碼中會(huì)定義成:uniform sampler2D _MainTex;
在Unity Shader中,這兩種寫(xiě)法效果一樣楷扬,uniform 關(guān)鍵字是可以省略的
屬性與變量類(lèi)型關(guān)系如下:
屬性類(lèi)型 | CG變量類(lèi)型 | 定義方式 |
---|---|---|
Int | int | _IntType ("Count", Int) = 1 |
Float | float解幽,half,fixed | _FloatType("Rate", Float) = 1.5 |
Vector | float4毅否,half4亚铁,fixed4 | _VectorType ("Vector", Vector) = (1,2,3,4) |
Range | float,half螟加,fixed | _RangeType ("Range", (0.1, 0.5)) = 0.2 |
Color | float4徘溢,half4,fixed4 | _ColorType ("Color", Color) = (1,1,1,1) |
2D | sampler2D | _2DType ("2DTex", 2D) = ""{} |
Cube | samplerCube | _CubeType ("CubeTex", Cube) = "white"{} |
3D | sampler3D | _3DType ("3DTex", 3D) = "black"{} |
其中貼圖屬性的字符串要么為空("")捆探,要么為內(nèi)置的紋理名稱(chēng)("white", "black", "gray", "bump")
SubShader 塊
在一個(gè) Shader 中然爆,可能會(huì)包含一個(gè)或多個(gè) SubShader。Unity在加載 Shader 時(shí)黍图,會(huì)對(duì)文件里的 SubShader 進(jìn)行順序掃描曾雕,然后選擇第一個(gè)能在當(dāng)前平臺(tái)下運(yùn)行的 SubShader,如果都不行助被,就會(huì)執(zhí)行 Fallback 所指定的 ShaderPass 塊
在一個(gè) SubShader 中會(huì)有一個(gè)或多個(gè) Pass剖张。不同于 SubShader 在執(zhí)行某個(gè) SubShader 時(shí)切诀,Unity 會(huì)把里面定義的 Pass 都執(zhí)行一次。而多個(gè) Pass 可以提高代碼的復(fù)用率和實(shí)現(xiàn)一些復(fù)雜的效果.
UsePass "MyShader/PassName"
上面的使用方式可以對(duì)已經(jīng)寫(xiě)好的 Pass 進(jìn)行復(fù)用搔弄。
- 主體 Shader 代碼部分
CGPROGRAM
...
ENDCG
在 CGPROGRAM-ENDCG 關(guān)鍵字中的代碼就是我們用CG語(yǔ)言所寫(xiě)的 Shader 代碼幅虑。
當(dāng)然也可以 OpenGL 的 Shader 代碼,那就要包含在 GLSLPROGRAM-ENDGLSL 關(guān)鍵字中顾犹。
#pragma vertex vert
#pragma fragment frag
這里定義了頂點(diǎn)著色器和片段著色器的函數(shù)聲明倒庵,Unity 可以在這里知道哪里是頂點(diǎn)著色器的入口,哪里是片段著色器的入口炫刷。我們需要做的是在這兩個(gè)函數(shù)里編寫(xiě)我們的代碼擎宝。(這系列會(huì)以頂點(diǎn)著色器和片段著色器為主,而 Unity 新的表面著色器先不探討)
#include "UnityCG.cginc"
這句代碼表示包含 Unity 自帶的 UnityCG.cginc 頭文件浑玛,這里包含了 Unity 為我們提供的很多很使用的變量和常量绍申,在現(xiàn)在的 Unity 版本(4.0以上)中會(huì)自動(dòng)包含進(jìn)來(lái),但是為了兼容性還是加上為好锄奢。
sampler2D _MainTex;
這里為定義屬性上對(duì)應(yīng)的變量失晴,也就是上面說(shuō)過(guò)的 Properties 部分
struct VertexOut
{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
float2 uv_MainTex : TEXCOORD0;
};
這里定義了一個(gè)頂點(diǎn)著色器數(shù)據(jù)輸出到片段著色器用的結(jié)構(gòu)體,變量后的為語(yǔ)義
例如:float4 pos : SV_POSITION;
這里定義了一個(gè) float4 類(lèi)型的位置變量拘央,語(yǔ)義為 SV_POSITION涂屁,表示這是一個(gè)坐標(biāo)點(diǎn)(很多其他 Shader 代碼中會(huì)使用 POSITION 語(yǔ)義,SV_ 前綴與沒(méi)有前綴其實(shí)沒(méi)有多大區(qū)別灰伟,但是為了平臺(tái)兼容性我們還是使用 SV_ 前綴拆又,例如 PS4 平臺(tái)使用的就是帶前綴的語(yǔ)義)
而下面的 COLOR0 和 TEXCOORD0 則表示是顏色和貼圖,后面的數(shù)字0栏账,1帖族,2等等之類(lèi)的表示這是第一組顏色(貼圖),第二組顏色(貼圖)挡爵,具體的數(shù)字可以到多少要看顯卡性能竖般。
VertexOut vert(appdata_base i)
{
VertexOut o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5);
o.uv_MainTex = i.texcoord.xy;
return o;
}
這個(gè)代碼塊為頂點(diǎn)著色器的代碼塊。返回類(lèi)型為我們之前所定義的 VertexOut 結(jié)構(gòu)體茶鹃,參數(shù)類(lèi)型為 UnityCG.cginc 自帶的數(shù)據(jù)結(jié)構(gòu)體涣雕,里面包含了很多數(shù)據(jù)相關(guān)的數(shù)值,例如:頂點(diǎn)闭翩、法線(xiàn)挣郭、貼圖等,具體內(nèi)容可以參照 Unity\Editor\Data\CGIncludes 目錄下的 UnityCG.cginc 文件疗韵。
下面給出這三個(gè)常用的數(shù)據(jù)結(jié)構(gòu)體:
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;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
half4 texcoord4 : TEXCOORD4;
half4 texcoord5 : TEXCOORD5;
#endif
fixed4 color : COLOR;
};
然后是下面的代碼:
VertexOut o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5);
o.uv_MainTex = i.texcoord.xy;
return o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex):進(jìn)行從模型空間到裁剪空間的矩陣變換兑障,這個(gè)在前面的篇幅中已經(jīng)說(shuō)明過(guò),這里不再贅述。
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5) :這里是對(duì)頂點(diǎn)的顏色進(jìn)行計(jì)算流译。
o.uv_MainTex = i.texcoord.xy:這里是對(duì)輸入的貼圖的 UV 信息對(duì)輸出結(jié)構(gòu)體進(jìn)行賦值
float4 frag(VertexOut i) : SV_Target
{
return float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex);
}
這個(gè)代碼塊為片段著色器的代碼塊逞怨。其中輸入的參數(shù)為從頂點(diǎn)著色器中返回的數(shù)據(jù)結(jié)構(gòu)體。函數(shù)后面的 SV_Target 語(yǔ)義意思為輸出的是顏色數(shù)據(jù)(一些 Shader 中會(huì)把語(yǔ)義寫(xiě)成 COLOR先蒋,跟上面說(shuō)的一樣骇钦,SV_Target 跟 COLOR 沒(méi)有多大區(qū)別宛渐,基于平臺(tái)兼容性我們還是選擇 SV_Target 語(yǔ)義)
而返回的數(shù)據(jù) float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex) 為對(duì)上面返回的顏色和貼圖進(jìn)行混合竞漾,生成最終的效果(tex2D 函數(shù)為取樣函數(shù),能從貼圖中按照 UV 坐標(biāo)獲取到對(duì)應(yīng)點(diǎn)的顏色窥翩,在低版本的顯示驅(qū)動(dòng)中不允許在頂點(diǎn)著色器中進(jìn)行取樣业岁,若真要使用要使用 tex2Dlod 函數(shù),并添加 #pragma target 3.0寇蚊,因?yàn)?tex2Dlod 是 Shader Model 3.0 中的特性)
好了笔时,到此為止,我們的第一個(gè) Shader 文件已經(jīng)解釋完畢仗岸,下面上一下 Shader 效果圖:
下面給出在書(shū)上看到的我覺(jué)得比較重要的優(yōu)化點(diǎn):
不鼓勵(lì)在 Shader 中使用流程控制語(yǔ)句(if-else允耿,for,while等)扒怖,因?yàn)闀?huì)降低 GPU 的并行處理效率较锡。
幾條建議:
- 把片段著色器上的計(jì)算放到頂點(diǎn)著色器中,或者在 CPU 中進(jìn)行預(yù)計(jì)算
- 分支判斷語(yǔ)句的條件最好是常量
- 每個(gè)分支中的操作盡可能少
- 分支嵌套盡可能少