1. 程式人生 > >【Unity Shader】(五) ------ 透明效果之半透明效果的實現及原理

【Unity Shader】(五) ------ 透明效果之半透明效果的實現及原理

pic sele 不同的 %20 分享圖片 渲染 select fall 就是

筆者使用的是 Unity 2018.2.0f2 + VS2017,建議讀者使用與 Unity 2018 相近的版本,避免一些因為版本不一致而出現的問題

【Unity Shader學習筆記】(三) ---------------- 光照模型原理及漫反射和高光反射的實現
【Unity Shader】(四) ------ 紋理之法線紋理、單張紋理及遮罩紋理的實現

前言

相信讀者對透明效果都不陌生,因為透明效果是遊戲中經常使用的一種效果。要實現透明效果,通常會在渲染模型時控制它的透明通道。而其透明度則控制是其是否會顯示,0 表示完全不顯示,1 表示完全顯示。

Unity 中通常使用兩種方法來實現透明效果:透明度測試(Alpha Test)

透明度混合(Alpha Blending)

  • 透明度測試。透明度測試是一種十分 “簡單粗暴” 的機制,當有一個片元的透明度不符合條件時,就直接舍棄,不再任何處理(不會對顏色緩沖有影響);如果符合,就進行正常的處理(深度測試,深度寫入等);所以這帶來的效果也是兩極分化的,要麽完全透明,要麽完全不透明。
  • 透明度混合。透明度混合可以得到真正的半透明效果。它會使用當前片元的透明度作為混合因子,與顏色緩沖中的值進行混合,得到新的顏色。需要註意的是,此方法需要關閉深度寫入,而因此帶來的問題就是要 十分十分十分 地註意渲染順序。

為了方便讀者理解,先解釋一下深度緩沖,深度測試和深度寫入

  • 深度緩沖。用於解決可見性問題的強大存在。決定了哪個物體的哪些部分會被渲染,哪些部分會被遮擋
  • 深度測試。開啟後,當渲染一個片元時,根據它的深度值判斷該片元距離攝像機的距離,然後將它的深度值和深度緩沖中的值進行比較
  • 深度寫入。開啟後,當一個片元進行了深度測試後,如果它的值距離更遠,則說明有物體擋在了它前面,那麽它就不會被渲染,如果更近,那麽這個片元就應該覆蓋掉顏色緩沖中的值,並把它的深度值更新到深度緩沖中。

可能會有讀者會問:為什麽透明度混合需要關閉深度寫入呢?我們可以同過一張圖來解釋

技術分享圖片

平面 1 和 平面 2 都是在攝像機視線上,平面 1 是透明的而平面 2 是不透明的且平面 1 擋住了平面 2。理論上我們應該可以透過平面 1 來看到平面 2。事實上,如果沒有關閉深度寫入,平面 1 和 平面 2在渲染時進行深度測試,測試結果為平面 2更遠,所以平面 2不會被渲染到屏幕上,即我們看不到平面 2。這很顯然是不符合我們所要的。

一. 渲染順序

1.1 渲染順序的重要性

前文說過,關閉了深度寫入後,渲染順序就變得十分重要,為什麽這麽說呢

技術分享圖片

如圖,A 和 B渲染順序不一樣。有兩種情況

    • 先渲染 B 再 渲染 A。此時深度緩沖中沒有數據,B 直接寫入它的顏色緩沖和深度緩沖;然後渲染 A,A進行深度測試,結果為 A 更近,所以此時會用 A 的透明度與顏色緩沖中值進行混合,得到正確的半透明效果。
    • 先渲染 A 再 渲染 B。此時深度緩沖中並沒有數據,A 會寫入顏色緩沖,但不會寫入深度緩沖(因為關閉了深度寫入);然後渲染 B ,B 進行深度測試,而此時深度緩沖中並沒有數據,所以 B 會直接寫進顏色緩沖和深度緩沖,就會覆蓋掉顏色緩沖中 A 的顏色,所以最終渲染出來,從視覺上是 B 在 A 的前面

1.2 渲染隊列

Unity 中提供了 渲染隊列,並用整數索引表示渲染隊列,索引越小,越早渲染

技術分享圖片

也可以使用 SubShader 中的 Queue 標簽來決定該模型屬於哪個渲染隊列

