在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中看出:

Unity中的半透明阴影_3d

这个纹理长啥样呢?我们可以写一个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通道值:

Unity中的半透明阴影_Unity_02

由于_DitherMaskLOD纹理是4×4×16的,我们实际上在v方向也重复了16次,因此真正的纹理长这样:

Unity中的半透明阴影_3d_03

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

Unity中的半透明阴影_Unity_04

Unity中的半透明阴影_Unity_05

Unity中的半透明阴影_Shader_06

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

Unity中的半透明阴影_Unity_07

-