Unity3D shader簡介
可以肯定的說Unity3D使得很多開發者開發游戲更容易。毫無疑問,shader(着色器)編碼,仍有很長的路要走。shader是一個專門運行在GPU的程序,經常被神秘包圍,它最終繪制3D模型的三角形。如果你想給游戲一個特殊的顯示,學習如何編寫shader是必要的。Unity3D使用shader做后期處理,對2D游戲也是必不可少的。這個系列的文章將逐步介紹shader編程,並面向幾乎沒有任何shader知識的開發者。
簡介
下圖大致表示了在Unity3D渲染流程中發揮作用的3個不同實體:
3D模型本質上是,被稱為頂點的3D坐標集合。他們連接在一起構成一些三角形。每個頂點包含一些其它的信息,如顏色、點指的方向(法線)、紋理映射坐標(UV數據)。
沒有材質模型是不能被渲染的。材質包含一個shader和其屬性值的封裝。因此,不同材質可以共享相同的shader,賦予不同的數據。
shader剖析
Unity3D支持兩種不同的shader:表面shader、片段和頂點shader。還有第三種類型:固定管線shader,但是如今已經過時了,將不包含在本系列文章。無論你需要的是哪種類型,shader的結構都一樣:
Shader "MyShader"
{
Properties
{
// The properties of your shaders
// - textures
// - colours
// - parameters
// ...
}
SubShader
{
// The code of your shaders
// - surface shader
// OR
// - vertex and fragment shader
// OR
// - fixed function shader
}
}
|
可以包含多個SubShader,一個接一個。他們包含GPU的實際指令。Unity3D將找到與你顯卡兼容的SubShader,並順序執行他們。這對多平台編碼是非常有用,因為你可以在一個文件中編寫同一shader的不同版本。
屬性
shader的屬性在某種程度上相當於C#腳本中的public字段,他們將出現在材質的inspector面板,給你機會來調整。但不像腳本,材質是資源:編輯中游戲運行時修改材質的屬性值是永久的。甚至游戲停止后,修改的屬性值也是有效的。
下面的代碼片段涵蓋了你可以在shader中使用的所有基本類型的定義:
3~4行中的2D,表示參數是紋理。他們可以初始化為white、black、gray。你也可以使用bump表示使用法線貼圖,這種情況下,它將自動初始為顏色#808080,這用來表示沒有任何凸凹。Vector和Color總是有4個元素(分別為xyzw和rgba)。
下圖展示了一個shader附着到一個材質上之后,在inspector面板中是如何顯示的。
不幸的是,這對我們使用屬性還不夠。事實上,Properties塊被Unity3D用來從inspector訪問shader隱藏變量。這些變量仍需定義在shader中,它們包含在SubShader塊中。
紋理的類型為sampler2D。向量是float4、顏色一般是half4,分別使用32和16位表示。用來編寫shader的語言為Cg/HLSL,很迂腐:參數的名稱必須與先前定義的匹配。然而類型不需要,例如把_MyRange聲明為half而不是float不會報任何錯誤。一些令人困惑的是,如果你定一個向量類型的屬性,關聯到一個float2變量,額外的2個值將被Unity3D忽略。
渲染順序
正如已提到的,SubShader塊包含實際代碼,使用Cg/HLSL(非常像C)編寫。不嚴格地說,一個shader為圖像的每個像素執行,它的性能非常關鍵。由於GPU的體系結構,一個shader中能夠執行的指令數量有限制。可以通過分割幾個部分執行,但本教程不包含這塊。
一個SubShader通常看起來像這樣:
第8~11行包含實際的Cg代碼,通過CGPROGRAM和ENDCG指令示意。
第3行,在實際代碼之前,介紹tags的概念。tags用來告訴Unity3D我們寫的shader的某些屬性。例如,shader渲染的順序(Queue)和應該如何渲染(RenderType)。
當渲染三角形時,GPU通常根據它們離攝像機的距離,遠的先繪制。這在渲染不透明的幾何形狀時夠用了,但是透明物體將失敗。這也是為什么Unity3D運行指定Queue標簽,可以控制每個材質的渲染順序。Queue接受正整數(越小越先繪制),預定義(mnemonic)的標簽也可以使用:
l Background(1000):用於背景和天空盒
l Geometry(2000):默認標簽,用於大部分不透明物體
l Transparent(3000):用於包含透明屬性的材質,例如玻璃、火、粒子、水
l Overlay(4000):用於鏡頭耀斑,GUI元素和文本
Unity3D還允許指定相關順序,例如Background+2,表示1002隊列值。搞亂了Queue值會導致惡劣的情況,一個對象總是被繪制,即使它應該被其他模型遮擋住了。
Ztest
記住,一個包含透明屬性(Transparent)的對象並不總是顯示在不透明(Geometry)對象上面。默認情況下,GPU執行ZTest避免隱藏的對象被繪制。原理是,它使用了一個額外的緩沖區,其大小與屏幕渲染的相同。每個像素包含繪制對象在該像素的深度(離相機的距離)。如果我們要繪制一個像素比當前深度更大,像素就被丟棄。ZTest剪裁被其它對象遮擋住的像素,無論他們繪制到屏幕上的順序。
表面 VS 頂點和片段
shader代碼的最后一部分。在渲染之前,需要決定使用那種類型的shader。本節將給出shader效果驚鴻一瞥的樣子,但不會深入解釋。表面、頂點和片段shader將在本教材的下一部分覆蓋。
表面shader
當材質需要根據光照模擬實際效果時,那么你就需要一個表面shader。表面shader在函數surf中隱藏光線如何被反射的計算,並允許指定“直觀”的屬性,如反照率、法線、反射率等。然后將這些值插入到光照模型,將輸出每個像素的最終RGB值。或者,當需要非常高級的效果是,你也可以編寫自己的光照模型。
一個典型的表面shader的Cg代碼如下所示:
第5行指定輸入的紋理,然后在第12行將指定Albedo屬性。第3行指定使用Lambertian光照模型,這個是非常典型光照如何影響對象的模型。shader僅使用反照率屬性通常稱為漫反射(diffuse)。
頂點和片段shader
頂點和片段shader的工作貼近GPU渲染三角形的方式,並沒有光照如何表現的概念。模型的幾何形狀首先通過一個調用函數vert,改變他的頂點。然后各個三角形通過另一個調用函數frag,這決定了最終每個像素的RGB顏色。這對2D效果非常有用,后期處理和特殊的3D效果非常付出需要使用表面shader。
下面的頂點和片段shader簡單只是使用紅色,沒有光照:
第15~17行,將原始的3D空間頂點轉換到最終的2D屏幕位置。Unity3D使用UNITY_MATRIX_MVP實現,隱藏了底層的實現數學運算。在此之后,第22行,給定每個像素為紅色。只需要記住,頂點和片段shader的Cg部分需要封裝到Pass塊中。它跟簡單的表面shader不一樣。
結論
本篇文章逐漸引入了兩種類型的Unity3D shader,並說明何時需要使用。接下來四篇文章,將介紹實現它們的細節。還有一個附加篇,將介紹屏幕shader,用於2D圖像的后期處理。
- Part 1: Unity3D shader簡介(A gentle introduction to shaders in Unity3D)
- Part 2: Unity3D表面shader(Surface shaders in Unity3D)
- Part 3: Unity3D基於物理渲染和光照模型( Physically Based Rendering and lighting models in Unity3D)
- Part 4: Unity3D頂點和片段shader(Vertex and fragment shader in Unity3D)
- Part 5: Unity3D傳遞數組給shader(Passing arrays to a shader: heatmaps in Unity3D)
- Part 6: Unity3D屏幕shader和效果后期處理(Screen shaders and postprocessing effects in Unity3D)