名稱 隊列索引號 描述
Background 1000 這個隊列會在任何隊列 之前被渲染,通常用來渲染繪制在背景的物體
Geometry 2000 默認的隊列,非透明物體使用此隊列
Alpha Test 2450 進行透明度測試的物體使用的隊列
Overlay 3000 按後往前順序渲染,使用透明度混合的物體應該使用此隊列
4000 在最後渲染的物體使用此隊列

二. 透明度測試

新建一個工程,去掉天空盒;新建一個Material 和 shader ,命名為 Alpha Test;新建一個 cube

需要提前了解的是:

  • ZWrite Off 用於關閉深度寫入,可以寫在 Pass裏面,也可以寫在 SubShader 裏,如果是後者,那麽就會對所有的 Pass 產生效果,即所有的 Pass 都會關閉深度寫入。
  • 我們在後面的代碼將會使用 clip 函數進行透明度測試。參數為裁剪時使用的標量或矢量,如果參數的任一分量為負數,就舍棄當前像素的輸出顏色。我們同樣可以在MSDN上找到它的定義

技術分享圖片

技術分享圖片

I. 創建一個場景,去掉天空盒;新建一個 Material 和 shader ,命名為 Alpha Test;創建一個 Cube;準備一張不同區域透明度不同的透明紋理(讀者可以在本文最下方下載)。

II. 定義 Propreties 語義塊

技術分享圖片

Properites 語義塊並沒有什麽特別的屬性,_Cutoff 屬性用來控制透明度,範圍為【0,1】,因為紋理像素的透明度範圍就在此範圍。

III. 指定渲染隊列

技術分享圖片

在 SubShader 中定義一個 Tags,IgnoreProjector 決定 shader 是否會受投影器的影響,RenderType 可以讓 shader 歸入提前定義的組(這裏是 TransparentCutout)。

IV. 定義與 Properties 中相匹配的變量

技術分享圖片

V. 定義輸入輸出結構體

技術分享圖片

VI. 定義頂點著色器

技術分享圖片

TRANSFORM_TEX 函數我們在之前已經解釋過了,如果讀者對此不太了解,可以翻看我的上一篇文章

VII. 定義片元著色器

技術分享圖片

這些代碼相信讀者都不陌生,這裏 clip 函數對不符合條件的片元舍棄了,即不渲染了。

VIII. 最後設置 FallBack

技術分享圖片

完整代碼:

 1 Shader "Unity/01-AlphaTest" {
 2     Properties {
 3         _Color ("Main Tint", Color) = (1,1,1,1)
 4         _MainTex ("Main Tex", 2D) = "white" {}
 5         _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
 6     }
 7     SubShader {
 8         Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
 9         Cull Off
10         Pass
11         {
12             Tags{"LightMode" = "ForwardBase"}
13 
14             CGPROGRAM
15             #pragma vertex vert
16             #pragma fragment frag
17             #include "Lighting.cginc"
18             #include "UnityCG.cginc"
19 
20             fixed4 _Color;
21             sampler2D _MainTex;
22             float4 _MainTex_ST;
23             fixed _Cutoff;
24 
25             struct a2v
26             {
27                 float4 vertex : POSITION;
28                 float3 normal : NORMAL;
29                 float4 texcoord : TEXCOORD0;
30             };
31 
32             struct v2f
33             {
34                 float4 pos : SV_POSITION;
35                 float3 worldNormal : TEXCOORD0;
36                 float3 worldPos : TEXCOORD1;
37                 float2 uv : TEXCOORD2;
38             };
39 
40             v2f vert(a2v v)
41             {
42                 v2f o;
43                 o.pos = UnityObjectToClipPos(v.vertex);
44                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
45                 o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
46 
47                 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
48 
49                 return o;
50 
51             }
52 
53             fixed4 frag(v2f i) : SV_TARGET0
54             {
55                 fixed3 worldNormal = normalize(i.worldNormal);
56                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
57 
58                 fixed4 texcolor = tex2D(_MainTex,i.uv);
59 
60                 clip(texcolor.a - _Cutoff);
61 
62                 fixed3 albedo = texcolor.rgb * _Color.rgb;
63                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
64                 fixed3 diffues = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
65 
66                 return fixed4(ambient + diffues,1.0);
67             }
68             ENDCG
69         }
70     }
71         FallBack "Transparent/Cutout/VertexLit"
72 
73 }

