GameTech/URP 渲染实战
源码 / Ch14/ToonRimSpec.shader
Ch14/ToonRimSpec.shader
// 边缘光(三种做法)与风格化高光(三种做法)。漫反射部分沿用上一案例的色阶。
Shader "Cookbook/Ch14/ToonRimSpec"
{
    Properties
    {
        [MainColor] _BaseColor ("Base Color", Color) = (0.35, 0.55, 0.95, 1)
        _ShadeColor ("Shade Color", Color) = (0.22, 0.25, 0.55, 1)
        _Threshold ("Threshold", Range(0, 1)) = 0.5
        _Softness ("Edge Softness", Range(0, 0.5)) = 0.02
        [Header(Rim)]
        [KeywordEnum(Off, Fresnel, LightMasked, Depth)] _Rim ("Rim Mode", Float) = 2
        [HDR] _RimColor ("Rim Color", Color) = (1, 0.95, 0.85, 1)
        _RimWidth ("Fresnel Rim Width", Range(0, 1)) = 0.3
        _RimWidthPx ("Depth Rim Width (px)", Range(0, 20)) = 5
        _RimDepthThreshold ("Depth Rim Threshold (m)", Range(0.01, 2)) = 0.2
        [Header(Specular)]
        [KeywordEnum(Off, Blinn, Toon, Stylized)] _Spec ("Specular Mode", Float) = 2
        [HDR] _SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
        _SpecThreshold ("Specular Threshold", Range(0.5, 1)) = 0.97
        _SpecShiftScale ("Shift T/B, Scale T/B", Vector) = (0, 0, 0, 0)
        _SpecSplit ("Split T/B", Vector) = (0, 0, 0, 0)
        _SpecSquare ("Squareness", Range(0, 1)) = 0
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
        CBUFFER_START(UnityPerMaterial)
            half4 _BaseColor, _ShadeColor, _RimColor, _SpecColor;
            half  _Threshold, _Softness, _RimWidth, _RimWidthPx, _RimDepthThreshold;
            half  _SpecThreshold, _SpecSquare;
            half4 _SpecShiftScale;
            half4 _SpecSplit;
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "ToonForward"
            Tags { "LightMode"="UniversalForwardOnly" }

            HLSLPROGRAM
            #pragma target 3.5
            #pragma vertex ToonVert
            #pragma fragment frag
            #pragma shader_feature_local_fragment _RIM_OFF _RIM_FRESNEL _RIM_LIGHTMASKED _RIM_DEPTH
            #pragma shader_feature_local_fragment _SPEC_OFF _SPEC_BLINN _SPEC_TOON _SPEC_STYLIZED
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            #include "Assets/Cookbook/Ch14/ToonCommon.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            // 屏幕空间深度边缘光:沿法线在屏幕上的投影方向偏移若干像素读深度,
            // 如果那里的深度比自己“深很多”,说明自己处在轮廓边上
            half DepthRim(ToonVaryings i, half3 N)
            {
                float3 normalCS = TransformWorldToHClipDir(N);
                float2 dirPx = normalize(normalCS.xy * _ScreenParams.xy + 1e-6);
            #if UNITY_UV_STARTS_AT_TOP
                dirPx.y = -dirPx.y;   // 这些平台的像素坐标 y 朝下,与裁剪空间 y 相反
            #endif
                int2 p = int2(i.positionCS.xy + dirPx * _RimWidthPx);
                p = clamp(p, int2(0, 0), int2(_ScreenParams.xy) - 1);
                float sceneDepth = LinearEyeDepth(LoadSceneDepth(p), _ZBufferParams);
                float selfDepth = LinearEyeDepth(i.positionCS.z, _ZBufferParams);
                return step(_RimDepthThreshold, sceneDepth - selfDepth);
            }

            half4 frag(ToonVaryings i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                half3 N = normalize(i.normalWS);
                half3 T = normalize(i.tangentWS.xyz);
                half3 B = cross(N, T) * i.tangentWS.w;
                half3 V = GetWorldSpaceNormalizeViewDir(i.positionWS);
                Light light = GetMainLight();
                half3 L = light.direction;
                half NoL = dot(N, L);
                half band = ToonBand(NoL * 0.5 + 0.5, _Threshold, _Softness);
                half3 color = lerp(_ShadeColor.rgb, _BaseColor.rgb, band) * light.color;
                color += SampleSH(half3(0, 0, 0)) * _BaseColor.rgb * 0.3;

                // ---------- 高光 ----------
                half3 H = normalize(L + V);
                half spec = 0;
            #if defined(_SPEC_BLINN)
                spec = pow(saturate(dot(N, H)), 64.0);                                // 对照:普通 Blinn-Phong
            #elif defined(_SPEC_TOON)
                spec = ToonBand(saturate(dot(N, H)), _SpecThreshold, 0.005);          // 色阶化:一个硬边色块
            #elif defined(_SPEC_STYLIZED)
                spec = StylizedSpecular(N, T, B, H, _SpecShiftScale, _SpecSplit.xy, _SpecSquare, _SpecThreshold, 0.005);
            #endif
                color += spec * band * _SpecColor.rgb * light.color;                  // 只在亮部出现

                // ---------- 边缘光 ----------
                half rim = 0;
            #if defined(_RIM_FRESNEL)
                rim = FresnelRim(N, V, _RimWidth, 0.02);
            #elif defined(_RIM_LIGHTMASKED)
                // 只保留朝光一侧的边缘:边缘光“像是被主光勾出来的”,背光侧不会到处发亮
                rim = FresnelRim(N, V, _RimWidth, 0.02) * ToonBand(NoL, 0.0, 0.1);
            #elif defined(_RIM_DEPTH)
                rim = DepthRim(i, N) * ToonBand(NoL, -0.2, 0.1);
            #endif
                color = lerp(color, _RimColor.rgb * light.color, rim);

                color = MixFog(color, i.fogFactor);
                return half4(color, 1);
            }
            ENDHLSL
        }

        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0 Cull Back
            HLSLPROGRAM
            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment
            #pragma multi_compile_instancing
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            // URP 自带的阴影 Pass 只在 _ALPHATEST_ON 时才读 _BaseMap,其余部分与材质无关,直接复用
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
        }
        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R Cull Back
            HLSLPROGRAM
            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
        Pass
        {
            Name "DepthNormals"
            Tags { "LightMode"="DepthNormals" }
            ZWrite On Cull Back
            HLSLPROGRAM
            #pragma vertex DepthNormalsVertex
            #pragma fragment DepthNormalsFragment
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthNormalsPass.hlsl"
            ENDHLSL
        }
    }
}