Unity-教程:頂點(diǎn)和片元程序
本教程將教您如何在 Unity 著色器中編寫頂點(diǎn)和片元程序的基礎(chǔ)知識(shí)。有關(guān) ShaderLab 的基本介紹,請(qǐng)參閱入門教程。如果要編寫與光照交互的著色器,請(qǐng)閱讀表面著色器。
讓我們先簡單回顧一下著色器的一般結(jié)構(gòu):
最后我們介紹一個(gè)新命令:__FallBack “VertexLit”。Fallback?命令可以在著色器的末尾使用;如果當(dāng)前著色器中沒有__子著色器__可以在用戶的圖形硬件上運(yùn)行,該命令會(huì)告訴應(yīng)該使用哪個(gè)著色器。效果等同于在結(jié)尾包含回退著色器中的所有子著色器。例如,如果您要編寫一個(gè)花哨的法線貼圖著色器,那么您可以回退到內(nèi)置的?VertexLit__ 著色器,而不必為舊顯卡編寫一個(gè)非?;镜姆欠ň€貼圖子著色器。
第一個(gè)著色器教程中介紹了著色器的基本構(gòu)建塊,同時(shí)也提供了關(guān)于屬性、子著色器和通道的完整文檔。
構(gòu)建子著色器的一種快速方法是使用在其他著色器中定義的通道。命令?UsePass?就能執(zhí)行此操作,因此您可以通過簡潔的方式重用著色器代碼。例如,以下命令使用內(nèi)置?Specular?著色器中名為“FORWARD”的通道:?UsePass “Specular/FORWARD”。
要讓?UsePass?運(yùn)行,必須為要使用的通道提供一個(gè)名稱。通道中的?Name?命令將為其提供名稱:__Name “MyPassName”__。
頂點(diǎn)和片元程序
我們?cè)诘谝粋€(gè)教程中描述了僅使用單個(gè)紋理組合指令的通道。現(xiàn)在演示如何在通道中使用頂點(diǎn)和片元程序。
使用頂點(diǎn)和片元程序(所謂的“可編程管線”)時(shí),圖形硬件中的大多數(shù)硬編碼功能(“固定函數(shù)管線”)將被關(guān)閉。例如,使用頂點(diǎn)程序會(huì)完全關(guān)閉標(biāo)準(zhǔn) 3D 變換、光照和紋理坐標(biāo)生成。同樣,使用片元程序會(huì)取代將在 SetTexture 命令中定義的任何紋理組合模式;因此不需要 SetTexture 命令。
編寫頂點(diǎn)/片元程序需要精通 3D 變換、光照和坐標(biāo)空間,因?yàn)槟仨氉约褐貙?OpenGL等 API 中內(nèi)置的固定功能。另一方面,您不僅僅可以使用內(nèi)置功能!
在 ShaderLab 中使用 Cg/HLSL
ShaderLab 中的著色器通常是用?Cg/HLSL?編程語言編寫的。Cg 和 DX9 風(fēng)格的 HLSL 實(shí)際上是同一種語言,所以我們將互換使用 Cg 和 HLSL(有關(guān)詳細(xì)信息,請(qǐng)參閱本頁)。
著色器代碼是通過在著色器文本中嵌入“Cg/HLSL 代碼片段”來編寫的。Unity Editor 將代碼片段編譯為低級(jí)著色器程序集,游戲數(shù)據(jù)文件中包含的最終著色器僅包含此低級(jí)程序集或字節(jié)碼(根據(jù)平臺(tái)而定)。在 __Project 視圖__中選擇著色器時(shí),檢視面板有一個(gè)按鈕可顯示已編譯的著色器代碼,這可以作為調(diào)試助手。Unity 自動(dòng)編譯所有相關(guān)平臺(tái)(Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)的 Cg 代碼片段。請(qǐng)注意,由于 Cg/HLSL 代碼是由 Editor 編譯的,因此無法在運(yùn)行時(shí)從腳本創(chuàng)建著色器。
通常,代碼片段放在 Pass 代碼塊內(nèi)。如下所示:
Pass {
? ?// ...常規(guī)通道狀態(tài)設(shè)置 ...
? ?CGPROGRAM
? ?// 此代碼片段的編譯指令,例如:
? ?#pragma vertex vert
? ?#pragma fragment frag
? ?// Cg/HLSL 代碼本身
? ?ENDCG
? ?// ...通道設(shè)置的剩余部分 ...
}
以下示例演示了一個(gè)完整的著色器,它將對(duì)象法線渲染為彩色:
當(dāng)應(yīng)用于對(duì)象時(shí),它將生成如下圖像:

