GameTech/URP 渲染实战
源码 / Ch01/NormalViz.shader
Ch01/NormalViz.shader
// 第 1 章 案例 1.2:法线可视化,以及“非均匀缩放下法线该怎么变换”
// _Mode:0 物体空间法线 / 1 世界空间法线 / 2 观察空间法线
//        3 用正确的世界法线做 Lambert / 4 用错误的方式(直接乘模型矩阵)变换法线做 Lambert
//        5 两种法线算出的光照之差(放大 4 倍)
Shader "Cookbook/Ch01/NormalViz"
{
    Properties
    {
        [Enum(ObjectNormal,0,WorldNormal,1,ViewNormal,2,LitCorrect,3,LitWrong,4,Difference,5)] _Mode ("模式", Float) = 1
        _BaseColor ("Lambert 底色", Color) = (0.85, 0.85, 0.85, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        Pass
        {
            Name "NormalViz"
            Tags { "LightMode"="UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float _Mode;
                half4 _BaseColor;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
            };

            struct Varyings
            {
                float4 positionCS   : SV_POSITION;
                float3 normalOS     : TEXCOORD0;
                float3 normalWS     : TEXCOORD1;
                float3 normalWSWrong: TEXCOORD2;
                float3 normalVS     : TEXCOORD3;
            };

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.normalOS   = input.normalOS;

                // 正确:乘“模型矩阵的逆转置”。URP 的实现是 mul(normalOS, (float3x3)WorldToObject),
                // 行向量左乘逆矩阵,等价于列向量左乘逆矩阵的转置(SpaceTransforms.hlsl)
                output.normalWS = TransformObjectToWorldNormal(input.normalOS);

                // 错误:把法线当成普通方向,直接乘模型矩阵。均匀缩放时没问题,非均匀缩放时法线不再垂直于表面
                output.normalWSWrong = TransformObjectToWorldDir(input.normalOS);

                // 观察空间法线:方向只受旋转影响,用 3x3 的观察矩阵即可(相机矩阵不含缩放)
                output.normalVS = TransformWorldToViewDir(output.normalWS);
                return output;
            }

            half3 Lambert(float3 n)
            {
                Light light = GetMainLight();
                half ndl = saturate(dot(n, light.direction));
                return _BaseColor.rgb * (light.color * ndl + 0.08);
            }

            half4 frag(Varyings input) : SV_Target
            {
                // 插值后的法线长度会小于 1,必须重新归一化
                float3 nOS = normalize(input.normalOS);
                float3 nWS = normalize(input.normalWS);
                float3 nWrong = normalize(input.normalWSWrong);
                float3 nVS = normalize(input.normalVS);

                int mode = (int)_Mode;
                half3 col;
                if (mode == 0)      col = nOS * 0.5 + 0.5;   // [-1,1] 映射到 [0,1] 才能当颜色看
                else if (mode == 1) col = nWS * 0.5 + 0.5;
                else if (mode == 2) col = nVS * 0.5 + 0.5;
                else if (mode == 3) col = Lambert(nWS);
                else if (mode == 4) col = Lambert(nWrong);
                else                col = abs(Lambert(nWS) - Lambert(nWrong)) * 4.0;
                return half4(col, 1);
            }
            ENDHLSL
        }
    }
}