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纹理进行放大,使其更明显。可以看下不同缩放比例的效果对比:

scale=1

scale=0.25

scale=0.0025

最后提一点的是,由于半透明的物体,render queue设置为transparent,所以在平行光绘制阴影前的depth pass阶段,是不会把半透明物体的深度信息写入depth buffer的,不过在shadow caster阶段,半透明阴影的信息还是正常绘制到shadowmap中的。这些都可以从frame debug中看出来:

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

posted @ 2021-10-11 20:15  异次元的归来  阅读(425)  评论(0编辑  收藏  举报