UnityShader14:透明效果实现(上)

前置:OpenGL基础31:混合

一、透明度测试(Alpha Test)

透明度测试的目的很直接,如果像素的 Alpha 值小于一个定值,那么这个像素就直接丢弃:

Shader "Jaihk662/AlphaTest1"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex ("MainTex", 2D) = "white" {}
        _Gloss ("Gloss", Range(8.0, 256)) = 20
        _CutOff ("AlphCutOff", Range(0, 1)) = 0.5
    }
    SubShader
    {
        LOD 200
        Tags { "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }
 
            CGPROGRAM
            #pragma vertex vert             //声明顶点着色器的函数
            #pragma fragment frag           //声明片段着色器的函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Gloss;
            fixed _CutOff;
            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float3 wPos: TEXCOORD0;
                float3 wNormal: TEXCOORD1;
                float2 uv: TEXCOORD2;
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));
                fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));

                fixed4 texColor = tex2D(_MainTex, i.uv);
                clip(texColor.a - _CutOff);
                //if ((texColor.a - _CutOff) < 0)
                //    discard;
                fixed3 albedo = texColor.rgb * _DiffuseColor.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));
                fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
 
                return fixed4(ambient + diffuse + specular, 1.0); 
            } 
            ENDCG
        }
    }
    FallBack "Specular"
}

代码中的标签:

  • "Queue" = "AlphaTest":使用 AlphaTest 渲染队列
  • "IgnoreProjector" = "True":这个 shader 不会受到投影器(Projectors)的影响
  • "RenderType" = "TransparentCutout":将 Shader 归入到 TransparentCutout 组中,以指明该 Shader 是一个使用了透明度测试的 Shader

关于渲染队列(RenderQueue):

为了解决渲染顺序问题,Unity 内部使用一系列整数索引来表示每个渲染队列,数字越小越早被渲染

有5个索引已经被 Unity 提前定义了:

  • Background:索引值1000,一般用于绘制背景
  • Geometry:索引值2000,默认渲染队列,一般大部分不透明物体都使用这个队列
  • AlphaTest:索引值2450,一般需要透明度测试的物体使用这个队列
  • Transparent:索引值3000,这个队列中的所有物体按照从后往前的顺序进行渲染,一般需要透明度混合的物体使用这个队列
  • Overlay:索引值4000,最后渲染的物体

二、深度测试与渲染次序

想要正确渲染透明物体,其实要考虑的东西并不少,特别是对于复杂的物体

参考于前置章节的混合公式:

\bar{C}_{\text {result}}=\bar{C}_{\text {source}} * F_{\text {source}}+\bar{C}_{\text {destination}} * F_{\text {destination}}

  • \bar{C}_{\text {source}}:源颜色向量,来自当前纹理的本来的颜色向量
  • \bar{C}_{\text {destination}}:目标颜色向量。这是储存在颜色缓冲中当前位置的颜色向量
  • F_{\text {source}}:源因子。设置了对源颜色的alpha值影响
  • F_{\text {destination}}:目标因子。设置了对目标颜色的alpha影响

可以看出,对于多个简单透明物体排成一排依次遮盖的情况,想要得到正确的渲染效果,必须要按照从后往前的顺序依次渲染物体(混合函数不满足交换律),并且不可进行深度测试(哪怕是被遮盖的物体,也会对最终显示在屏幕上的的颜色有贡献)。基于此透明物体的渲染方法步骤如下

  1. 开启深度测试和深度写入,渲染所有不透明物体
  2. 关闭深度写入,所有半透明物体按照其距离摄像机的远近进行排序,然后按照从后往前的顺序依次渲染这些半透明物体

但是,有破绽!

要知道,实际的深度测试是像素级别的,即每个像素都有一个深度值,可是我们却是对物体远近进行的排序的,若遇到下面两个比较出名的反例就不对了:

对于第一张图,三张透明纸条相互覆盖,对于第二张图,按照距离A物体排在B物体的前面,但是事实上反而是B物体遮盖住了A物体

