一、子着色器(SubShader)相關內容講解
話不多說,我們接着上篇文章繼續講。
Unity中的每一個着色器都包含一個subshader的列表,當Unity需要顯示一個網格時,它能發現使用的着色器,並提取第一個能運行在當前用戶的顯示卡上的子着色器。
我們知道,子着色器定義了一個渲染通道的列表,並可選是否爲所有通道初始化所需要的通用狀態。子着色器的寫法如下:
Subshader{ [Tags] [CommonState] Passdef [Passdef …] }
也就是通過可選標籤,通用狀態 和 一個Pass 定義的列表構成了子着色器。
當Unity選擇用於渲染的子着色器時,它爲每一個被定義的通道渲染一次對象(可能會更多,這取決於光線的交互作用)。當對象的每一次渲染都是很費資源之時,我們便使用盡量少的通道來定義一個着色器。當然,有時在一些顯示硬件上需要的效果不能通過單次通道來完成。自然就得使用多通道的子着色器了。
另外,通道定義的類型包括a regular Pass, a Use Pass or aGrab Pass。
任何出現在通道定義的狀態同時也能整個子着色器塊中可見。這將使得所有通道共享狀態。
1.1 關於子着色器標籤(SubShader Tags)
子着色器使用標籤來告訴渲染引擎期望何時和如何渲染對象。其語法如下:
Tags { “TagName1″ =”Value1” “TagName2” = “Value2” }
也就是,爲標籤”TagName1″指定值”Value1″。爲標籤”TagName2″指定值”Value2″。我們可以設定任意多的標籤。
標籤是標準的鍵值對,也就是可以根據一個鍵值獲得對應的一個值的。SubShader 中的標籤是用來決定渲染的次序和子着色器中的其他變量的。
1.1.1 決定渲染次序——隊列標籤(Queue tag)
我們可以使用 Queue 標籤來決定對象被渲染的次序。着色器決定它所歸屬的對象的渲染隊列,任何透明渲染器可以通過這個辦法保證在所有不透明對象渲染完畢後再進行渲染。
有四種預定義(predefined)的渲染隊列,在預定義隊列之間還可以定義更多的隊列。這四種預定義的標籤如下:
- 後臺(Background) – 這個渲染隊列在所有隊列之前被渲染,被用於渲染天空盒之類的對象。
- 幾何體(Geometry,默認值)- 這個隊列被用於大多數對象。 不透明的幾何體使用這個隊列。
- 透明(Transparent) – 這個渲染隊列在幾何體隊列之後被渲染,採用由後到前的次序。任何採用alpha混合的對象(也就是不對深度緩衝產生寫操作的着色器)應該在這裏渲染(如玻璃,粒子效果等)
- 覆蓋(Overlay) – 這個渲染隊列被用於實現疊加效果。任何需要最後渲染的對象應該放置在此處。(如鏡頭光暈等)
一個使用Tags的示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Shader
"Transparent
QueueExample" { SubShader { //寫上Tags標籤 Tags
{ "Queue"
= "Transparent"
} //開始一個通道 Pass { //
寫Shader實體內容 } } } |
1.1.2 自定義中間隊列
讓我們來舉例說明如何在透明隊列中渲染對象。一般情況下,幾何體渲染隊列爲了達到最優的性能優化了對象的繪製次序。而其他渲染隊列依據舉例排序對象,從最遠的對象開始渲染到最近的對象。
而對於特殊的需要,可以使用中間隊列來滿足。在Unity實現中每一個隊列都被一個整數的索引值所代表。後臺爲1000,幾何體爲2000,透明爲3000,疊加層爲4000. 着色器可以自定義一個隊列,如:
Tags { “Queue” =”Geometry+1″ }
因爲渲染隊列是從小到大來數的,這就會使對象在所有不透明的對象渲染之後但卻在所有透明物體前被渲染,該渲染隊列的索引值爲2001。當我們希望某些對象總是在其他某些對象前被繪製的情況下,這用起來就很方便了。比如,在絕大多數時候,透明的水總是應該在所有不透明的物體之後並在透明對象前被渲染,這就可以通過中間隊列來滿足渲染需求。
1.1.3 關於忽略投影標籤(IgnoreProjector tag)
後面我們會接觸到,若設置IgnoreProjector(忽略投影)標籤爲”True”,那麼使用這個着色器的對象就不會被投影機制(Projectors)所影響。這對半透明的物體來說是一個福利,因爲暫時沒有對他們產生投影的比較合適的辦法,那麼直接忽略掉就行了。
二、 通道(Pass)相關內容講解
Pass通道塊控制被渲染的對象的幾何體。其語法定義是這樣的:
Pass { [Name and Tags] [RenderSetup][TextureSetup] }
基本通道命令包含一個可選的渲染設置命令的列表,和可選的被使用的紋理的列表。
2.1 通道中的名稱與標籤(Name and tags )
一個通道能定義它的Name 和任意數量的Tags。通過使用tags來告訴渲染引擎在什麼時候該如何渲染他們所期望的效果。語法如下:
Tags { “TagName1″ =”Value1” “TagName2” = “Value2” }
指定TagName1 的值爲Value1 ,TagName2 的值爲 Value2 你可以指定很多自己喜歡的標籤,下面會詳細來列舉。
標籤基本上是鍵-值對的形式。 內部的Pass標籤用來控制光照管道(環境光照,頂點光照和像素光照)中pass 的任務和一些其它選項。注意以下的標籤必須在pass段內部,而不是在SubShader中被識別。
2.1.1 光照模式標籤(LightMode tag)
LightMode 標籤定義了Shader的光照模式,具體含義以後會在講渲染管線時講到。下面我們先簡單瞭解一下有哪些光照模式可選,以及他們的具體作用:
- Always: 總是渲染。沒有運用光照。
- ForwardBase:用於正向渲染,環境光、方向光和頂點光等
- ForwardAdd:用於正向渲染,用於設定附加的像素光,每個光照對應一個pass
- PrepassBase:用於延遲光照,渲染法線/鏡面光。
- PrepassFinal:用於延遲光照,通過結合紋理,光照和自發光渲染最終顏色
- Vertex: 用於頂點光照渲染,當物體沒有光照映射時,應用所有的頂點光照
- VertexLMRGBM:用於頂點光照渲染,當物體有光照映射的時候使用頂點光照渲染。在平臺上光照映射是RGBM 編碼
- VertexLM:用於頂點光照渲染,當物體有光照映射的時候使用頂點光照渲染。在平臺上光照映射是double-LDR 編碼(移動平臺,及老式臺式CPU)
- ShadowCaster: 使物體投射陰影。
- ShadowCollector: 爲正向渲染對象的路徑,將對象的陰影收集到屏幕空間緩衝區中。
2.1.2 條件選項標籤 (RequireOptions tag )
若想要在一些外部條件得到滿足時某pass才渲染,就可以通過使用RequireOptions標籤,它的值是一個空格分割的字符串,目前由Unity3d支持的選項只有一個,就是渲染植被之時:
SoftVegetation: 如果在QualitySettings中開啓渲染軟植被(Edit->Project Settings->Quality),則該pass可以渲染
2.2 關於渲染設置 (Render Setup )
通道設定顯示硬件的各種狀態,例如能打開alpha混合,能使用霧,等等。這些命令如下:
Material { Material Block }
定義一個使用頂點光照管線的材質,詳情參考上次我們講的Material
Lighting On | Off
開啓或關閉頂點光照。開啓燈光之後,頂點光照纔會有作用
Cull Back | Front | Off
設置多邊形剔除模式,詳細內容後面的文章會講解到。
ZTest (Less | Greater | LEqual | GEqual |Equal | NotEqual | Always)
設置深度測試模式,詳細內容後面的文章會講解到。
ZWrite On | Off
設置深度寫模式,詳細內容後面的文章會講解到。
Fog { Fog Block }
設置霧參數,詳細內容後面的文章會講解到。
AlphaTest (Less | Greater | LEqual | GEqual| Equal | NotEqual | Always) CutoffValue
開啓alpha測試
Blend SourceBlendMode |DestBlendMode
設置alpha混合模式
Color Color value
設置當頂點光照關閉時所使用的顏色
ColorMask RGB | A | 0 | any combination of R, G, B, A
設置顏色寫遮罩。設置爲0將關閉所有顏色通道的渲染
Offset OffsetFactor , OffsetUnits
設置深度偏移
SeparateSpecular On | Off
開啓或關閉頂點光照相關的平行高光顏色。
ColorMaterial AmbientAndDiffuse | Emission
當計算頂點光照時使用每頂點的顏色
2.3 關於紋理設置(Texture Setup )
在完成渲染設定後,我們可以指定一定數量的紋理和當使用 SetTexture 命令時所採用的混合模式:
SetTexture [texture property]{ [Combineoptions] }
紋理設置,用於配置固定函數多紋理管線,當自定義fragment shaders 被使用時,這個設置也就被忽略掉了。
2.4 一些細節
2.4.1 關於每像素光照(Per-pixel Lighting )
每像素光照管線通過多次通道渲染對象來完成。Unity渲染對象一次來獲取陰影色和任何頂點光照。然後再在額外的並行通道中渲染出每像素光照的效果。
2.4.2 關於每頂點光照(Per-vertex Lighting)
每頂點光照是標準的Direct3D/OpenGL光照模式,通過計算每個頂點的光照來完成。Lighting on命令開啓光照。而我們知道,光照被材質塊,顏色材質和平行高光等命令所影響。
2.5 一些高端特效的通道命令
有時候,我們會寫一些特殊的通道,要多次反覆利用普通的功能或是實現高端的特效。應對這些情況,Unity中就有一些高級點武器可以選用,這裏簡單講一講吧,現在先稍微有個概念就好。
2.5.1 UsePass——包含已經寫好的通道
UsePass 可以包含來自其他着色器的通道,來減少重複的代碼。
例如,在許多像素光照着色器中,陰影色或頂點光照通道在在相應的頂點光照着色器中是相同的。UsePass命令只是包含了另一個着色器的給定通道。例如如下的命令可以使用內置的高光着色器中的名叫”Base”的通道:
UsePass “Specular/BASE”
而爲了讓UsePass能夠認識到指定的是誰,必須給希望使用的通道命名,弄個身份證。通道中的Name命令就是這個功能:
Name “MyPassName”
2.5.2 GrabPass——捕獲屏幕內容到紋理中
GrabPass 可以捕獲物體所在位置的屏幕的內容並寫入到一個紋理中,通常在靠後的通道中使用,這個紋理能被用於後續的通道中完成一些高級圖像特效。
一個示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Shader
"GrabPassInvert" { SubShader { //在所有不透明幾何體之後繪製 Tags
{ "Queue"
= "Transparent"
} //捕獲對象後的屏幕到_GrabTexture中 GrabPass
{ } //用前面捕獲的紋理渲染對象,並反相它的顏色 Pass <span
style= "white-space:
pre;" >
</span>{ SetTexture
[_GrabTexture] { combine one-texture } } } } |
三、紋理(Texturing)相關內容講解
紋理在基本的頂點光照計算完成之後被應用,這也就是SetTexture 命令必須放置在通道的末尾的原因了。在着色器中通過SetTexture 命令來完成。
需要注意的是,SetTexture 命令在使用了片段着色器時不會生效;因爲在片段着色器下像素操作被完全描述在着色器中。
材質貼圖可以用來實現舊式風格的混合器效果。我們可以在一個通道中使用多個SetTexture命令, SetTexture所有紋理都是按代碼順序來添加的,也就是如同Photoshop中的圖層操作一樣。SetTexture的語法如下:
SetTexture [TexturePropertyName] { TextureBlock }
解釋:分配一個紋理,其中TexturePropertyName必須爲一個紋理,也就是在shader最開始的Properties中的屬性。在TextrueBlock中設置如何應用紋理,即紋理塊控制紋理如何被應用。而在紋理塊中能執行3種命令:合併操作,矩陣操作、與常量顏色進行混合操作。
3.1 紋理合並命令
combine src1 * src2
將源1和源2的元素相乘。結果會比單獨輸出任何一個都要暗
combine src1 + src2
將將源1和源2的元素相加。結果會比單獨輸出任何一個都要亮
combine src1 – src2
源1 減去 源2
combine src1 +- src2
先相加,然後減去0.5(也就是添加了一個符號)
combine src1 lerp (src2) src3
使用源2的透明度通道值在源3和源1中進行差值,注意差值是反向的:當透明度值是1是使用源1,透明度爲0時使用源3
combine src1 * src2 + src3
源1和源2的透明度相乘,然後加上源3
combine src1 * src2 +- src3
源1和源2的透明度相乘,然後和源3做符號加
combine src1 * src2 – src3
源1和源2的透明度相乘,然後和源3相減
其中,所有src屬性都可以是previous,constant, primary or texture其中的一個。
- Previous 是上一次SetTexture的結果
- Primary 是來自光照計算的顏色或是當它綁定時的頂點顏色
- Texture是在SetTexture中被定義的紋理的顏色
- Constant是被ConstantColor定義的顏色
一些小技巧:
1.上述的公式都均能通過關鍵字 Double 或是 Quad 將最終顏色調高亮度2倍或4倍。
2.所有的src屬性,除了差值參數都能被標記一個“-”負號來使最終顏色反相。
3.所有src屬性能通過跟隨 alpha 標籤來表示只取用alpha通道。
3.2 顏色常量命令
ConstantColor color
定義在combine命令中能被使用的常量顏色
3.3 紋理矩陣命令
matrix [MatrixPropertyName]
使用給定矩陣變換紋理座標
3.4 一些細節
較老的顯卡對紋理一般會使用分層的操作方案,而紋理在每一層後被應用一次顏色的修改。對每一個紋理,一般來說紋理都是和上一次操作的結果混合,如圖:
需要注意的是,對於“純正”的“固定功能流水線”設備(比如說OpenGL, OpenGL ES 1.1, Wii),每個SetTexture階段的值被限制爲0到1的範圍之間。而其他的設備(如Direct3D, OpenGL ES 2.0)中,這個範圍就不一定是固定的。這種情況就可能會影響SetTexture階段,可能使產生的值高於1.0。
3.4.1 關於分離的透明度和顏色混合(Separate Alpha & Color computation)
在默認情況下,混合公式被同時用於計算紋理的RGB通道和透明度。同時,我們也能指定針對透明度來單獨計算,比如這樣,將RGB操作和Alpha操作隔開:
SetTexture [_MainTex] { combine previous *texture, previous + texture }
如上所述,我們對RGB的顏色做乘然後對Alpha透明度相加
3.4.2 關於反射高光(Specular highlights)
默認情況下primary顏色是漫反射,陰影色和高光顏色(在光線計算中定義)的加和。如果我們將通道設置中的SeparateSpecular On 寫上,高光色便會在混合計算後被加入,而不是之前。PS:Unity內置的頂點着色器就是加上SeparateSpecular On的。
3.4.3 關於顯卡的硬件支持情況說明
我們上篇文章中已經講到過,一些舊的顯示卡不能支持某些紋理混合模式,且不同的卡有不同數目的SetTexture階段可用。所以我們應該爲想支持的顯卡來分開寫SubShader,適應各種情況 。
PS::支持像素着色器1.1版本的顯卡(即NVIDIA GeForce 3 或更高, ATI Radeon 8500 或更高, Intel 9xx)支持所有的混合器模式,並且可以擁有至少4級渲染階段。下表簡述了硬件支持情況。
Card 顯卡 Stage count級數 Combiner modes not supported不支持的結合模式 NVIDIA GeForce 3/4Ti and up 4 In OpenGL on Windows, src1*src2-src3 is not supported NVIDIA TNT2, GeForce 256, GeForce 2, GeForce 4MX 2 In OpenGL on Windows, src1*src2-src3 is not supported ATI Radeon 9500 and up 4-8 8 in OpenGL, 4 in D3D9 ATI Radeon 8500-9250 4-6 6 in OpenGL, 4 in D3D9 ATI Radeon 7500 3 ATI Rage 2 src1*src2+src3
src1*src2+-src3
src1*src2-src3四、Shader書寫實戰
上面講了一堆一堆的概念和寫法,估計大家一遍看下來頭都大了。沒關係,依舊是讓我們看一些示例Shader的寫法,弄清楚上面這一堆堆的概念是如何應用的。主要是紋理相關內容的Shader書寫
1. Alpha紋理混合
先看看如何用本文講解的寫法,寫出一個簡單的紋理混合Shader。首先設置第一個混合器只使用_MainTex,然後使用_BlendTex的Alpha通道來淡入_BlendTex的RGB顏色:
123456789101112131415161718192021Shader
"淺墨Shader編程/Volume3/7.Alpha紋理混合"
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_MainTex (
"基礎紋理(RGB)"
, 2D) =
"white"
{}
_BlendTex (
"混合紋理(RGBA) "
, 2D) =
"white"
{}
}
//--------------------------------【子着色器】--------------------------------
SubShader
{
Pass
{
// 【1】應用主紋理
SetTexture [_MainTex] { combine texture }
// 【2】使用相乘操作來進行Alpha紋理混合
SetTexture [_BlendTex] {combine texture * previous}
}
}
}
進行混合的兩張紋理如下:
此Shader編譯後賦給材質的效果如下:
2.紋理的Alpha通道與自發光相混合
這個着色器使用_MainTex的Alpha來描述什麼地方應用光照。它通過分兩個階段應用紋理來實現;第一個階段,紋理的Alpha值被用來在頂點顏色和純白色之間混合。第二階段,乘入紋理的RGB通道:
1234567891011121314151617181920212223242526272829303132333435363738Shader
"淺墨Shader編程/Volume3/8.紋理的Alpha通道與自發光相混合"
{
//-------------------------------【屬性】-----------------------------------------
Properties
{
_MainTex (
"基礎紋理 (RGB)-自發光(A)"
, 2D) =
"red"
{ }
}
//--------------------------------【子着色器】----------------------------------
SubShader
{
Pass
{
//【1】設置白色的頂點光照
Material
{
Diffuse (1,1,1,1)
Ambient (1,1,1,1)
}
//【2】開光照
Lighting On
//【3】使用紋理的Alpha通道來插值混合顏色(1,1,1,1)
SetTexture [_MainTex]
{
constantColor (1,1,1,1)
combine constant lerp(texture) previous
}
//【4】和紋理相乘
SetTexture [_MainTex]
{
combine previous * texture
}
}
}
}
此Shader編譯後賦給材質的效果如下:
3. 紋理Alpha與自發光混合可調色版
這次我們給出一個Color屬性,讓自發光顏色可調:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
Shader
"淺墨Shader編程/Volume3/9.紋理Alpha與自發光混合可調色版" { //-------------------------------【屬性】--------------------------------------- Properties { _IlluminCol
( "自發光(RGB)" ,
Color) = (1,1,1,1) _MainTex
( "基礎紋理
(RGB)-自發光(A)" ,
2D) = "white"
{} } //--------------------------------【子着色器】-------------------------------- SubShader { Pass { //【1】設置白色的頂點光照 Material { Diffuse
(1,1,1,1) Ambient
(1,1,1,1) } //【2】開啓光照 Lighting
On //
【3】將自發光顏色混合上紋理 SetTexture
[_MainTex] { //
使顏色屬性進入混合器 constantColor
[_IlluminCol] //
使用紋理的alpha通道混合頂點顏色 combine
constant lerp(texture) previous } //
【4】乘以紋理 SetTexture
[_MainTex] {combine previous * texture } } } } |
此Shader編譯後賦給材質的效果如下,可以自由調節顏色:
4. 頂點光照+紋理Alpha自發光混合
我們將本文中介紹的知識點和上一篇文章中頂點光照相關的內容結合起來,主要是在Pass中添加了一句,讓頂點光照可以和紋理顏色結合起來:
1
2
|
//---------------------開啓獨立鏡面反射---------------- SeparateSpecular
On |
完整的Shader代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
Shader
"淺墨Shader編程/Volume3/10.頂點光照+紋理Alpha自發光混合" { //-------------------------------【屬性】--------------------------------------- Properties { _IlluminCol
( "自發光色" ,
Color) = (1,1,1,1) _Color
( "主顏色" ,
Color) = (1,1,1,0) _SpecColor
( "高光顏色" ,
Color) = (1,1,1,1) _Emission
( "光澤顏色" ,
Color) = (0,0,0,0) _Shininess
( "光澤度" ,
Range (0.01, 1)) = 0.7 _MainTex
( "基礎紋理
(RGB)-自發光(A)" ,
2D) = "white"
{ } } //--------------------------------【子着色器】-------------------------------- SubShader { Pass { //【1】設置頂點光照值 Material { Diffuse
[_Color] Ambient
[_Color] Shininess
[_Shininess] Specular
[_SpecColor] Emission
[_Emission] } //【2】開啓光照 Lighting
On //【3】---------------------開啓獨立鏡面反射---------------- SeparateSpecular
On //
【4】將自發光顏色混合上紋理 SetTexture
[_MainTex] { //
使顏色屬性進入混合器 constantColor
[_IlluminCol] //
使用紋理的alpha通道插值混合頂點顏色 combine
constant lerp(texture) previous } //
【5】乘上紋理 SetTexture
[_MainTex] { combine previous * texture } //【6】乘以頂點紋理 SetTexture
[_MainTex] { Combine previous * primary DOUBLE, previous * primary} } } } |
此Shader編譯後賦給材質的效果如下:
5. 頂點光照+自發光混合+紋理混合
在剛剛介紹的第四個Shader的基礎上,加上第一個Shader中講解的紋理混合,就做成了本文最終的頂點光照+自發光混合+紋理混合Shader:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
Shader
"淺墨Shader編程/Volume3/11.頂點光照+自發光混合+紋理混合" { //-------------------------------【屬性】----------------------------------------- Properties { _IlluminCol
( "自發光色" ,
Color) = (0,0,0,0) _Color
( "主顏色" ,
Color) = (1,1,1,0) _SpecColor
( "高光顏色" ,
Color) = (1,1,1,1) _Emission
( "光澤顏色" ,
Color) = (0,0,0,0) _Shininess
( "光澤度" ,
Range (0.01, 1)) = 0.7 _MainTex
( "基礎紋理
(RGB)-自發光(A)" ,
2D) = "white"
{} _BlendTex
( "混合紋理(RGBA)
" ,
2D) = "white"
{} } //--------------------------------【子着色器】-------------------------------- SubShader { //----------------通道--------------- Pass { //【1】設置頂點光照值 Material { //可調節的漫反射光和環境光反射顏色 Diffuse
[_Color] Ambient
[_Color] //光澤度 Shininess
[_Shininess] //高光顏色 Specular
[_SpecColor] //自發光顏色 Emission
[_Emission] } //【2】開啓光照 Lighting
On //【3】--------------開啓獨立鏡面反射-------------- SeparateSpecular
On //【4】將自發光顏色混合上紋理 SetTexture
[_MainTex] { //
使顏色屬性進入混合器 constantColor
[_IlluminCol] //
使用紋理的alpha通道插值混合頂點顏色 combine
constant lerp(texture) previous } //【5】乘上基本紋理 SetTexture
[_MainTex] { combine previous * texture } //【6】使用差值操作混合Alpha紋理 SetTexture
[_BlendTex] { combine previous*texture } //【7】乘以頂點紋理 SetTexture
[_MainTex] {Combine previous * primary DOUBLE, previous * primary } } } } |
此Shader編譯後賦給材質的效果如下:
換些高光顏色玩一玩:
正常白色高光版: