卡通渲染中的描边
法线外扩
基本原理
原理是渲染两次,第一个 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