IX. 保存,回到Unity,查看效果

不同 Cutoff 的效果:

技術分享圖片

我們可以看到通過透明度測試實現的效果在邊界處並不理想,有鋸齒,而為了解決這個問題,我們就應該使用透明度混合,來得到更柔和的效果。

三. 透明度混合

3.1 透明度混合的實現

回想一下,我們前面所說的透明度混合的原理:把自身的顏色和顏色緩沖中的顏色進行混合,得到新的顏色。既然要混合,那就需要混合命令 Blend。混合語義有許多,我們稍後會具體地介紹,在這裏,我們使用 Blend SrcFactor DstFactor 這條語義,其中 Blend 是操作,SrcFactor,DstFactor 是因子;我們把 SrcFactor 設為 SrcAlpha,DstFactor 設為 OneMinusSrcAlpha。即我們即將使用的混合語義代碼為 Blend SrcAlpha OneMinusSrcAlpha,這相當於,混合後顏色為:

技術分享圖片

不明白這條公式的讀者不用著急,我們稍後會具體解釋,這裏先知道我們即將使用這條式子便可。

代碼和透明度測試類似,所以這裏只列出需要註意的修改的地方。

I. 新建一個 Material 和 shader ,命名為 Alpha Blend;創建一個 Cube;使用同一張透明紋理。


II.修改 Properties 語義塊

技術分享圖片

其中 _AlphaScale 用來控制整體的透明度。當然也要在CG代碼片中定義與其對應的變量。

III. 修改 Tags

技術分享圖片

IV. 關閉深度寫入和開啟混合

技術分享圖片

V.修改片元著色器

技術分享圖片

我們用透明紋理的透明通道和 _AlphaScale 來控制整體透明度

VI. 修改 FallBack

技術分享圖片

完整代碼:

 1 Shader "Unity/02-AlphaBlend" {
 2     Properties {
 3         _Color ("Main Tint", Color) = (1,1,1,1)
 4         _MainTex ("Main Tex", 2D) = "white" {}
 5         _AlphaScale("Alpha Scale",Range(0,1)) = 1
 6     }
 7     SubShader {
 8         Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
 9 
10         Pass
11         {
12             Tags{"LightMode" = "ForwardBase"}
13             ZWrite Off
14             Blend SrcAlpha OneMinusSrcAlpha
15 
16 
17             CGPROGRAM
18             #pragma vertex vert
19             #pragma fragment frag
20             #include "Lighting.cginc"
21             #include "UnityCG.cginc"
22 
23             fixed4 _Color;
24             sampler2D _MainTex;
25             float4 _MainTex_ST;
26             fixed _AlphaScale;
27 
28             struct a2v
29             {
30                 float4 vertex : POSITION;
31                 float3 normal : NORMAL;
32                 float4 texcoord : TEXCOORD0;
33             };
34 
35             struct v2f
36             {
37                 float4 pos : SV_POSITION;
38                 float3 worldNormal : TEXCOORD0;
39                 float3 worldPos : TEXCOORD1;
40                 float2 uv : TEXCOORD2;
41             };
42 
43             v2f vert(a2v v)
44             {
45                 v2f o;
46                 o.pos = UnityObjectToClipPos(v.vertex);
47                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
48                 o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
49 
50                 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
51 
52                 return o;
53 
54             }
55 
56             fixed4 frag(v2f i) : SV_TARGET0
57             {
58                 fixed3 worldNormal = normalize(i.worldNormal);
59                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
60 
61                 fixed4 texcolor = tex2D(_MainTex,i.uv);
62 
63                 fixed3 albedo = texcolor.rgb * _Color.rgb;
64                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
65                 fixed3 diffues = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
66 
67                 return fixed4(ambient + diffues,texcolor.a * _AlphaScale);
68             }
69             ENDCG
70         }
71     }
72         FallBack "Transparent/VertexLit"
73 
74 }

VII. 保存,回到Unity,查看效果

不同 _AlphaScale 的效果:

技術分享圖片

對比透明度測試,我們可以看到透明度混合更加柔和平滑。

3.2 混合命令

Blend SrcFactor DstFactor 開啟混合,設置因子。源顏色 x ScrFacor + 目標顏色 x DstFactor,結構存入顏色緩沖
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面類似,只是混合透明通道的因子不同

