文章內使用Unity 2019 LTS
實作一個半透明效果會先需要了解一下深度緩衝跟混合效果,我在系列文中有介紹過它們。
要實作出半透明效果,首先請想一個問題,深度緩衝會幫我們處理繪製順序的問題,誰在前,誰在後,會再經過深度測試後,把沒通過深度測試的片段處理掉,但如果一個半透明的物體擋在一個不透光的物體前面呢?
正常來說擋在前面的,深度緩衝會把後面的片段去掉,但透過半透明的物體,又要顯示出後面的片段,這成了一個矛盾。
這時我們就需要使用「混合效果」,而且是針對A通道進行混合
據之前所介紹過的混合效果,它會將目前片段的顏色乘上因數,然後加上(註1)當前顏色緩衝內的顏色乘上因數,最後得出新的顏色,返回至原本的顏色緩衝當中,
因為是透明混合,所以對A通道進行混合實作出需求會變成:
Blend SrcAlpha OneMinusSrcAlpha
// => DstColor = ScrColor * SrcAlpha + (1 - ScrAlpha) * DstColor
註1: 這邊的「加上」為預設的混合操作,可以利用BlendOp
更改操作方式
請記得還有一件事,在本篇最「開始之前...」敘述到深度緩衝會造成問題,所以在實作半透明時,需要把深度緩衝關閉。
Shader "Learning/Ch8/alpha-blend"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
// 可以通過調節該值,得到想要的半透明效果
_AlphaScale ("Alpha Scale", Range(0, 1)) = 0.5
}
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
// 第一個Pass
Pass
{
ZWrite on
ColorMask 0
}
// 第二個Pass
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(appdata_base app)
{
v2f o;
o.pos = UnityObjectToClipPos(app.vertex);
o.worldPos = mul(unity_ObjectToWorld, app.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(app.normal);
o.uv = TRANSFORM_TEX(app.texcoord, _MainTex);
return o;
}
// 漫反射 + 紋理貼圖
fixed4 frag(v2f i) : SV_Target
{
fixed3 norm = normalize(i.worldNormal);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(norm, lightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
ENDCG
}
}
}
比起以往的範例,這次用了兩個Pass,而且第一個Pass還只有把深度測試打開,還有將ColorMask設為0
先來說甚麼是ColorMask,以及它的用途:
ColorMask RGB | A | 0
ColorMask
代表對特定的顏色通道進行寫入,開發者可以對RGB進行任何排序,例如: ColorMask RB
是指針對顏色緩衝的RB通道進行寫入,而範例中的ColorMask 0
表示不對顏色緩衝進行寫入,原因來自於上面的ZWrite On
會覺得奇怪,為什麼要寫入一遍深度緩衝,請看下圖:
像是這樣的模型,我們還是得先確認它的深度才可以。
這裡是正常的結果:
如同深度緩衝中的深度測試,有一個叫做透明測試,比起透明混合,透明測試更加嚴格,只要沒有通過透明測試,該片段就會直接剔除,所以結果不是完全透明就是完全不透明。