Unity Shader 之 透明效果

本文引用 Unity Shader入門精要緩存

開啓透明混合後,一個物體被渲染到屏幕上時,每一個片元除了顏色值和深度值外,還有——透明度。透明度爲1,則徹底不透明,透明度爲0,則徹底不會顯示。函數

在Unity中咱們有兩種方式實現透明度效果性能

  • 透明度測試(Alpha Test):這種方式沒法獲得真正的半透明效果。只是0或1(徹底透明和徹底不透明)
  • 透明度混合(Alpha Blending):使用當前的透明度做爲混合因子,與已經存儲在顏色緩衝中的顏色值進行混合。

那讓我討論第一個問題:渲染順序測試

渲染順序

爲何先說渲染順序呢?spa

在以前的兩篇文章中,我並無涉獵到渲染順序。由於對於不透明的物體,渲染順序的決定是由深度緩衝決定的。3d

深度緩衝的基本思想爲:根據深度緩衝中的值來判斷該片元距離攝像機的距離,當渲染一個片元時,須要把深度值和已經存在於深度緩衝中的值進行比較(前提:開啓深度測試),若是它的值距離攝像機更遠,說明該片元不該被渲染。code

可是當咱們使用透明度混合時,就得關閉深度寫入(ZWrite)。orm

爲何關閉深度寫入呢?blog

一個半透明的物體後面若是有物體的話,應該是能夠被看到的,可是深度寫入會把它剔除掉。排序

因此對於渲染順序就得咱們自行控制了。

 

上兩個圖分別是兩種渲染順序狀況,

圖一:透明物體在前,不透明物體在後。

  • 狀況一:若是先渲染不透明物體(開啓深度寫入和深度測試),將不透明物體顏色寫入顏色緩存,深度寫入深度緩衝,而後渲染透明物體(關閉深度寫入,開啓深度測試),將透明物體的顏色與顏色緩衝中的顏色混合,獲得正確結果。
  • 狀況二:先渲染透明物體(關閉深度寫入,開啓深度測試),透明物體顏色寫入顏色緩衝,而後渲染不透明物體(開啓深度寫入和深度測試),深度緩存中沒有內容,因此直接覆蓋顏色緩衝。獲得錯誤結果。

圖二:兩個透明物體。

  • 狀況一:先渲染後方的透明物體,顏色寫入顏色緩衝,而後渲染前方透明物體,顏色和顏色緩衝中的顏色混合,獲得正確結果。
  • 狀況二:先渲染前方的透明物體,顏色寫入顏色緩衝,而後渲染後方透明物體,顏色和顏色緩衝中的顏色混合,獲得後方物體在前方物體前的畫面,獲得錯誤結果。

基於這種狀況Unity給咱們一種解決方式(大多數引擎的解決方式):物體排序+分割網格。

  • 物體排序:1.先渲染全部不透明物體,並開啓它們的深度測試和深度寫入。2.把半透明物體按它們距離攝像機的遠近進行排序,而後按照從後往前的順序渲染半透明物體(開啓深度測試,關閉深度寫入)。
  • 分割網格:解決物體排序遺留問題:循環重疊(例:3個物體互相重疊),咱們把網格分割,分別判斷分開後的網格的順序來進行渲染。

Unity Shader 的渲染順序

Unity提供了渲染隊列(render queue)解決渲染順序的問題。用SubShader的Queue標籤來設置咱們的模型在哪一個渲染隊列。索引越小越先渲染

Unity的5個渲染隊列:

  • Background:索引:1000,這個隊列是最早渲染的。
  • Geometry:索引:2000,默認渲染隊列。不透明物體使用這個隊列。
  • Alpha Test:索引:2450,須要透明度測試的物體使用該隊列。
  • Transparent:索引:3000,按照從後往前的順序渲染,使用透明度混合的物體都應該用該隊列。
  • Overlay:索引:4000,該隊列用於實現一些疊加效果,最後渲染。

透明度測試

只要一個片元的透明度不知足條件,那麼這個片元就會被捨棄。用clip來進行透明度測試。

立方體的貼圖每一個塊都是不一樣的透明度分別是50%、60%、70%、80%,下面是我把Alpha Cutoff設爲0.7時的效果。你會發現50%和60%透明度的貼圖已經不見了。

透明度測試Shader代碼:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
 8     }
 9     SubShader
