209 字
1 分钟
卡通渲染中的描边
2026-02-06

法线外扩#

基本原理#

原理是渲染两次,第一个 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/posts/卡通渲染中的描边/
作者
nekoside
发布于
2026-02-06
许可协议
CC BY-NC-SA 4.0

目录