混合有兩個操作數:源顏色(source color) 目標顏色(destination color)

  • 源顏色。指片元著色器產生的顏色值,用 S 表示。
  • 目標顏色。指顏色緩沖中的值,用 D 表示。
  • 兩者混合後,得到的新顏色用 O 表示。

而上面三者都包含了 RGBA 通道。

除了 Blend Off 以外使用Blend 命令,Unity 會為我們開啟混合,因為只有開啟了混合,混合命令才起效。

混合命令由 操作因子 組成,操作默認是使用 加操作,而為了混合RGB 通道 和 A通道,所以我們需要 4 個因子

以混合命令 Blend SrcFactor DstFactor 為例,默認為加操作,SrcFactor 為源顏色, DstFactor 為目標顏色,然後計算

技術分享圖片

技術分享圖片

下面是 ShaderLab 支持的一些混合因子:

混合有兩個操作數:源顏色(source color) 目標顏色(destination color)

  • 源顏色。指片元著色器產生的顏色值,用 S 表示。
  • 目標顏色。指顏色緩沖中的值,用 D 表示。
  • 兩者混合後,得到的新顏色用 O 表示。

而上面三者都包含了 RGBA 通道。

除了 Blend Off 以外使用Blend 命令,Unity 會為我們開啟混合,因為只有開啟了混合,混合命令才起效。

混合命令由 操作因子 組成,操作默認是使用 加操作,而為了混合RGB 通道 和 A通道,所以我們需要 4 個因子

以混合命令 Blend SrcFactor DstFactor 為例,默認為加操作,SrcFactor 為源顏色, DstFactor 為目標顏色,然後計算

技術分享圖片

技術分享圖片

下面是 ShaderLab 支持的一些混合因子:

混合有兩個操作數:源顏色(source color) 目標顏色(destination color)

  • 源顏色。指片元著色器產生的顏色值,用 S 表示。
  • 目標顏色。指顏色緩沖中的值,用 D 表示。
  • 兩者混合後,得到的新顏色用 O 表示。

而上面三者都包含了 RGBA 通道。

除了 Blend Off 以外使用Blend 命令,Unity 會為我們開啟混合,因為只有開啟了混合,混合命令才起效。

混合命令由 操作因子 組成,操作默認是使用 加操作,而為了混合RGB 通道 和 A通道,所以我們需要 4 個因子

以混合命令 Blend SrcFactor DstFactor 為例,默認為加操作,SrcFactor 為源顏色, DstFactor 為目標顏色,然後計算

技術分享圖片

技術分享圖片

下面是 ShaderLab 支持的一些混合因子:

參數描述
One 因子為1
Zero 因子為0
SrcColor 源顏色值
SrcAlpha 源顏色的透明通道的值
DstColor 目標顏色值
DstAlpha 目標顏色的透明通道的值
OneMinusSrcColor 1-源顏色值
OneMinusSrcAlpha 1-源顏色的透明通道的值
OneMinusDstColor 1-目標顏色值
OneMinusDstAlpha 1-目標顏色的透明通道的值

讀者可以自行選擇因子來試試效果

四. 雙面渲染

一般來說,如果一個物體是透明的,要麽我們應該可以看到它的內部和它的任一個面,但前面我們實現的透明中並沒有實現這個效果,因為 Unity 在默認引擎下剔除了物體背面(是相對於攝像機方向的背面,而不是世界坐標中前後左右的背面),不渲染。而剔除的指令為

Cull Back | Front | Off

為了實現雙面渲染,我們可以這樣實現:設置兩個 Pass ,一個只渲染前面,一個只渲染背面。不過需要註意的是,由於開啟了深度測試,所以要註意渲染順序,要先渲染背面,再渲染正面,這樣就能確保背面會被渲染出來。

技術分享圖片

