URP 渲染实战 / 第 6 章 多光源与阴影 / 案例 6.6
案例 6.6级联阴影可视化进阶
目标:写一个调试 Shader,按像素所在的级联染色,直观检查级联划分;再对比 1 个和 4 个级联的阴影精度。
Assets/Cookbook/Ch06/CascadeDebug.shader(节选:frag)
half4 frag(Varyings input) : SV_Target { half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).rgb * _BaseColor.rgb; half3 normalWS = normalize(input.normalWS); Light mainLight = GetMainLight(TransformWorldToShadowCoord(input.positionWS)); half3 color = albedo * mainLight.color * mainLight.shadowAttenuation * saturate(dot(normalWS, mainLight.direction)); color += albedo * SampleSH(normalWS) * 0.4; // ComputeCascadeIndex:依次判断像素是否落在 4 个“级联包围球”里,返回第一个命中的序号; // 4 表示超出了所有级联(阴影距离之外) #if defined(_MAIN_LIGHT_SHADOWS_CASCADE) half cascade = ComputeCascadeIndex(input.positionWS); #else half cascade = 0; // 只有一个级联时 URP 不会更新包围球数据,直接当作第 0 级 #endif const half3 kCascadeColors[5] = { half3(1.0, 0.25, 0.2), half3(0.25, 0.85, 0.3), half3(0.25, 0.45, 1.0), half3(1.0, 0.85, 0.2), half3(1, 1, 1) }; half3 tint = kCascadeColors[(uint)cascade]; color = lerp(color, color * tint * 1.6, _TintStrength); return half4(color, 1); }
void main() { vec3 albedo = texture(_BaseMap, uv).rgb * _BaseColor.rgb; vec3 N = normalize(normalWS); Light mainLight = GetMainLight(TransformWorldToShadowCoord(positionWS)); vec3 color = albedo * mainLight.color * mainLight.shadowAttenuation * saturate(dot(N, mainLight.direction)); color += albedo * SampleSH(N) * 0.4; // ComputeCascadeIndex:依次判断像素是否落在 4 个“级联包围球”里,返回第一个命中的序号; // 4 表示超出了所有级联(阴影距离之外)。 // HLSL 版用 _MAIN_LIGHT_SHADOWS_CASCADE 关键字区分;GLSL 版里它是 uniform:_MainLightShadowsCascade > 0.5 float cascade = 0.0; // 只有一个级联时 URP 不会更新包围球数据,直接当作第 0 级 if (_MainLightShadowsCascade > 0.5) cascade = ComputeCascadeIndex(positionWS); const vec3 kCascadeColors[5] = vec3[5]( vec3(1.0, 0.25, 0.2), vec3(0.25, 0.85, 0.3), vec3(0.25, 0.45, 1.0), vec3(1.0, 0.85, 0.2), vec3(1, 1, 1) ); vec3 tint = kCascadeColors[int(cascade)]; color = mix(color, color * tint * 1.6, _TintStrength); fragColor = vec4(color, 1.0); }
ComputeCascadeIndex 是 Shadows.hlsl 中的公开函数。只有一个级联时,URP 不会更新包围球数据,这时读出来的可能是残留值,所以要用 _MAIN_LIGHT_SHADOWS_CASCADE 关键字保护。返回值 4 表示超出了所有级联,用白色表示。


调参建议级联划分的原则是让每一级在屏幕上的“texel 密度”接近。透视投影下远处缩小得很快,所以分割点应该向近处集中(默认的 6.7%、20%、46.7% 就是这个思路)。把 Shadow Distance 设得刚好覆盖需要阴影的范围,往往比加级联更有效:阴影距离减半,所有级联的精度都翻倍。移动端常用 1~2 个级联,配合较短的阴影距离。