GameTech/URP 渲染实战
源码 / shaders/Ch14/ToonLit.glsl
shaders/Ch14/ToonLit.glsl
// 本章的完整卡通 Shader:色阶(两层阴影色 / 可选 Ramp 贴图)+ 卡通化接收阴影 + 风格化高光 + 边缘光
// + 附加光 + 平坦环境光 + 背面外扩描边(裁剪空间等宽,可选平滑法线来源)。(GLSL 版,对应 Ch14/ToonLit.shader)
Shader "Cookbook/Ch14/ToonLit"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        GLSLINCLUDE
        uniform vec4 _BaseMap_ST;
        uniform vec4 _BaseColor, _ShadeColor, _ShadeColor2, _SpecColor, _RimColor, _OutlineColor;
        uniform float _Threshold, _Shade2Threshold, _Shade2Strength, _Softness;
        uniform float _ReceiveShadow, _ShadowSoftness, _ShadowNormalOffset;
        uniform float _SpecThreshold, _SpecStrength, _RimWidth, _RimStrength;
        uniform float _AmbientStrength, _AddLightStrength, _OutlineWidth;
        uniform sampler2D _BaseMap;
        uniform sampler2D _RampTex;
        ENDGLSL

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

            GLSLPROGRAM
            #pragma shader_feature_local_fragment _ _RAMPMAP
            #define TOON_VARYINGS
            #include "Ch14/ToonCommon.glsl"

            #ifdef VERTEX
            void main() { ToonVert(); }
            #endif

            #ifdef FRAGMENT
            // 主光的色阶:输入是“半 Lambert × 卡通化后的阴影”,输出是最终的漫反射颜色
            vec3 ToonDiffuse(vec3 albedo, float halfLambert, float shadow)
            {
                // 接收阴影卡通化:把 PCF 的软阴影值也切成硬边,再“乘进”明暗,而不是在最后乘一次
                float toonShadow = mix(1.0, ToonBand(shadow, 0.5, _ShadowSoftness), _ReceiveShadow);
                // 投影区域只压到“第一层阴影”的位置,而不是压到 0(那会掉进第二层阴影,比背光面还暗)
                float shadowedX = min(halfLambert, (_Threshold + _Shade2Threshold) * 0.5);
                float x = mix(shadowedX, halfLambert, toonShadow);
            #if defined(_RAMPMAP)
                return albedo * texture(_RampTex, vec2(clamp(x, 0.01, 0.99), 0.5)).rgb;
            #else
                float band1 = ToonBand(x, _Threshold, _Softness);
                float band2 = ToonBand(x, _Shade2Threshold, _Softness);
                vec3 shade = mix(albedo * _ShadeColor2.rgb, albedo * _ShadeColor.rgb, mix(1.0, band2, _Shade2Strength));
                return mix(shade, albedo, band1);
            #endif
            }

            // 附加光:同样做一次硬边,得到一圈有清晰边界的彩色光斑
            vec3 ToonAdditional(vec3 albedo, vec3 N, Light light)
            {
                float atten = light.distanceAttenuation * light.shadowAttenuation;
                float x = saturate(dot(N, light.direction)) * saturate(atten * 4.0);
                return albedo * light.color * ToonBand(x, 0.3, _Softness + 0.02) * _AddLightStrength;
            }

            vec4 frag()
            {
                vec3 N = normalize(normalWS);
                vec3 V = GetWorldSpaceNormalizeViewDir(positionWS);
                vec3 albedo = texture(_BaseMap, TransformTex(uv, _BaseMap_ST)).rgb * _BaseColor.rgb;

                // ---------- 主光 + 阴影 ----------
                // 接收端法线偏移:沿法线挪一点再查阴影图,消除明暗交界处的自阴影锯齿(shadow acne)
                vec4 shadowCoord = TransformWorldToShadowCoord(positionWS + N * _ShadowNormalOffset);
                Light mainLight = GetMainLight(shadowCoord, positionWS, vec4(1.0));
                float NoL = dot(N, mainLight.direction);
                vec3 color = ToonDiffuse(albedo, NoL * 0.5 + 0.5, mainLight.shadowAttenuation) * mainLight.color;

                // 高光与边缘光只出现在“真正被照亮”的区域
                float litMask = ToonBand((NoL * 0.5 + 0.5) * mix(1.0, ToonBand(mainLight.shadowAttenuation, 0.5, _ShadowSoftness), _ReceiveShadow), _Threshold, _Softness);
                vec3 H = normalize(mainLight.direction + V);
                float spec = ToonBand(saturate(dot(N, H)), _SpecThreshold, 0.005) * litMask;
                color += spec * _SpecStrength * _SpecColor.rgb * mainLight.color;
                float rim = FresnelRim(N, V, _RimWidth, 0.02) * litMask;
                color = mix(color, _RimColor.rgb * albedo * mainLight.color * 1.6, rim * _RimStrength);

                // ---------- 附加光 ----------
            #if defined(_ADDITIONAL_LIGHTS)
                // Forward / Forward+ 的光源循环在网页版里都是普通 for 循环
                int lightCount = GetAdditionalLightsCount();
                for (int lightIndex = 0; lightIndex < lightCount; lightIndex++)
                    color += ToonAdditional(albedo, N, GetAdditionalLight(lightIndex, positionWS, vec4(1.0)));
            #endif

                // ---------- 环境光:只取球谐常数项,整块抬亮暗部 ----------
                color += SampleSH(vec3(0.0)) * albedo * _AmbientStrength;

                return vec4(MixFog(color, fogFactor), 1.0);
            }
            void main() { fragColor = frag(); }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "Outline"
            Tags { "LightMode"="SRPDefaultUnlit" }
            Cull Front
            ZWrite On
            GLSLPROGRAM
            #pragma shader_feature_local_vertex _OUTLINENORMAL_MESH _OUTLINENORMAL_TANGENT _OUTLINENORMAL_VERTEXCOLOR
            #include "Ch14/ToonOutlinePass.glsl"
            ENDGLSL
        }

        // URP 自带的阴影/深度 Pass 与材质无关,网页版用公共的同名 Pass
        UsePass "Cookbook/Common/SHADOWCASTER"
        UsePass "Cookbook/Common/DEPTHONLY"
        UsePass "Cookbook/Common/DEPTHNORMALS"
    }
}