GameTech/URP 渲染实战
源码 / Ch07/TBNDebug.shader
Ch07/TBNDebug.shader
// 把切线空间的三根轴(T/B/N)和法线贴图扰动后的法线显示成颜色,用来检查网格切线是否正确。
Shader "Cookbook/Ch07/TBNDebug"
{
    Properties
    {
        [Enum(Tangent,0,Bitangent,1,Normal,2,MappedNormal,3)] _Mode("Show", Float) = 0
        [NoScaleOffset][Normal] _BumpMap("Normal Map", 2D) = "bump" {}
        _BumpScale("Normal Scale", Range(0, 2)) = 1
        _Tiling("Tiling", Vector) = (1, 1, 0, 0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
            float4 _Tiling;
            half _Mode;
            half _BumpScale;
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="UniversalForwardOnly" }
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
                float4 tangentOS  : TANGENT;     // xyz = 切线,w = 副切线的朝向(±1)
                float2 uv         : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv         : TEXCOORD0;
                float3 normalWS   : TEXCOORD1;
                float4 tangentWS  : TEXCOORD2;   // w 保存 sign,副切线在片元里重建
            };

            Varyings vert(Attributes input)
            {
                Varyings o;
                o.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                o.uv = input.uv * _Tiling.xy + _Tiling.zw;
                // GetVertexNormalInputs 会:法线用逆转置矩阵变换、切线按方向变换、
                // 副切线 = cross(N, T) * tangentOS.w * GetOddNegativeScale()
                VertexNormalInputs n = GetVertexNormalInputs(input.normalOS, input.tangentOS);
                o.normalWS = n.normalWS;
                o.tangentWS = float4(n.tangentWS, input.tangentOS.w * GetOddNegativeScale());
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                // 插值之后长度不再为 1,先归一化(MikkTSpace 的约定是在像素里才归一化)
                float3 N = normalize(i.normalWS);
                float3 T = normalize(i.tangentWS.xyz);
                float3 B = i.tangentWS.w * cross(N, T);
                float3 c;
                if (_Mode < 0.5)      c = T;
                else if (_Mode < 1.5) c = B;
                else if (_Mode < 2.5) c = N;
                else
                {
                    half3 nTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, i.uv), _BumpScale);
                    // 行向量 T/B/N 组成的矩阵;mul(向量, 矩阵) = nTS.x*T + nTS.y*B + nTS.z*N
                    c = normalize(TransformTangentToWorld(nTS, half3x3(T, B, N)));
                }
                return half4(c * 0.5 + 0.5, 1);   // [-1,1] 映射到 [0,1] 才能当颜色看
            }
            ENDHLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R
            HLSLPROGRAM
            #pragma vertex Ch07DepthVert
            #pragma fragment Ch07NullFrag
            #include "Assets/Cookbook/Ch07/Ch07Common.hlsl"
            ENDHLSL
        }
    }
}