我們的“Display Normals”著色器沒有任何屬性,包含一個(gè)子著色器,子著色器只有一個(gè)通道,通道中除了 Cg/HLSL 代碼外無任何其他內(nèi)容。讓我們逐個(gè)剖析代碼:
整個(gè)代碼片段寫在?CGPROGRAM?與?ENDCG?關(guān)鍵字之間。在開頭,編譯指令以?#pragma?語句形式給出:
#pragma vertex name?表示代碼包含給定函數(shù)中的頂點(diǎn)程序(此處為 __vert__)。
#pragma fragment name?表示代碼包含給定函數(shù)中的片元程序(此處為 __frag__)。
編譯之后的指令只是普通的 Cg/HLSL 代碼。我們首先包含一個(gè)內(nèi)置 include 文件:
# include "UnityCG.cginc"
UnityCG.cginc?文件包含常用的聲明和函數(shù),以便著色器可以保持較?。ㄓ嘘P(guān)詳細(xì)信息,請(qǐng)參閱著色器 include 文件頁面)。這里我們將使用該文件中的?appdata_base?結(jié)構(gòu)。我們可以直接在著色器中定義它們,當(dāng)然不包括文件。
接下來我們定義一個(gè)“頂點(diǎn)到片元”結(jié)構(gòu)(這里名為 __v2f__):從頂點(diǎn)傳遞給片元程序的信息。我們將傳遞位置和顏色參數(shù)。顏色將在頂點(diǎn)程序中計(jì)算,并在片元程序中輸出。
我們繼續(xù)定義頂點(diǎn)程序,即?vert?函數(shù)。在這里,我們將位置和輸出輸入法線計(jì)算為顏色: o.color = v.normal * 0.5 + 0.5;
法線分量在 –1 到 1 范圍內(nèi),而顏色在 0 到 1 范圍內(nèi),因此我們?cè)谏厦娴拇a中縮放和偏置法線。接下來我們定義一個(gè)片元程序,即?frag?函數(shù),該函數(shù)只輸出計(jì)算所得顏色,以 1 為 Alpha 分量:
就是這樣,我們的著色器完成了!即使這個(gè)簡單的著色器對(duì)于可視化網(wǎng)格法線也非常有用。
當(dāng)然,這個(gè)著色器根本不會(huì)對(duì)光源做出響應(yīng),這就是事情變得有趣的地方;有關(guān)詳細(xì)信息,請(qǐng)參閱表面著色器。
在 Cg/HLSL 代碼中使用著色器屬性
在著色器中定義屬性時(shí),您將為它們指定一個(gè)名稱,如?_Color?或?_MainTex。要在 Cg/HLSL 中使用它們,只需定義匹配名稱和類型的變量。有關(guān)詳細(xì)信息,請(qǐng)參閱著色器程序中的屬性頁面。
下面是一個(gè)完整的著色器,顯示由顏色調(diào)制的紋理。當(dāng)然,您也可以在紋理組合器調(diào)用中輕松進(jìn)行相同操作,但這里的重點(diǎn)只是展示如何在 Cg 中使用屬性:
此著色器的結(jié)構(gòu)與前一個(gè)示例中的相同。這里我們定義兩個(gè)屬性,即?_Color?和?_MainTex。在 Cg/HLSL 代碼中,我們定義了相應(yīng)的變量:
fixed4 _Color;
sampler2D _MainTex;
有關(guān)更多信息,請(qǐng)參閱使用 Cg/HLSL 訪問著色器屬性。
這里的頂點(diǎn)和片元程序沒有任何花哨的東西;頂點(diǎn)程序使用 UnityCG.cginc 中的?TRANSFORM_TEX?宏來確保正確應(yīng)用紋理比例和偏移,片元程序只是對(duì)紋理進(jìn)行采樣并乘以顏色屬性。
總結(jié)
我們已經(jīng)展示了如何通過幾個(gè)簡單的步驟編寫自定義著色器程序。雖然這里顯示的示例非常簡單,但您可以編寫復(fù)雜的任意著色器程序!這可以幫助您充分利用 Unity 并獲得最佳渲染效果。
此處是完整的 ShaderLab 參考手冊(cè),頂點(diǎn)和片元著色器程序示例頁面中提供了更多示例。我們還在?forum.unity3d.com?上有一個(gè)著色器論壇,請(qǐng)?jiān)L問這個(gè)論壇以獲取與著色器相關(guān)的幫助!快樂編程,享受 Unity 和 ShaderLab 的強(qiáng)大功能。