一个解决方案是:分割网格,可以理解为是将这个物体给“拆掉”,然后逐块去排序。很好理解,但是这样做的成本可不低,因此大部分情况下只能退而求其次保证透明物体不要过于复杂,并且尽量是凸物体

三、透明度混合(Alpha Blending)

深度测试明显不靠谱,它过于“暴力”了,直接将小于某个 alpha 值的所有物体全部丢弃。想要得到真正的半透明效果,还是需要进行透明度混合

Unity 内部混合命令 → Blend:

  • Blend Off:关闭混合
  • Blend SrcFactor DstFactor:开启混合,设置混合因子并计算最终颜色存入颜色缓冲,遵循公式 \bar{C}_{\text {result}}=\bar{C}_{\text {source}} * F_{\text {source}}+\bar{C}_{\text {destination}} * F_{\text {destination}},其中 SrcFactor 对应 F_{\text {source}},DstFactor 对应 F_{\text {destination}}
  • Blend SrcFactor DstFactor SrcFactorA DstFactorA:同上,不过 alpha 通道的计算使用因子 SrcFactorA DstFactorA
  • BlendOp BlendOperation:使用其它公式进行颜色混合操作
  • BlendOp OpColor, OpAlpha:使用其它公式进行颜色混合操作,其中 RGB 通道计算方法为 OpColor,Alpha 通道计算方法为 OpAlpha

一般来讲, F_{\text {destination}} =1 -F_{\text {source}},在 Unity中,1 -F_{\text {source}} 对应 OneMinusSrcAlpha,也就是下面这些数据

  • One:float4(1.0, 1.0, 1.0, 1.0)
  • Zero:float4(0.0, 0.0, 0.0, 0.0)
  • SrcColor:fragment_output(片元颜色,对应 \bar{C}_{\text {source}}
  • SrcAlpha:fragment_output.aaaa
  • DstColor:pixel_color(当前颜色缓冲区中的颜色,对应 \bar{C}_{\text {destination}}
  • DstAlpha:pixel_color.aaaa
  • OneMinusSrcColor:One - SrcColor
  • OneMinusSrcAlpha:One - SrcAlpha
  • OneMinusDstColor:One - DstColor
  • OneMinusDstAlpha:One - DstAlpha

而对于其它的混合公式 BlendOp:

  • Add:\bar{C}_{\text {result}}=\bar{C}_{\text {source}} * F_{\text {source}}+\bar{C}_{\text {destination}} * F_{\text {destination}},默认计算公式
  • Sub:\bar{C}_{\text {result}}=\bar{C}_{\text {source}} * F_{\text {source}}-\bar{C}_{\text {destination}} * F_{\text {destination}}
  • RevSub:\bar{C}_{\text {result}}=\bar{C}_{\text {destination}} * F_{\text {destination}}-\bar{C}_{\text {source}} * F_{\text {source}}
  • Min:逐个通道比较取较小值
  • Max:逐个通道比较取较大值

有了这些之后,代码就很好写了:

Shader "Jaihk662/AlphaTest1"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex ("MainTex", 2D) = "white" {}
        _Gloss ("Gloss", Range(8.0, 256)) = 20
        _AlphaScale ("AlphCutOff", Range(0, 1)) = 1
    }
    SubShader
    {
        LOD 200
        Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }
            ZWrite Off Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Gloss;
            fixed _AlphaScale;
            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float3 wPos: TEXCOORD0;
                float3 wNormal: TEXCOORD1;
                float2 uv: TEXCOORD2;
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.wNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.wPos));
                fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));

                fixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseColor.rgb;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(i.wNormal, wLightDir));
                fixed3 reflectDir = normalize(reflect(-wLightDir, i.wNormal));
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
 
                return fixed4(ambient + diffuse + specular, tex2D(_MainTex, i.uv).a * _AlphaScale); 
            } 
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

这个代码相对于之前反而更加简单

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/111869780