10     {
11         // 透明度測試隊列爲AlphaTest,因此Queue=AlphaTest
12         // RenderType標籤讓Unity把這個Shader納入提早定義的組中,以指明該Shader是一個使用了透明度測試的Shader
13         // IgonreProjector爲True代表此Shader不受投影器(Projectors)影響
14         Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
15 
16         Pass
17         {
18             Tags { "LightMode"="ForwardBase" }
19 
20             CGPROGRAM
21             #pragma vertex vert
22             #pragma fragment frag
23             
24             #include "UnityCG.cginc"
25             #include "Lighting.cginc"
26 
27             struct a2v
28             {
29                 float4 vertex : POSITION;
30                 float3 normal : NORMAL;
31                 float4 texcoord : TEXCOORD0;
32             };
33 
34             struct v2f
35             {
36                 float4 pos : SV_POSITION;
37                 float2 uv : TEXCOORD0;
38                 float3 worldNormal : TEXCOORD1;
39                 float3 worldPos : TEXCOORD2;
40             };
41 
42             sampler2D _MainTex;
43             float4 _MainTex_ST;
44             fixed4 _Color;
45             // 用於決定調用clip函數時進行的透明度測試使用的判斷條件
46             fixed _Cutoff;
47             
48             v2f vert (a2v v)
49             {
50                 v2f o;
51 
52                 o.pos = UnityObjectToClipPos(v.vertex);
53                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
54                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
55                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
56 
57                 return o;
58             }
59             
60             fixed4 frag (v2f i) : SV_Target
61             {
62                 fixed3 worldNormal = normalize(i.worldNormal);
63                 fixed3 worldPos = normalize(i.worldPos);
64                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
65                 // 紋素值
66                 fixed4 texColor = tex2D(_MainTex, i.uv);
67                 // 原理
68                 // if ((texColor.a - _Cutoff) < 0.0) { discard; }
69                 // 若是結果小於0,將片元捨棄
70                 clip(texColor.a - _Cutoff);
71                 // 反射率
72                 fixed3 albedo =  texColor.rgb * _Color.rgb;
73                 // 環境光
74                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
75                 // 漫反射
76                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
77                 return fixed4(ambient + diffuse, 1.0);
78             }
79             ENDCG
80         }
81     }
82 }

透明度混合

那咱們看看Unity給咱們提供的混合命令——Blend。給出Blend的經常使用語義。

  • Blend Off:關閉混合
  • Blend SrcFactor DstFactor:開啓混合,設置混合因子。源顏色(片元顏色)乘以SrcFactor,目標顏色(已經在顏色緩衝中的顏色)乘以DstFactor,而後把二者相加
  • Blend SrcFactor DstFactor,SrcFactorA DstFactorA:同上,不過把透明通道(a)與顏色通道(rgb)用不一樣的因子。
  • BlendOp BlendOperation:使用BlendOperation對其進行其餘操做,非簡單相加混合。

混合公式:DstColorNew = SrcAlpha * SrcColor + (1 - SrcAlpha) * DstColorOld

下面是最簡單的調整透明通道值的效果,AlphaScale = 0.6

Shader代碼爲:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
 8     }
 9     SubShader
10     {
11         // 透明度混合隊列爲Transparent,因此Queue=Transparent
12         // RenderType標籤讓Unity把這個Shader納入提早定義的組中,以指明該Shader是一個使用了透明度混合的Shader
13         // IgonreProjector爲True代表此Shader不受投影器(Projectors)影響
14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
15 
16         Pass
17         {
18             Tags { "LightMode"="ForwardBase" }
19             
20             // 關閉深度寫入
21             ZWrite Off
22             // 開啓混合模式,並設置混合因子爲SrcAlpha和OneMinusSrcAlpha
23             Blend SrcAlpha OneMinusSrcAlpha
24 
25             CGPROGRAM
26             #pragma vertex vert
27             #pragma fragment frag
28             
29             #include "UnityCG.cginc"
30             #include "Lighting.cginc"
31 
32             struct a2v
33             {
34                 float4 vertex : POSITION;
35                 float3 normal : NORMAL;
36                 float4 texcoord : TEXCOORD0;
37             };
38 
39             struct v2f
40             {
41                 float4 pos : SV_POSITION;
42                 float2 uv : TEXCOORD0;
43                 float3 worldNormal : TEXCOORD1;
44                 float3 worldPos : TEXCOORD2;
45             };
46 
47             sampler2D _MainTex;
48             float4 _MainTex_ST;
49             fixed4 _Color;
50             // 用於決定調用clip函數時進行的透明度測試使用的判斷條件
51             fixed _AlphaScale;
52             
53             v2f vert (a2v v)
54             {
55                 v2f o;
56 
57                 o.pos = UnityObjectToClipPos(v.vertex);
58                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
59                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
60                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
61 
62                 return o;
63             }
64             
65             fixed4 frag (v2f i) : SV_Target
66             {
67                 fixed3 worldNormal = normalize(i.worldNormal);
68                 fixed3 worldPos = normalize(i.worldPos);
69                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
70                 // 紋素值
71                 fixed4 texColor = tex2D(_MainTex, i.uv);
72                 // 反射率
73                 fixed3 albedo =  texColor.rgb * _Color.rgb;
74                 // 環境光
75                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
76                 // 漫反射
77                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
78                 // 返回顏色,透明度部分乘以咱們設定的值
79                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
80             }
81             ENDCG
82         }
83     }
84 }

