【UnityShader】透明度混合

總述

  • 關閉深度寫入,ZWrite Off,不會修改深度緩衝中的值,但不影響修改顏色緩衝中的值。原因:如果不關閉深度寫入,就無法透過半透明表面看到後面的物體了。
  • 不需要關閉深度測試;
  • Unity 在使用 Blend (除去Blend Off)時,自動開啓混合模式。
  • 混合是一個逐片元的操作,而且它不是可編程的,但卻是高度可配置的。
  • 通常,使用了透明度混合的 Shader 都應該在SubShader 中設置這三個標籤
    Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
  • FallBack "Transparent/VertexLit"

透明度混合基本原理

透明度混合可以得到真正的半透明效果。它會使用當前片元的透明度作爲混合因子,與已經存儲在顏色緩衝中的顏色值進行混合,得到新的顏色。但是,透明度混合需要關閉深度寫入,這使得我們要非常小心物體的渲染順序。

透明度混合的實現

當片元着色器產生一個顏色的時候,可以選擇與顏色緩存中的顏色進行混合。混合中的顏色值包含了 RGBA 四個通道的值。

Blend 命令

把當前自身的顏色和已經存在於顏色緩衝中的顏色值進行混合。

語義 描述
Blend Off 關閉混合
Blend SrcFactor DstFactor 開啓混合,並設置混合因子,源顏色(該片元的顏色)*SrcFactor+目標顏色(緩衝中的)*DstFactor
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面幾乎一樣,只是使用不同的因子來混合透明通道
BlendOp BlendOperation 並非是把源顏色和目標顏色簡單相加後混合,而是使用BlendOperation對它們進行其他操作

關閉深度寫入帶來的問題

當模型本身有複雜的遮擋關係或是包含了複雜的非凸網格的時候,就會有各種各樣因爲排序錯誤而產生的錯誤的透明效果。

這都是由於我們關閉了深度寫入造成的,因爲這樣我們就無法對模型進行像素級別的深度排序。一種解決方法是分割網格,從而可以得到一個“質量優等”的網絡。但很多情況下往往不切實際。這時,可以想辦法重新利用深度寫入,讓模型可以像半透明物體一樣進行淡入淡出。

開啓深度寫入的半透明效果

  • 由於關閉深度寫入而造成錯誤排序的情況,一種解決方法是使用兩個 Pass 來渲染模型:第一個Pass開啓深度寫入,但不輸出顏色,它的目的僅僅是爲了把該模型的深度值寫入深度緩衝中;第二個Pass進行正常的透明度混合,由於上一個Pass已經得到了逐像素正確的深度信息,該Pass就可以按照像素級別的深度排序結果進行透明度渲染。
  • 缺點是多使用一個 Pass 會對性能造成一定的影響。
  • 新添加的Pass的目的是爲了把模型的深度信息寫入深度緩衝中,從而剔除模型中被自身遮擋的片元。

ColorMask

在 ShaderLab 中,ColorMask 用於設置顏色通道的寫掩碼(write mask),它的語義如下:

ColorMask RGB | A | 0 | 其他任何R、G、B、A的組合
  • 當 ColorMask 設爲0時,意味着該Pass不寫入任何顏色通道,即不會輸出任何顏色。
  • ColorMask R,意思是輸出顏色中只有R通道會被寫入
  • ColorMask 0,意思是不會輸出任何顏色
  • 默認值爲RGBA,即四個通道都寫入

ShaderLab的混合命令

源顏色(source color):指的是由片元着色器產生的顏色值。下面用S表示。
目標顏色(destination color):指的是從顏色緩衝中讀取到的顏色值。下面用D表示。
輸出顏色:用O表示,它會重新寫入到顏色緩衝中。
混合等式:由源顏色和目標顏色得到輸出顏色的計算公式。

注意

  1. 當混合時,使用兩個混合等式:一個用於混合RGB通道,一個用於混合A通道。
  2. 當設置混合狀態時,實際上設置的就是混合等式中的操作和因子。默認情況下,混合等式使用的操作都是加操作,只需要設置一下混合因子即可。

Blend命令詳述

命令 描述
Blend Off 關閉混合
Blend SrcFactor DstFactor 開啓混合,並設置混合因子,SrcFactor和DstFactor爲混合因子(係數)
Orgb = SrcFactor * Srgb + DstFactor * Drgb
Oa = SrcFactor * Sa + DstFactor * Da
Blend SrcFactor DstFactor, SrcFactorA DstFactorA 和上面幾乎一樣,只是使用不同的因子來混合透明通道
Orgb = SrcFactor * Srgb + DstFactor * Drgb
Oa = SrcFactorA * Sa + DstFactorA * Da

