http://www.7fires.cn/archives/203
年關將至,整理今年的大小事務,看看哪些是可以拖到年後的。整理髮現今年一直想寫,卻遲遲未動手寫的《合併Shader》還沒有開工。是的,如你所見這是在開一個新坑,不多挖點坑,將來怎麼會有意願去填坑呢!!!當然更早的《USequencer系列》也會一直更新,畢竟USequencer最大的問題還沒有解決,對於這個又愛又恨的插件,我會一直跟到底。
還是先說說這個新坑吧,《合併Shader》系列旨在介紹一些技能和方法,用來把數量繁多的Shader進行減量,在保證功能不打折的情況下,精簡Shader數量,原理就是把相似功能的Shader文件合併在一個文件裏。當然學過這些技能後,極致情況下可以做到把所有的Shader文件合併成一個,如Unity 5.x的Standard着色器,但我不會建議你這樣做,原因嘛,你嘗試過後就會知道,我就不多說,因爲我沒有嘗試過,嘿嘿!本系列對一些常用的,不常用的,正派的,歪門邪道的技能和方法都會有所涉獵。林林總總的技能和方法中,我們儘量遵從從簡到繁的方式來依次遍歷。
在數學中,我們學習過:把多項式中的同類項合併成一項叫做同類項的合併,也叫合併同類項。同理,提取Shader的相似部分,把多個Shader合併成一個就叫做Shader的合併,也叫合併Shader,偶爾也會引用數學的名詞來稱呼他爲Shader的合併同類項。
Shader的合併方式方法有很多,根據不同的合併技能和方法,可以畫分爲不同的派系。今天優先介紹一種不太常見,但又很實用的派系,往下看。
王婆賣瓜,自賣自誇, 讓我再多說幾句廢話。對於Shader的合併,首先讓人想到的應該是宏定義,相信宏定義也是大家應用最廣,最先接觸到的,使用自然也不再話下,畢竟由於GPU的特殊性,Shader裏常常通篇都充滿了各種宏定義。當然,該系列會對宏定義有所介紹,他可是Shader合併裏功高蓋世的重要角色,很多地方都會有他的身影。但對他的介紹不在這一篇,也許會是下一篇,因爲他還不是我認爲的最簡單的合併Shader方式。至於最簡單的合併Shader的方式,應該是使用Unity已經預先定製好的幾種MaterialPropertyDrawer來合併Shader的方式。只用修改2行代碼,就可以搞定一類Shader的合併,該方法主要用來合併那些只是渲染狀態不一樣的Shader。
一、初次簡單使用
一個完整的Shader,他的渲染狀態變量有很多種,由於不是每一種狀態的改變都能很明顯的看到結果,而作爲初次使用,優先選擇一種最明顯的、最容易懂的狀態作爲我們的測試用例,他就是ZTest,深度比較。對ZTest不太瞭解的朋友,可以看看官方的學習文檔或者查看一下相關技術書籍,我就不在這裏具體介紹這個狀態了。
1.1 首先我們選用的是Unity官方提供的一個最常用Shader:Normal-Diffuse.shader(Legacy Shaders/Diffuse)
1.2 在屬性列表(Properties)中添加一行
1 | [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2 |
1.3 在SubShader中添加一行
1
|
ZTest
[_ZTest]
|
1.4 完整的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 | Shader "ShaderCombine/01.ShaderCombineSimpleZTest" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 ZTest [_ZTest] CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; fixed4 _Color; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } Fallback "Legacy Shaders/VertexLit" } |
只用添加這兩行代碼,我們就可以再Inspector面板中控制使用該Shader物體的深度測試方法。
1.5 直觀展示
在Unity中的測試示例是這樣的:
當然大家也可以通過這個示例測試一下每一種深度測試的方法是否與你心中所想或之前所學的是否衝突。
二、再次深入使用
在上面的例子中,我們只使用了深度測試。但對於我們來說,單單一個深度測試肯定滿足不了我的,我們還需要更多、更多的狀態,比如背面剔除、混合模式等等。
在這一節中,我列舉出了一些常用的狀態控制量,對於一些不常用的模板什麼的,就不在這裏列舉。當然對於沒有列舉的,可以依葫蘆畫瓢,大部分基本都是可行的。
2.1 一個大而全的簡單示例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
|
Shader
"ShaderCombine/02.ShaderCombineCommonState"
{
Properties
{
_Color
("Main
Color",
Color)
=
(1,1,1,1)
_MainTex
("Base
(RGB)",
2D)
=
"white"
{}
[Enum(UnityEngine.Rendering.CullMode)]
_Cull
("Cull
Mode",
Float)
=
1
[Enum(Off,0,On,1)]
_ZWrite
("ZWrite",
Float)
=
1
[Enum(UnityEngine.Rendering.CompareFunction)]
_ZTest
("ZTest",
Float)
=
1
[Enum(UnityEngine.Rendering.BlendMode)]
_SourceBlend
("Source
Blend Mode",
Float)
=
2
[Enum(UnityEngine.Rendering.BlendMode)]
_DestBlend
("Dest
Blend Mode",
Float)
=
2
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD
200
ZTest
[_ZTest]
Cull
[_Cull]
ZWrite
[_ZWrite]
ZTest
[_ZTest]
Blend
[_SourceBlend]
[_DestBlend]
CGPROGRAM
#pragma
surface surf Lambert
sampler2D
_MainTex;
fixed4
_Color;
struct
Input
{
float2
uv_MainTex;
};
void
surf
(Input
IN,
inout
SurfaceOutput
o)
{
fixed4
c
=
tex2D(_MainTex,
IN.uv_MainTex)
*
_Color;
o.Albedo
=
c.rgb;
o.Alpha
=
c.a;
}
ENDCG
}
Fallback
"Legacy Shaders/VertexLit"
}
|
2.2 直觀展示
應用效果:
三、有限自定義
在上面的示例中,我們都是使用Unity預先定義好的一些枚舉類型,比如UnityEngine.Rendering.CompareFunction,UnityEngine.Rendering.BlendMode等。這些定義好的類型把每個狀態可能的選項都一一列舉了,但有時候我們並不需要這麼多選項,或者說我們並不希望給美術列舉出所有的可選項,畢竟很多的選項我們可能做完整個項目或者幾個項目都不會使用到,而過多的選項也會帶來很多的麻煩。或者換一個說法,我希望我們的功能使用起來簡單易懂,不易出錯並且可控,那就需要我們開發做更多的工作,去掉那些”無用”的選項。其實說這麼廢話,無非就是我們能不能自己定義每個狀態的選項呢?答案當然是可以的。
在給出自定義方式前,我們先來熟悉一下Unity給我們提供的這幾個枚舉類型。
3.1 剔除模式
1 2 3 4 5 6 7 | UnityEngine.Rendering.CullMode: public enum CullMode { Off = 0, //Disable culling. Front = 1, //Cull front-facing geometry. Back = 2 //Cull back-facing geometry. } |
3.2 比較方式
該比較方式通用與深度比較和模板比較
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
UnityEngine.Rendering.CompareFunction
//Depth or stencil comparison function.
public
enum
CompareFunction
{
Disabled
=
0,
//Depth or stencil test is disabled.
Never =
1,
//Never pass depth or stencil test.
Less
=
2,
//Pass depth or stencil test when new value is less than old one.
Equal =
3,
//Pass depth or stencil test when values are equal.
LessEqual =
4,
//Pass depth or stencil test when new value is less or equal than old one.
Greater =
5,
//Pass depth or stencil test when new value is greater than old one.
NotEqual
=
6,
//Pass depth or stencil test when values are different.
GreaterEqual
=
7,
//Pass depth or stencil test when new value is greater or equal than old one.
Always
=
8 //Always
pass depth or stencil test.
}
|
3.3 混合模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | UnityEngine.Rendering.BlendMode //Blend mode for controlling the blending. public enum BlendMode { Zero = 0, //Blend factor is (0, 0, 0, 0). One = 1, //Blend factor is (1, 1, 1, 1). DstColor = 2, //Blend factor is (Rd, Gd, Bd, Ad). SrcColor = 3, //Blend factor is (Rs, Gs, Bs, As). OneMinusDstColor = 4, //Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad). SrcAlpha = 5, //Blend factor is (As, As, As, As). OneMinusSrcColor = 6, //Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As). DstAlpha = 7, //Blend factor is (Ad, Ad, Ad, Ad). OneMinusDstAlpha = 8, //Blend factor is (1 - Ad, 1 - Ad, 1 - Ad, 1 - Ad). SrcAlphaSaturate = 9, //Blend factor is (f, f, f, 1); where f = min(As, 1 - Ad). OneMinusSrcAlpha = 10 //Blend factor is (1 - As, 1 - As, 1 - As, 1 - As). } |
3.4 有限的自定義
在上面3個小小節中,我們瞭解了Unity自身提供的狀態選項,而且每一個狀態選項後都強制賦上了相應的數值,這是有原因的。因爲我們寫好的Shader不管怎樣都要首先經過Unity的編譯等處理轉換爲目標平臺的着色器語言。而Unity自己的Shader編譯器我們是沒法修改(沒有源碼的情況),也就是說我們不能隨意更改這些狀態的數值。其實我們修改的這些狀態值都是給Unity的Shader編譯器看的,而編譯器對狀態的數值是理解是固化好的。SO,雖然我們可以自定義這些狀態選項,但也不會任由我們隨意定義,這就好比戴着鐐銬跳舞,雖然有限制,但我們依然可以跳出優美的舞蹈。
自定義非常的簡單,我們可以減少選項的數量,但是不能改變每一項的值,這就要求我們強行給每一個值賦上對應的值,依然還是用深度測試實驗,如下所示:
這是之前的:
1
|
[Enum(UnityEngine.Rendering.CompareFunction)]
_ZTest
("ZTest",
Float)
=
2
|
這是自定後的:
1 | [Enum(Less,2,Greater,5)] _ZTest ("ZTest", Float) = 2 |
選項與數值全部使用逗號分隔,該示例中我只給出了兩個選項,小於和大於,便於直觀查看。
完整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
|
Shader
"ShaderCombine/03.ShaderCombineCustomState"
{
Properties
{
_Color
("Main
Color",
Color)
=
(1,1,1,1)
_MainTex
("Base
(RGB)",
2D)
=
"white"
{}
[Enum(Less,2,Greater,5)]
_ZTest
("ZTest",
Float)
=
2
}
SubShader
{
Tags
{
"RenderType"="Opaque"}
LOD
200
ZTest
[_ZTest]
CGPROGRAM
#pragma
surface surf Lambert
sampler2D
_MainTex;
fixed4
_Color;
struct
Input
{
float2
uv_MainTex;
};
void
surf
(Input
IN,
inout
SurfaceOutput
o)
{
fixed4
c
=
tex2D(_MainTex,
IN.uv_MainTex)
*
_Color;
o.Albedo
=
c.rgb;
o.Alpha
=
c.a;
}
ENDCG
}
Fallback
"Legacy Shaders/VertexLit"
}
|
3.5 直觀展示
在Unity中的樣子是這樣的:
細心的讀者可能已經發現我們在第二章節中的完整示例中就有使用自定義,就是裏面的那個寫深度_ZWrite選項,因爲沒有在Unity裏面找到相應的枚舉值,就直接使用了自定義,反正只要保證數值是正確的就可以任意發揮使用。更多的使用和應用場景就等你們去發現了,我這只是拋磚引玉。
以上便是MaterialPropertyDrawer的應用場景之一,對於MaterialPropertyDrawer的應用,在後續的篇章中也還會陸續出現。我計劃把MaterialPropertyDrawer應用當成《合併Shader》系列中的一個分支,當然《合併Shader》系列不會僅且只有這一個分支的,O(∩_∩)O哈哈~
其實最初是想花一整章篇幅來講解MaterialPropertyDrawer的各種使用,但其內部的擴展空間還比較廣泛。寫下來篇幅太長,而太長的篇幅,閱讀起來也比較麻煩,所以還是拆成幾章篇幅來慢慢絮叨吧,同時也遵從一次只講一個問題。