Unity Shader入門精要學習筆記 - 第3章 Unity Shader 基礎
來源作者:candycat http://blog.csdn.net/candycat1992/article/
概述
總體來說,在Unity中我們需要配合使用材質和Unity Shader才能達到需要的效果。一個最常見的流程是。
1)創建一個材質
2)創建一個Unity Shader,並把它賦給上一步創建的材質
3)把材質賦給要渲染的對象
4)在材質面板中調整Unity Shader的屬性,以得到滿意的效果
下圖顯示了Unity Shader和材質是如何一起工作來控制物體的渲染的。
Unity中的材質需要結合一個GameObject的Mesh 或者Particle Systems 組件來工作。它決定了我們的遊戲對象看起來是什麽樣子的(這當然也需要Unity Shader的配合)。
Unity Shader 的基礎:ShaderLab
Unity Shader 為控制渲染過程提供了一層抽象。如果沒有使用Unity Shader(上左),開發者需要很多文件設置打交道,才能讓畫面呈現想要的效果,而在Unity Shader的幫助下(上右),開發者只需要使用ShaderLab來編寫Unity Shader文件就可以完成所有的工作。
在Unity中,所有的Unity Shader 都是使用ShaderLab來編寫的。ShaderLab是Unity提供的編寫Unity Shader 的一種說明性語言。它使用了一些嵌套在花括號內部的語義來描述一個Unity Shader文件的結構。這些結構包含了許多渲染所需的數據,例如Properties 語句塊中定義了著色器所需的各種屬性,這些屬性將會出現在材質面板中。從設計上來說,ShaderLab類似於CgFx的Direct3D Effects (.Fx)語言,它們都定義了要顯示一個材質所需要的所有東西,而不僅僅是著色器代碼。
一個Unity Shader 的基礎結構如下所示
Unity 會在背後根據使用的平臺來把這些結構編譯成真正的代碼和Shader 文件,而開發者只需要和Unity Shader 打交道即可。
Unity Shader 的結構
每個Unity Shader文件的第一行都需要通過Shader語義來指定該Unity Shader的名字。這個名字由一個字符串來定義,例如"MyShader"。當為材質選擇使用的Unity Shader 時,這些名稱就會出現在材質面板的下拉面板裏。通過在字符串中添加斜杠(“/”),可以控制Unity Shader在材質面板中出現的位置。
Properties語義塊中包含了一系列屬性,這些屬性將會出現在材質面板中。
Properties語義塊通常定義如下:
Properties { Name ("display name",PropertyType) = DefaultValue }
開發者們聲明這些屬性是為了在材質面板中能夠方便地調整各種材質屬性。如果我們需要再Shader 中訪問它們,就需要使用每個屬性的名字。在Unity中,這些屬性的名字通常由一個下劃線開始。顯示的名稱則是出現在材質面板上的名字。我們需要為每個屬性指定它的類型,常見的屬性類型如下表。除此之外,我們還需要為每個屬性指定一個默認值,在我們第一次把該Unity Shader賦給某個材質,材質面板上顯示的就是這些默認值。
每個Unity Shader 文件可以包含多個SubShader語義塊,但最少要有一個,當Unity需要加載這個Unity Shader時,Unity 會掃描所有的SubShader 語義塊,然後選擇第一個能夠在目標平臺上運行的SubShader。如果都不支持的話,Unity就會使用Fallback語義指定的Unity Shader。
Unity提供這種語義的原因在於,不同的顯卡具有不同的能力。
SubShader語義塊中包含的定義通常如下。
SubShader中定義了一系列Pass以及可選的狀態和標簽設置。每個Pass定義了一次完整的渲染流程,但如果Pass的數目過多,往往會造成渲染性能的下降。因此,我們應盡量使用最小數目的Pass。狀態和標簽同樣可以在Pass聲明。不同的是,SubShader中的一些標簽設置是特定的。也就是說,這些標簽設置和Pass中使用的標簽是不一樣的。而對於狀態設置來說,其使用的語法是相同的。但是,如果我們在SubShader進行了這些而設置,那麽將會用於所有的Pass。
ShaderLab提供了一系列渲染狀態的設置指令,這些指令可以設置顯卡的各種狀態,下表給出了ShaderLab中常見的渲染狀態設置選項。
當在SubShader塊中設置了上述渲染狀態時,將會應用到所有的Pass。如果我們不想這樣(例如在雙面渲染中,我們希望在第一個Pass中剔除正面來對背面進行渲染,在第二個Pass中剔除背面來對正面進行渲染),可以在Pass語義塊中單獨進行上面的設置。
SubShader的標簽是一個鍵值對,它的鍵和值都是字符串類型。這些鍵值對是SubShader和渲染引擎之間的溝通橋梁。它們用來告訴Unity的引擎:SubShader我希望怎樣以及何時渲染這個對象。支持的標簽類型如下:
需要註意的是,上述標簽僅可以在SubShader中聲明,而不可再Pass塊中聲明。Pass塊雖然也可以定義標簽,但這些標簽是不同於SubShader的標簽類型。
Pass語義塊的定義如下:
首先,我們可以在Pass中定義該Pass的名稱,例如:
Name "MyPassName"
通過這個名稱,我們可以使用ShaderLab的UsePass命令來直接使用其他Unity Shader 中的Pass,例如:
UsePass "MyShader/MYPASSNAME"
這樣可以提高代碼的復用性。需要註意的是,由於Unity內部會把所有Pass的名稱轉換成大寫字母的表示,因此,在使用UsePass命令時必須使用大寫形式的名字。
其次,我們可以對Pass設置渲染狀態。SubShader的狀態設置通用適用於Pass。除了上面提到的狀態設置外,在Pass中我們還可以使用固定渲染管線的著色器命令。
Pass同樣可是設置標簽,但它的標簽不同於SubShader的標簽。這些標簽頁是用於告訴渲染引擎我們希望怎樣來渲染該物體。下表給出了Pass中使用的標簽類型。
除了上面普通的Pass定義外,Unity Shader 還支持一些特殊的Pass,以便進行代碼復用或實現更復雜的效果。
緊跟著在各個SubShader語義塊後面的,可以是一個Fallback指令。它用於告訴Unity,如果上面所有的SubShader在這塊顯卡上都不能允許,那就使用這個最低級的吧。
它的語義如下:
Fallback "name" //或者 Fallback off
如上所述,我們可以通過一個字符串來告訴Unity這個最低級的Shader是誰,我們也可以關閉Fallback 功能。
事實上,Fallback 還會影響陰影的投射。在渲染陰影紋理時,Unity會在每個Unity Shader中尋找一個陰影投射的Pass。通常情況下,我們不需要自己專門實現一個Pass,這是因為Fallback使用的內置Shader包含了這樣一個通用的Pass。因此,為每個Unity Shader正確設置Fallback是非常重要的。
Unity Shader 的形式
盡管Unity Shader可以做的事情非常多,但其最重要的任務還是指定各種著色器所需的代碼。這些著色器代碼可以寫在SubShader語義塊中(表面著色器的做法),也可以寫在Pass語義塊中(頂點/片元著色器和固定函數著色器的做法)。
在Unity中,我們可以使用下面3種形式來編寫Unity Shader。而不管使用哪種形式,真正意義上的Shader代碼都需要包含在ShaderLab 語義塊中,如下所示:
Shader "MyShader" { Properties { //所需的各種屬性 } SubShader { //真正意義上的Shader代碼會出現在這裏 //表面著色器或者頂點/片元著色器或者固定函數著色器 }
表面著色器是Unity自己創造的一種著色器代碼類型。它需要的代碼量很少,Unity在背後做了很多工作,但渲染的代價比較大。它在本質上和下面要講到的頂點/片元著色器是一樣的。也就是說,當給Unity提供一個表面著色器的時候,它在背後仍舊把它轉換成對應的頂點/片元著色器。我們可以理解成,表面著色器是Unity對頂點/片元著色器的更高一層的抽象。它存在的價值在於,Unity為我們處理了很多光照細節,使得我們不需要操心這些煩人的事情。
一個非常簡單表面著色器實例代碼如下:
Shader "Custom/Simple Surface Shader" { SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float4 color : COLOR; }; void surf(INPUT IN,inout SurfaceOutput o) { o.Albedo = 1; } ENDCG } Fallback "Diffuse" }
從上述程序中可以看出,表面著色器被定義在SubShader語義塊中的CGPROGRAM 和 ENDCG之間。原因是,表面著色器不需要開發者關心使用多少個Pass、每個Pass如何渲染等問題,Unity會在背後為我們做好這些事情。我們要做的只是告訴它:“使用這些紋理去填充顏色,使用這個法線紋理去填充法線,使用Lambert光照模型。”
CGPROGRAM 和 ENDCG之間的代碼是使用CG/HLSL 編寫的,也就是說,我們需要把CG/HLSL語言嵌套在ShaderLab語言中。值得註意的是,這裏的CG/HLSL是Unity經封裝後提供的,它的語法和標準的CG/HLSL語法幾乎一樣,但還是由細微不同的,例如有些原聲的函數和用法Unity並沒有提供支持。
在Unity中我們可以使用CG/HLSL 語言來編寫頂點/片元著色器。它們更加復雜,但靈活性也更高。
一個非常簡單的頂點/片元著色器示例代碼如下:
Shader "Custom/Simple VertextFrament Shader" { SubShader { Pass { CGPROGRAM #pragma vertext vert #pragma frament frag float4 vert(float4 v : POSITION) : SV_POSITION { return mul (UNITY_MAX_MVP,v); } float4 frag() : SV_Target { return fixed(1.0,1.0,1.0,1.0); } ENDCG } } }
和表面著色器類似,頂點/片元著色器的代碼也需要定義在CGPROGRAM 和 ENDCG之間,但不同的是,頂點/片元著色器是寫在Pass語義塊內,而非SubShader內的。原因是,我們需要自己定義每個Pass需要使用的Shader代碼。雖然我們可能需要編寫更多的代碼,但帶來的好處是靈活性很高。更重要的是,我們可以控制渲染的實現細節。同樣,這裏的CGPROGRAM和ENDCG之間的代碼也是使用CG/HLSL編寫的。
上述兩種Unity Shader 形式都使用了可編程管線。而對於一些較舊的設備,它們不支持可編程管線著色器,因此,這時候我們就需要使用固定函數著色器來完成渲染。這些著色器往往只可以完成一些非常簡單的效果。
一個非常簡單的固定函數著色器示例代碼如下:
Shader "Tutorial/Basic" { Properties{ _Color ( "Main Color" , Color) = (1,0.5,0.5,1) } SubShader{ Pass{ Material{ Diffuse[_Color] } Lightinh On } } }
可以看出,固定函數著色器的代碼被定義在Pass語義塊中,這些代碼相當於Pass中的一些渲染設置。
對於固定函數著色器來說,我們需要完全使用ShaderLab的語法來編寫,而非使用CG/HLSL。
由於現在絕大多數GPU都支持可編程的渲染管線,這種固定管線 編程方式已經逐漸被拋棄。實際上,在Unity5.2中,所有固定函數著色器都會在背後Unity被編譯成對應的頂點/片元著色器,因此真正意義上的固定函數著色器已經不存在了。
如何選擇哪種Unity Shader:
除非有非常明確的需求必須要使用固定函數著色器,例如需要在非常舊的設備上運行遊戲,否則不要使用固定函數著色器。
如果想和各種光源打交道,你可能更喜歡使用表面著色器,但需要小心它在移動平臺的性能表現。
如果光照數量少,例如只有一個平行光,那麽使用頂點/片元著色器是一個更好的選擇。
更重要的是,如果有很多自定義的渲染效果,那麽頂點/片元著色器更好。
Unity Shader入門精要學習筆記 - 第3章 Unity Shader 基礎