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