GameTech/URP 渲染实战

案例 30.1可切换输出的调试视图 Shader入门

目标:写一个带法线贴图、能接收阴影的 Shader,用一个枚举属性切换输出最终光照、世界法线、UV、N·L、阴影衰减、切线空间法线和世界网格。

Assets/Cookbook/Ch30/DebugView.shader(节选:debug)
half4 frag(Varyings i) : SV_Target
{
    half3 normalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, i.uv), _BumpScale);
    half3 bitangentWS = i.tangentWS.w * cross(i.normalWS, i.tangentWS.xyz);
    half3 normalWS = normalize(TransformTangentToWorld(normalTS, half3x3(i.tangentWS.xyz, bitangentWS, i.normalWS)));

    Light light = GetMainLight(TransformWorldToShadowCoord(i.positionWS));
    half ndl = saturate(dot(normalWS, light.direction));
    int view = (int)_DebugView;

    // 约定:方向向量 [-1,1] 映射到 [0,1] 再显示;标量直接显示为灰度
    if (view == 1) return half4(normalWS * 0.5 + 0.5, 1);
    if (view == 2) return half4(frac(i.uv), 0, 1);
    if (view == 3) return half4(ndl.xxx, 1);
    if (view == 4) return half4(light.shadowAttenuation.xxx, 1);
    if (view == 5) return half4(normalTS * 0.5 + 0.5, 1);
    if (view == 6)
    {
        float3 g = abs(frac(i.positionWS * 2 + 0.5) - 0.5) / fwidth(i.positionWS * 2);
        half line01 = 1 - saturate(min(g.x, min(g.y, g.z)));
        return half4(lerp(half3(0.15, 0.15, 0.18), half3(1, 0.6, 0.2), line01), 1);
    }

    half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv).rgb;
    half3 color = albedo * (light.color * ndl * light.shadowAttenuation + SampleSH(normalWS));
    return half4(color, 1);
}
void main()
{
    vec3 normalTS = UnpackNormalScale(texture(_BumpMap, uv), _BumpScale);
    vec3 bitangentWS = tangentWS.w * cross(normalWS, tangentWS.xyz);
    vec3 nWS = normalize(TransformTangentToWorld(normalTS, mat3(tangentWS.xyz, bitangentWS, normalWS)));

    Light light = GetMainLight(TransformWorldToShadowCoord(positionWS));
    float ndl = saturate(dot(nWS, light.direction));
    int view = int(_DebugView);

    // 约定:方向向量 [-1,1] 映射到 [0,1] 再显示;标量直接显示为灰度
    if (view == 1) { fragColor = vec4(nWS * 0.5 + 0.5, 1.0); return; }
    if (view == 2) { fragColor = vec4(fract(uv), 0.0, 1.0); return; }
    if (view == 3) { fragColor = vec4(vec3(ndl), 1.0); return; }
    if (view == 4) { fragColor = vec4(vec3(light.shadowAttenuation), 1.0); return; }
    if (view == 5) { fragColor = vec4(normalTS * 0.5 + 0.5, 1.0); return; }
    if (view == 6)
    {
        vec3 g = abs(fract(positionWS * 2.0 + 0.5) - 0.5) / fwidth(positionWS * 2.0);
        float line01 = 1.0 - saturate(min(g.x, min(g.y, g.z)));
        fragColor = vec4(mix(vec3(0.15, 0.15, 0.18), vec3(1, 0.6, 0.2), line01), 1.0);
        return;
    }

    vec3 albedo = texture(_BaseMap, uv).rgb;
    vec3 color = albedo * (light.color * ndl * light.shadowAttenuation + SampleSH(nWS));
    fragColor = vec4(color, 1.0);
}

调试模式用一个普通的 float 属性加 if 分支,而不是关键字:调试 Shader 不在乎这点分支开销,却能避免变体数量翻倍,切换时也不需要重新编译。[Enum(Final,0, NormalWS,1, …)] 让 Inspector 显示成下拉菜单。投影阴影借用了 Lit 的 ShadowCaster Pass(UsePass),这会让本 Shader 不兼容 SRP Batcher(两个 Pass 的 UnityPerMaterial 不一致),对调试 Shader 无所谓。

图 30-1同一组物体的四种调试视图。从左到右:最终光照;世界空间法线(叠加了法线贴图,砖缝的凹凸清晰可见);UV(球体的 U 方向绕一圈,立方体每个面独立 0~1,圆环纽结的 UV 沿管道拉伸);N·L(只看主光的余弦项,不含阴影与环境光)

其他几条实用技巧:

本案例出自《URP 渲染实战》第 30 章「调试与排错」。在章节中阅读上下文 →

本章全部案例

  1. 案例 30.1 可切换输出的调试视图 Shader

全书 234 个案例:案例目录 · 完整工程:源码与下载