兩個 Pass 中,除了 Cull 指令不一樣外,其余代碼都是和透明度混合中的代碼一樣,所以,這裏直接給出完整代碼

  1 // Upgrade NOTE: replaced ‘_Object2World‘ with ‘unity_ObjectToWorld‘
  2 
  3 Shader "Unity/04-AlphaBlendBothSide" {
  4     Properties {
  5         _Color ("Main Tint", Color) = (1,1,1,1)
  6         _MainTex ("Main Tex", 2D) = "white" {}
  7         _AlphaScale("Alpha Scale",Range(0,1)) = 1
  8     }
  9     SubShader {
 10         Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
 11 
 12         Pass
 13         {
 14             Tags{"LightMode" = "ForwardBase"}
 15 
 16             Cull Front
 17 
 18             ZWrite Off
 19             Blend SrcAlpha OneMinusSrcAlpha
 20 
 21 
 22             CGPROGRAM
 23             #pragma vertex vert
 24             #pragma fragment frag
 25             #include "Lighting.cginc"
 26             #include "UnityCG.cginc"
 27 
 28             fixed4 _Color;
 29             sampler2D _MainTex;
 30             float4 _MainTex_ST;
 31             fixed _AlphaScale;
 32 
 33             struct a2v
 34             {
 35                 float4 vertex : POSITION;
 36                 float3 normal : NORMAL;
 37                 float4 texcoord : TEXCOORD0;
 38             };
 39 
 40             struct v2f
 41             {
 42                 float4 pos : SV_POSITION;
 43                 float3 worldNormal : TEXCOORD0;
 44                 float3 worldPos : TEXCOORD1;
 45                 float2 uv : TEXCOORD2;
 46             };
 47 
 48             v2f vert(a2v v)
 49             {
 50                 v2f o;
 51                 o.pos = UnityObjectToClipPos(v.vertex);
 52                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
 53                 o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
 54 
 55                 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
 56 
 57                 return o;
 58 
 59             }
 60 
 61             fixed4 frag(v2f i) : SV_TARGET0
 62             {
 63                 fixed3 worldNormal = normalize(i.worldNormal);
 64                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
 65 
 66                 fixed4 texcolor = tex2D(_MainTex,i.uv);
 67 
 68                 fixed3 albedo = texcolor.rgb * _Color.rgb;
 69                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
 70                 fixed3 diffues = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
 71 
 72                 return fixed4(ambient + diffues,texcolor.a * _AlphaScale);
 73             }
 74 
 75 
 76 
 77 
 78 
 79             ENDCG
 80         }
 81 
 82 
 83         Pass
 84         {
 85             Tags{"LightMode" = "ForwardBase"}
 86 
 87             Cull Back
 88 
 89             ZWrite Off
 90             Blend SrcAlpha OneMinusSrcAlpha
 91 
 92 
 93             CGPROGRAM
 94             #pragma vertex vert
 95             #pragma fragment frag
 96             #include "Lighting.cginc"
 97             #include "UnityCG.cginc"
 98 
 99             fixed4 _Color;
100             sampler2D _MainTex;
101             float4 _MainTex_ST;
102             fixed _AlphaScale;
103 
104             struct a2v
105             {
106                 float4 vertex : POSITION;
107                 float3 normal : NORMAL;
108                 float4 texcoord : TEXCOORD0;
109             };
110 
111             struct v2f
112             {
113                 float4 pos : SV_POSITION;
114                 float3 worldNormal : TEXCOORD0;
115                 float3 worldPos : TEXCOORD1;
116                 float2 uv : TEXCOORD2;
117             };
118 
119             v2f vert(a2v v)
120             {
121                 v2f o;
122                 o.pos = UnityObjectToClipPos(v.vertex);
123                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
124                 o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
125 
126                 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
127 
128                 return o;
129 
130             }
131 
132             fixed4 frag(v2f i) : SV_TARGET0
133             {
134                 fixed3 worldNormal = normalize(i.worldNormal);
135                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
136 
137                 fixed4 texcolor = tex2D(_MainTex,i.uv);
138 
139                 fixed3 albedo = texcolor.rgb * _Color.rgb;
140                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
141                 fixed3 diffues = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
142 
143                 return fixed4(ambient + diffues,texcolor.a * _AlphaScale);
144             }
145 
146             ENDCG
147 
148     }
149     }
150         FallBack "Transparent/VertexLit"
151 
152 }

現在來查看下效果:

技術分享圖片

現在我們可以清楚地看到物體的內部了。

五. 總結

透明效果是十分常見且有用的一種實現,我們可以利用它來實現很多有趣的效果。要實現透明,更多地是對渲染的一種理解。本文只是對Unity中渲染的一些基礎解釋,希望能對讀者有所幫助。

本文所用透明紋理及shader

技術分享圖片

【Unity Shader】(五) ------ 透明效果之半透明效果的實現及原理