卡通渲染中的描边

法线外扩

基本原理

原理是渲染两次,第一个 Pass 正常渲染正面,将背面剔除,第二个 Pass 将正面剔除后先将背面顶点沿着法线的方向移动,外扩后再渲染背面,

最后两个 Pass 叠加在一起。

Unity 实现

渲染正面

Shader "nekoside/default"
{
    Properties
    {
        _BaseColor("BaseColor", Color) = (0, 0, 0, 1)
    }

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

        // 渲染正面
        Pass
        {
            Cull Back
            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #pragma vertex vert
            #pragma fragment frag

            CBUFFER_START(UnityPerMaterial)
                half4 _BaseColor;
            CBUFFER_END

            struct Attributes
            {
                float3 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 OUT;
                OUT = half4(_BaseColor.rgba);
                return OUT;
            }
            ENDHLSL
        }
    }
}

渲染背面(描边)

Shader "nekoside/outline"
{
    Properties
    {
        _OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
        _OutlineWidth ("Outline Width", Range(0, 0.5)) = 0.25
    }

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

        // 渲染背面(描边)
        Pass
        {
            Cull Front
            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #pragma vertex vert
            #pragma fragment frag

            CBUFFER_START(UnityPerMaterial)
                half4 _OutlineColor;
                float _OutlineWidth;
            CBUFFER_END

            struct Attributes
            {
                float3 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                IN.positionOS += _OutlineWidth * IN.normalOS;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                return _OutlineColor;
            }
            ENDHLSL
        }
    }
}

因为 Unity 默认不会渲染所有 Pass(因为会对性能造成影响),所以我这里把两个 Pass 拆分为两个 Shader,一共使用两个 Material.

描边断开

直接使用刚刚的 Shader 会变成这样

因为法线外扩得到的描边,会导致模型的硬边(比如这个立方体)连接处断开

解决方法是生成平滑法线

修改 Smoothing Angle

效果如下


卡通渲染中的描边
https://nekoside.com/archives/qia-tong-xuan-ran-zhong-de-miao-bian
作者
nekoside
发布于
2026年02月06日
更新于
2026年02月06日
许可协议