GameTech/URP 渲染实战
源码 / Ch15/MatcapStylized.shader
Ch15/MatcapStylized.shader
// Matcap(材质捕获):把“一个球在某种光照下的样子”存成贴图,用观察空间法线去查表。
// 修正了透视畸变(以视线方向为基准建立局部坐标系),并可选地叠加场景阴影。
Shader "Cookbook/Ch15/MatcapStylized"
{
    Properties
    {
        [NoScaleOffset] _MatCap ("MatCap", 2D) = "gray" {}
        _BaseColor ("Tint", Color) = (1, 1, 1, 1)
        [Toggle] _PerspectiveFix ("Perspective Correction", Float) = 1
        _ShadowStrength ("Receive Shadow Strength", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
            half4 _BaseColor;
            float _PerspectiveFix;
            half  _ShadowStrength;
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForwardOnly" }
            HLSLPROGRAM
            #pragma vertex NprVert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
            #include "Assets/Cookbook/Ch15/NPRCommon.hlsl"

            TEXTURE2D(_MatCap); SAMPLER(sampler_MatCap);

            float2 MatcapUV(float3 positionWS, float3 normalWS)
            {
                float3 normalVS = normalize(TransformWorldToViewDir(normalWS));
                // 朴素做法:直接取观察空间法线的 xy
                float2 naive = normalVS.xy;

                // 修正:以“从该点指向相机”的方向 V 为 z 轴,重新构造 x/y 轴,
                // 等于把每个像素都当成“正对相机”的那一点来查表,画面边缘不再被拉扯
                float3 V = normalize(-TransformWorldToView(positionWS));   // 观察空间里相机在原点
                float3 up = normalize(float3(0, 1, 0) - V * V.y);          // 把世界“上”投影到垂直于 V 的平面
                float3 right = cross(up, V);
                float2 corrected = float2(dot(normalVS, right), dot(normalVS, up));

                float2 n = lerp(naive, corrected, _PerspectiveFix);
                return n * 0.49 + 0.5;    // 0.49 而非 0.5:避开贴图最外圈的边缘像素
            }

            half4 frag(NprVaryings i) : SV_Target
            {
                float3 N = normalize(i.normalWS);
                half3 col = SAMPLE_TEXTURE2D(_MatCap, sampler_MatCap, MatcapUV(i.positionWS, N)).rgb * _BaseColor.rgb;
                // Matcap 不认识场景里的灯,阴影只能“乘”上去:这是风格化里常见的折中
                NprLight L = GetNprLight(i.positionWS, N);
                col *= lerp(1.0h, L.shadow, _ShadowStrength);
                return half4(col, 1);
            }
            ENDHLSL
        }

        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0
            HLSLPROGRAM
            #pragma vertex NprShadowVert
            #pragma fragment NprNullFrag
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Assets/Cookbook/Ch15/NPRCommon.hlsl"
            ENDHLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R
            HLSLPROGRAM
            #pragma vertex NprDepthVert
            #pragma fragment NprNullFrag
            #include "Assets/Cookbook/Ch15/NPRCommon.hlsl"
            ENDHLSL
        }
    }
}