然而這種實現方式是有問題的。就像前面說的同樣,它並不能渲染正確的順序。

解決方式:用兩個Pass來渲染模型

  • 第一個Pass:開啓深度寫入,但不輸出顏色,目的僅僅爲了填充深度緩衝。
  • 第二個Pass:正常的透明度混合,因爲上一個Pass已經獲得了逐像素的正確深度信息,該Pass就能夠按照像素級別的深度排序進行透明渲染。
  • 缺點:多了一個Pass性能有所影響。

代碼以下:

 1 Shader "My Shader/AlphaShader"
 2 {
 3     Properties
 4     {
 5         _Color ("Color", Color) = (1,1,1,1)
 6         _MainTex ("Texture", 2D) = "white" {}
 7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
 8     }
 9     SubShader
10     {
11         // 透明度混合隊列爲Transparent,因此Queue=Transparent
12         // RenderType標籤讓Unity把這個Shader納入提早定義的組中,以指明該Shader是一個使用了透明度混合的Shader
13         // IgonreProjector爲True代表此Shader不受投影器(Projectors)影響
14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
15 
16         Pass
17         {
18             // 開啓深度寫入
19             ZWrite On
20             // 設置顏色通道的寫掩碼,0爲不寫入任何顏色
21             ColorMask 0
22         }
23 
24         Pass
25         {
26             Tags { "LightMode"="ForwardBase" }
27             
28             // 關閉深度寫入
29             ZWrite Off
30             // 開啓混合模式,並設置混合因子爲SrcAlpha和OneMinusSrcAlpha
31             Blend SrcAlpha OneMinusSrcAlpha
32 
33             CGPROGRAM
34             #pragma vertex vert
35             #pragma fragment frag
36             
37             #include "UnityCG.cginc"
38             #include "Lighting.cginc"
39 
40             struct a2v
41             {
42                 float4 vertex : POSITION;
43                 float3 normal : NORMAL;
44                 float4 texcoord : TEXCOORD0;
45             };
46 
47             struct v2f
48             {
49                 float4 pos : SV_POSITION;
50                 float2 uv : TEXCOORD0;
51                 float3 worldNormal : TEXCOORD1;
52                 float3 worldPos : TEXCOORD2;
53             };
54 
55             sampler2D _MainTex;
56             float4 _MainTex_ST;
57             fixed4 _Color;
58             // 用於決定調用clip函數時進行的透明度測試使用的判斷條件
59             fixed _AlphaScale;
60             
61             v2f vert (a2v v)
62             {
63                 v2f o;
64 
65                 o.pos = UnityObjectToClipPos(v.vertex);
66                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
67                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
68                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
69 
70                 return o;
71             }
72             
73             fixed4 frag (v2f i) : SV_Target
74             {
75                 fixed3 worldNormal = normalize(i.worldNormal);
76                 fixed3 worldPos = normalize(i.worldPos);
77                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
78                 // 紋素值
79                 fixed4 texColor = tex2D(_MainTex, i.uv);
80                 // 反射率
81                 fixed3 albedo =  texColor.rgb * _Color.rgb;
82                 // 環境光
83                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
84                 // 漫反射
85                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
86                 // 返回顏色,透明度部分乘以咱們設定的值
87                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
88             }
89             ENDCG
90         }
91     }
92 }

雙面渲染的透明效果

對於剛纔的立方體,雖然是透明的,可是卻看不到裏面的構造,是否是感受也不太對,若是想看到內部構造怎麼辦呢?

Unity默認會剔除物體的背面(就是內部),那麼咱們能夠用Cull指令來控制須要剔除哪一個面的渲染圖元。

  • Cull Back:背對着攝像機的渲染圖元不會渲染,默認狀況。
  • Cull Front:朝向攝像機的渲染圖元不會渲染。
  • Cull Off:關閉剔除功能,全部的都會渲染。缺點:須要渲染的數目成倍增長,除非用於特殊效果,建議不開啓。

接下來咱們看一下效果:

這回也是用連個Pass來完成:第一個Pass渲染背面,第二個Pass渲染前面

