1. 程式人生 > 其它 >Unity中的半透明陰影

Unity中的半透明陰影

在Unity中渲染半透明陰影可以使用Unity提供的dither texture。在這之前,先考慮一般半透明物體的渲染流程:

  • 設定render queue為Transparent,這樣不透明的物體會先渲染,然後位於被不透明物體遮擋的透明物體就可以不必渲染,減少開銷
  • 設定render type為Transparent,便於一些replacement操作
  • 設定blend mode,例如fade是srcBlend = SrcAlpha,dstBlend = OneMinusSrcAlpha,而Transparent是srcBlend = One,dstBlend = OneMinusSrcAlpha
  • 關閉深度寫入,zwrite = false

Unity中的半透明陰影本質上是不透明的,只是對dither texture進行取樣,根據取樣的結果,clip掉一些fragment,使得shadow caster過程中只有一部分陰影資訊會被繪製到shadowmap上。Unity builtin shaders提供的參考寫法如下:

struct Interpolators {
	UNITY_VPOS_TYPE vpos : VPOS;
    ...
};

float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
    ...
	half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy*0.25,alpha*0.9375)).a;
    clip(dither - 0.01);
    ...
}

vpos表示的是當前畫素在screen space下的座標,_DitherMaskLOD是一個尺寸為4×4×16的3D紋理,這個可以從frame debug中看出:

這個紋理長啥樣呢?我們可以寫一個shader手動把它輸出:

Shader "Custom/TextureViewShader"
{
    Properties
    {
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            sampler3D _DitherMaskLOD;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                i.uv *= 16;
                fixed4 col = tex3D(_DitherMaskLOD, float3(i.uv, floor(i.uv.x) * 0.0625)).a;
                return col;
            }
            ENDCG
        }
    }
}

從frame debug可知紋理是alpha8格式,因而只需輸出alpha通道值:

由於_DitherMaskLOD紋理是4×4×16的,我們實際上在v方向也重複了16次,因此真正的紋理長這樣:

可以看出,該紋理從4×4的全黑畫素開始,隨著維度z的增加,黑色畫素每次減少1個,直至最後全部變成4×4的全白畫素。通過這個規律,不難理解前面程式碼tex3D的取樣座標z的寫法為alpha*0.9375。alpha表示透明度,0為完全透明1為完全不透明,而0.9375實際上就是15/16。這就是說在0的情況下采樣的3D紋理是純黑畫素,會被clip掉,不會產生陰影;而1的情況下采樣的3D紋理是全白畫素,會完全產生陰影,就彷彿跟不透明物體一樣。

最後再看一下這個vpos*0.25是幹啥的。vpos表示的是pixel在screen space下的座標,x和y取值範圍類似[0, screenWidth],[0, screenHeight]。乘以0.25的係數就是對取值範圍進行縮放處理,換言之就是將_DitherMaskLOD紋理進行放大,使其更明顯。可以看下不同縮放比例的效果對比:

最後提一點的是,由於半透明的物體,render queue設定為transparent,所以在平行光繪製陰影前的depth pass階段,是不會把半透明物體的深度資訊寫入depth buffer的,不過在shadow caster階段,半透明陰影的資訊還是正常繪製到shadowmap中的。這些都可以從frame debug中看出來:

如果你覺得我的文章有幫助,歡迎關注我的微信公眾號(大齡社畜的遊戲開發之路-