混合因子

參數 描述
One 因子:1
Zero 因子:0
SrcColor 因子:源顏色值
當用於混合 RGB 的混合等式時,使用 SrcColor 的 RGB 分量作爲混合因子;
當用於混合 A(透明度)的混合等式時,使用 SrcColor 的 A 分量作爲混合因子;
SrcAlpha 因子:源顏色的Alpha值
DstColor 因子:目標顏色值
當用於混合 RGB 的混合等式時,使用 DstColor 的 RGB 分量作爲混合因子;
當用於混合 A(透明度)的混合等式時,使用 DstColor 的 A 分量作爲混合因子;
DstAlpha 因子:目標顏色的Alpha值
OneMinusSrcColor 因子:1-源顏色值
當用於混合 RGB 的混合等式時,使用結果的 RGB 分量作爲混合因子;
當用於混合 A(透明度)的混合等式時,使用結果的 A 分量作爲混合因子;
OneMinusSrcAlpha 因子:1-源顏色的Alpha值
OneMinusDstColor 因子:1-目標顏色值
OneMinusDstAlpha 因子:1-目標顏色的Alpha值

BnendOp BlendOperation(混合操作命令)

並非是把源顏色和目標顏色簡單相加後混合,而是使用BlendOperation對它們進行其他操作。

參數 描述
Add 將源顏色和目標顏色相加(默認的混合操作)
Orgb = SrcFactor * Srgb + DstFactor * Drgb
Oa = SrcFactor * Sa + DstFactor * Da
Sub 用源顏色減去目標顏色
Orgb = SrcFactor * Srgb - DstFactor * Drgb
Oa = SrcFactor * Sa - DstFactor * Da
RevSub 用目標顏色減去源顏色
Orgb = DstFactor * Drgb - SrcFactor * Srgb
Oa = DstFactor * Da - SrcFactor * Sa
Min 取源顏色和目標顏色中的較小值,是逐分量比較
Orgba = (min(Sr,Dr),min(Sg,Dg),min(Sb,Db),min(Sa,Da))
Max 取源顏色和目標顏色中的較大值,是逐分量比較
Orgba = (max(Sr,Dr),max(Sg,Dg),max(Sb,Db),max(Sa,Da))

常見的混合類型

//正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha

//柔和相加(Soft Addtive)
Blend OneMinusDstAlpha One

//正片疊底(Multiply),即相乘
Blend DstColor Zero

//兩倍相乘(2x Multiply)
Blend DstColor SrcColor

//變暗(Darken)
BlendOp Min
Blend One One

//變亮(Lighten)
BlendOp Max
Blend One One

//濾色(Screen)
Blend OneMinusDstColor One
// 等同於
Blend One OneMinusSrcColor

//線性減淡(Linear Dodge)
Blend One One

注意:

  1. 雖然上面使用Min和Max混合操作時仍然設置了混合因子,但實際上它們(混合因子)並不會對結果有任何影響,因爲Min和Max混合操作會忽略混合因子。
  2. 雖然上面有些混合模式並沒有設置混合操作的類型,但默認就是使用加法操作,相當於設置了 BlendOp Add。

透明度混合代碼實現

Shader "zjt/Shader8_2_blend"
{
    // 透明度混合, Blend SrcFactor DstFactor
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 // 這個只是用來控制整體的透明度
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        
        Pass {
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _AlphaScale;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                
                o.worldPos = mul(_Object2World, v.vertex).xyz;
                
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                fixed4 texColor = tex2D(_MainTex, i.uv);
                
                fixed3 albedo = texColor.rgb * _Color.rgb;
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
                
                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            
            ENDCG
        }
    } 
    FallBack "Transparent/VertexLit"
}

開啓深度寫入的半透明效果代碼實現

    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        
        // Extra pass that renders to depth buffer only
        Pass {
            ZWrite On
            ColorMask 0
        }
        
        Pass {
            Tags { "LightMode"="ForwardBase" }
            
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _AlphaScale;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                
                o.worldPos = mul(_Object2World, v.vertex).xyz;
                
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                fixed4 texColor = tex2D(_MainTex, i.uv);
                
                fixed3 albedo = texColor.rgb * _Color.rgb;
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
                
                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            
            ENDCG
        }
    } 
    FallBack "Transparent/VertexLit"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章