Shader 代碼以下:

  1 Shader "My Shader/AlphaShader"
  2 {
  3     Properties
  4     {
  5         _Color ("Color", Color) = (1,1,1,1)
  6         _MainTex ("Texture", 2D) = "white" {}
  7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
  8     }
  9     SubShader
 10     {
 11         // 透明度混合隊列爲Transparent,因此Queue=Transparent
 12         // RenderType標籤讓Unity把這個Shader納入提早定義的組中,以指明該Shader是一個使用了透明度混合的Shader
 13         // IgonreProjector爲True代表此Shader不受投影器(Projectors)影響
 14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
 15 
 16         Pass
 17         {
 18             Tags { "LightMode"="ForwardBase" }
 19 
 20             // 只渲染背面
 21             Cull Front
 22             // 關閉深度寫入
 23             ZWrite Off
 24             // 開啓混合模式,並設置混合因子爲SrcAlpha和OneMinusSrcAlpha
 25             Blend SrcAlpha OneMinusSrcAlpha
 26 
 27             CGPROGRAM
 28             #pragma vertex vert
 29             #pragma fragment frag
 30             
 31             #include "UnityCG.cginc"
 32             #include "Lighting.cginc"
 33 
 34             struct a2v
 35             {
 36                 float4 vertex : POSITION;
 37                 float3 normal : NORMAL;
 38                 float4 texcoord : TEXCOORD0;
 39             };
 40 
 41             struct v2f
 42             {
 43                 float4 pos : SV_POSITION;
 44                 float2 uv : TEXCOORD0;
 45                 float3 worldNormal : TEXCOORD1;
 46                 float3 worldPos : TEXCOORD2;
 47             };
 48 
 49             sampler2D _MainTex;
 50             float4 _MainTex_ST;
 51             fixed4 _Color;
 52             // 用於決定調用clip函數時進行的透明度測試使用的判斷條件
 53             fixed _AlphaScale;
 54             
 55             v2f vert (a2v v)
 56             {
 57                 v2f o;
 58 
 59                 o.pos = UnityObjectToClipPos(v.vertex);
 60                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
 61                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
 62                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
 63 
 64                 return o;
 65             }
 66             
 67             fixed4 frag (v2f i) : SV_Target
 68             {
 69                 fixed3 worldNormal = normalize(i.worldNormal);
 70                 fixed3 worldPos = normalize(i.worldPos);
 71                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
 72                 // 紋素值
 73                 fixed4 texColor = tex2D(_MainTex, i.uv);
 74                 // 反射率
 75                 fixed3 albedo =  texColor.rgb * _Color.rgb;
 76                 // 環境光
 77                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
 78                 // 漫反射
 79                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
 80                 // 返回顏色,透明度部分乘以咱們設定的值
 81                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
 82             }
 83             ENDCG
 84         }
 85 
 86         Pass
 87         {
 88             Tags { "LightMode"="ForwardBase" }
 89             
 90             // 只渲染前面
 91             Cull Back 
 92             // 關閉深度寫入
 93             ZWrite Off
 94             // 開啓混合模式,並設置混合因子爲SrcAlpha和OneMinusSrcAlpha
 95             Blend SrcAlpha OneMinusSrcAlpha
 96 
 97             CGPROGRAM
 98             #pragma vertex vert
 99             #pragma fragment frag
100             
101             #include "UnityCG.cginc"
102             #include "Lighting.cginc"
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                 float2 uv : TEXCOORD0;
115                 float3 worldNormal : TEXCOORD1;
116                 float3 worldPos : TEXCOORD2;
117             };
118 
119             sampler2D _MainTex;
120             float4 _MainTex_ST;
121             fixed4 _Color;
122             // 用於決定調用clip函數時進行的透明度測試使用的判斷條件
123             fixed _AlphaScale;
124             
125             v2f vert (a2v v)
126             {
127                 v2f o;
128 
129                 o.pos = UnityObjectToClipPos(v.vertex);
130                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
131                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
132                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
133 
134                 return o;
135             }
136             
137             fixed4 frag (v2f i) : SV_Target
138             {
139                 fixed3 worldNormal = normalize(i.worldNormal);
140                 fixed3 worldPos = normalize(i.worldPos);
141                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
142                 // 紋素值
143                 fixed4 texColor = tex2D(_MainTex, i.uv);
144                 // 反射率
145                 fixed3 albedo =  texColor.rgb * _Color.rgb;
146                 // 環境光
147                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
148                 // 漫反射
149                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
150                 // 返回顏色,透明度部分乘以咱們設定的值
151                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
152             }
153             ENDCG
154         }
155     }
156 }
相關文章
相關標籤/搜索