最近想着自己拿SRP写个能用的管线试试的,写到阴影的时候发现自己对Unity的阴影绘制流程并没有一个毕竟全面的了解,正好借此机会把Unity默认管线的阴影绘制过一遍
一、非屏幕空间阴影
我们从最简单的入手,目标PC DX11平台,不要Cascade,不要 screen space shadowmap。在 Graphics setting 里关掉 Cascaded Shadow,场景只打一个 hard shadow 的平行光,此时场景和 framedebugger 如下:
可以看到没有 screen space shadow 的 collect 阶段了,阴影渲染阶段直接调用了 std 的 shadowcaster pass,涉及代码如下:
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
opos = UnityApplyLinearShadowBias(opos);
float4 UnityClipSpaceShadowCasterPos(float4 vertex, float3 normal)
{
float4 wPos = mul(unity_ObjectToWorld, vertex);
if (unity_LightShadowBias.z != 0.0)
{
float3 wNormal = UnityObjectToWorldNormal(normal);
float3 wLight = normalize(UnityWorldSpaceLightDir(wPos.xyz));
// apply normal offset bias (inset position along the normal)
// bias needs to be scaled by sine between normal and light direction
// (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
//
// unity_LightShadowBias.z contains user-specified normal offset amount
// scaled by world space texel size.
float shadowCos = dot(wNormal, wLight);
float shadowSine = sqrt(1-shadowCos*shadowCos);
float normalBias = unity_LightShadowBias.z * shadowSine;
wPos.xyz -= wNormal * normalBias;
}
return mul(UNITY_MATRIX_VP, wPos);
}
float4 UnityApplyLinearShadowBias(float4 clipPos)
{
#if defined(UNITY_REVERSED_Z)
// We use max/min instead of clamp to ensure proper handling of the rare case
// where both numerator and denominator are zero and the fraction becomes NaN.
clipPos.z += max(-1, min(unity_LightShadowBias.x / clipPos.w, 0));
float clamped = min(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#else
clipPos.z += saturate(unity_LightShadowBias.x/clipPos.w);
float clamped = max(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#endif
clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);
return clipPos;
}
其解释我直接引用 topameng 的:
如果z值没有偏移,很容易 Shadow Acne 现象。类似的比如z-fighting, 而如果z偏移值过大,又容易出现Peter Panning 现象,影子和物体出现分离现在常用的shadow bias的计算方法,是基于物体斜度的称为slope scale based depth bias 就是UnityClipSpaceShadowCasterPos中的内容。UnityApplyLinearShadowBias函数是在裁剪空间中的线性增加Z坐标的值unity_LightShadowBiasx表示阴影裁切空间中的线性偏移bias一般是个比较小的负数比如-0.0005,透视相机unity是按照摄像机参数计算的这个值, y 表示插值,一般为1. z 代表阴影slope depth bias scale对于直射光你可以认为ShadowMap就是一张深度图, 你改变颜色信息并不能影响深度。上面提到过是返回SV_Target 和SV_Depth的区别。shadowMap = new RenderTexture(width, height, 16, RenderTextureFormat.Depth);这样给一样能很好的工作不过Unity有新的RenderTextureFormat.Shadowmap格式,在Depth上加了一些保护之类。UnityApplyLinearShadowBias 给的偏移是往离摄像机近的方向挪一点。两个偏移都是往摄像机近一点调节,并不是相互抵消的,如果出现Peter Panning影子和模型分离,可以去光源面板调整阴影的bias项
继续往后看,此时framedebugger 给出的 keywords 是 :
DIRECTIONAL SHADOWS_SCREEN LIGHTPROBE_SH
我们可以在 AutoLight.cginc 中看到对应的阴影代码:
// ---- Screen space direction light shadows helpers (any version)
#if defined (SHADOWS_SCREEN)
#if defined(UNITY_NO_SCREENSPACE_SHADOWS)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
{
#if defined(SHADOWS_