GameTech/URP 渲染实战

案例 7.9POM 自阴影高阶

目标:在案例 7.8 的基础上加入软自阴影,并把结果乘到主光的衰减上。

Assets/Cookbook/Ch07/Parallax.hlsl(节选:shadow)
// 视差自阴影:从命中点朝灯光方向往上走,检查途中有没有更高的凸起挡住光。
// 返回 1 = 不在阴影里,0 = 完全遮挡。用“挡了多少 × 离起点多近”做软化。
// dx/dy 传入原始 UV 的导数:偏移后的 UV 在台阶边缘不连续,用它求导会选错 mip。
half ParallaxSelfShadow(TEXTURE2D_PARAM(heightMap, sampler_heightMap), float2 uv, float2 dx, float2 dy,
                        float hitDepth, half3 lightDirTS, float scale, float steps, float softness)
{
    if (lightDirTS.z <= 0) return 0;           // 灯在表面背面
    if (hitDepth <= 0.001) return 1;           // 已经在最高处,没有东西能挡它

    float layerDepth = hitDepth / steps;       // 只需要从命中深度走回表面
    float2 deltaUV = lightDirTS.xy / max(lightDirTS.z, 0.05) * scale * layerDepth;

    float rayDepth = hitDepth;
    float occlusion = 0;
    [loop] for (int i = 1; i <= (int)steps && i <= CH07_MAX_STEPS; i++)
    {
        uv += deltaUV;
        rayDepth -= layerDepth;
        float mapDepth = CH07_DEPTH(uv);
        // mapDepth < rayDepth:这里的表面比光线还高 → 光线被挡
        float o = (rayDepth - mapDepth) * softness * (1.0 - (i - 1) / steps);
        occlusion = max(occlusion, o);
    }
    return 1.0 - saturate(occlusion);
}
// 视差自阴影:从命中点朝灯光方向往上走,检查途中有没有更高的凸起挡住光。
// 返回 1 = 不在阴影里,0 = 完全遮挡。用“挡了多少 × 离起点多近”做软化。
// dx/dy 传入原始 UV 的导数:偏移后的 UV 在台阶边缘不连续,用它求导会选错 mip。
float ParallaxSelfShadow(sampler2D heightMap, vec2 uv, vec2 dx, vec2 dy,
                         float hitDepth, vec3 lightDirTS, float scale, float steps, float softness)
{
    if (lightDirTS.z <= 0.0) return 0.0;       // 灯在表面背面
    if (hitDepth <= 0.001) return 1.0;         // 已经在最高处,没有东西能挡它

    float layerDepth = hitDepth / steps;       // 只需要从命中深度走回表面
    vec2 deltaUV = lightDirTS.xy / max(lightDirTS.z, 0.05) * scale * layerDepth;

    float rayDepth = hitDepth;
    float occlusion = 0.0;
    for (int i = 1; i <= int(steps) && i <= CH07_MAX_STEPS; i++)
    {
        uv += deltaUV;
        rayDepth -= layerDepth;
        float mapDepth = CH07_DEPTH(uv);
        // mapDepth < rayDepth:这里的表面比光线还高 → 光线被挡
        float o = (rayDepth - mapDepth) * softness * (1.0 - float(i - 1) / steps);
        occlusion = max(occlusion, o);
    }
    return 1.0 - saturate(occlusion);
}

实现要点:

图 7-19低角度的主光从左侧照来。左:POM 无自阴影,砖缝底部被照得和砖面一样亮;右:开启自阴影,每块砖在右侧和下方的砖缝里投下柔和的影子,凹凸感大幅增强。

自阴影对凹凸感的贡献往往比视差本身还大,尤其在低角度光照(清晨、傍晚、手电筒)下。它的代价是又一轮步进:每像素多 8~32 次采样。移动端可以只在近处开启,或者把步数降到 4~8 并加大软化系数。