【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"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章