iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 14
0
自我挑戰組

初見Unity Shader系列 第 14

半透明效果

文章內使用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

會覺得奇怪,為什麼要寫入一遍深度緩衝,請看下圖:

像是這樣的模型,我們還是得先確認它的深度才可以。

這裡是正常的結果:

透明測試

如同深度緩衝中的深度測試,有一個叫做透明測試,比起透明混合,透明測試更加嚴格,只要沒有通過透明測試,該片段就會直接剔除,所以結果不是完全透明就是完全不透明。

Reference


上一篇
時間變數與2D動畫
下一篇
那張藍藍的圖,法線貼圖(上)
系列文
初見Unity Shader30

尚未有邦友留言

立即登入留言