GameTech/URP 渲染实战
源码 / shaders/Ch14/ToonOutline.glsl
shaders/Ch14/ToonOutline.glsl
// 背面外扩描边(Back-face Outline / Inverted Hull)。(GLSL 版,对应 Ch14/ToonOutline.shader)
// 第二个 Pass 只画背面,并把顶点沿法线推出去,露出来的一圈就是轮廓线。
Shader "Cookbook/Ch14/ToonOutline"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        GLSLINCLUDE
        uniform vec4 _BaseColor, _ShadeColor, _OutlineColor;
        uniform float _OutlineWidth, _OutlineWidthOS;
        ENDGLSL

        Pass
        {
            Name "ToonForward"
            Tags { "LightMode"="UniversalForwardOnly" }
            GLSLPROGRAM
            #define TOON_VARYINGS
            #include "Ch14/ToonCommon.glsl"
            #ifdef VERTEX
            void main() { ToonVert(); }
            #endif
            #ifdef FRAGMENT
            vec4 frag()
            {
                vec3 N = normalize(normalWS);
                Light light = GetMainLight();
                float band = ToonBand(dot(N, light.direction) * 0.5 + 0.5, 0.5, 0.02);
                vec3 color = mix(_ShadeColor.rgb, _BaseColor.rgb, band) * light.color;
                color += SampleSH(vec3(0.0)) * _BaseColor.rgb * 0.3;
                return vec4(MixFog(color, fogFactor), 1.0);
            }
            void main() { fragColor = frag(); }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "Outline"
            // URP 的不透明队列会画 UniversalForward 和 SRPDefaultUnlit 两种 LightMode,
            // 所以第二个 Pass 用 SRPDefaultUnlit 就能自动被画到,不需要 Renderer Feature
            Tags { "LightMode"="SRPDefaultUnlit" }
            Cull Front      // 只画背面
            ZWrite On

            GLSLPROGRAM
            #pragma shader_feature_local_vertex _OUTLINESPACE_OBJECT _OUTLINESPACE_CLIP
            #pragma shader_feature_local_vertex _OUTLINENORMAL_MESH _OUTLINENORMAL_TANGENT _OUTLINENORMAL_VERTEXCOLOR
            #include "Ch14/ToonCommon.glsl"

            VARYING float fogFactor;

            #ifdef VERTEX
            // 取“用来外扩的法线”:原始法线 / 烘焙在切线里的平滑法线 / 烘焙在顶点色里的切线空间平滑法线
            vec3 GetOutlineNormalOS()
            {
            #if defined(_OUTLINENORMAL_TANGENT)
                return tangentOS.xyz;                               // 物体空间平滑法线
            #elif defined(_OUTLINENORMAL_VERTEXCOLOR)
                vec3 nTS = color.rgb * 2.0 - 1.0;                    // 切线空间平滑法线
                vec3 bitangentOS = cross(normalOS, tangentOS.xyz) * tangentOS.w;
                return normalize(nTS.x * tangentOS.xyz + nTS.y * bitangentOS + nTS.z * normalOS);
            #else
                return normalOS;
            #endif
            }

            void main()
            {
                vec3 n = GetOutlineNormalOS();
            #if defined(_OUTLINESPACE_OBJECT)
                gl_Position = OutlinePositionCS_ObjectSpace(positionOS.xyz, n, _OutlineWidthOS);
            #else
                gl_Position = OutlinePositionCS(positionOS.xyz, n, _OutlineWidth);
            #endif
                fogFactor = ComputeFogFactor(gl_Position.z);
            }
            #endif

            #ifdef FRAGMENT
            void main() { fragColor = vec4(MixFog(_OutlineColor.rgb, fogFactor), 1.0); }
            #endif
            ENDGLSL
        }

        UsePass "Cookbook/Common/SHADOWCASTER"
        UsePass "Cookbook/Common/DEPTHONLY"
        UsePass "Cookbook/Common/DEPTHNORMALS"
    }
}