描边四种做法:倒模 / 后处理 / 边缘检测 / 贴图
卡通描边、选中轮廓、赛璐璐风格——「给物体画一圈线」至少有四种正路,各有明确的适用边界。这篇按「目标 → 原理 → 关键代码 → 坑」过一遍,最后给选型表。
目标
沿物体轮廓画一圈可控宽度的线,摄像机移动时线钉在模型上(区别于菲涅尔 rim,见 /notes/unity/effects-2/)。
做法一:倒模(inverted hull)——最常用
原理:两个 Pass。第一个 Pass 把顶点沿法线外扩、Cull Front 只画背面——你看到的「线」其实是放大了一圈的模型内壁;第二个 Pass 正常画本体盖住中间。
Pass // 描边 Pass
{
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _OutlineWidth;
fixed4 _OutlineColor;
struct Attributes { float4 vertex : POSITION; float3 normal : NORMAL; };
struct V2F { float4 pos : SV_POSITION; };
V2F vert(Attributes v) {
V2F o;
// 观察空间外扩:宽度按「屏幕像素」恒定,不随距离变细
float3 posVS = mul(UNITY_MATRIX_MV, v.vertex).xyz;
float3 nVS = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
posVS += nVS * _OutlineWidth;
o.pos = mul(UNITY_MATRIX_P, float4(posVS, 1));
return o;
}
fixed4 frag(V2F i) : SV_Target { return _OutlineColor; }
ENDCG
}
// 其后接正常本体 Pass
坑:硬边模型(立方体)顶点法线平均后外扩方向错,角上开裂——解法是资产侧拆分顶点法线(每面独立法线)或按平滑组处理;蒙皮模型描边要带蒙皮(用 UNITY_SKINNING 或 URP 的 Skin 节点);宽度按世界空间时远处线会细于一个像素产生闪烁,观察空间写法解决。
做法二:后处理描边(全屏)
原理:把目标物体渲进一张 mask(Replace 材质或 Stencil 标记),全屏后处理对 mask 做邻域检测(采样偏移找边界)画线。
坑/边界:一次全屏 Pass 的固定带宽成本(移动端敏感,见 /notes/unity/performance-1/);描的是「屏幕合成后的轮廓」,两个相邻同类物体只出一圈外轮廓——要每个物体独立描边就得逐物体 mask,成本回升。
做法三:边缘检测(深度/法线)
原理:不针对特定物体——对 _CameraDepthTexture 和 _CameraNormalsTexture 做 Sobel 类梯度检测,深度/法线突变处画线。整个场景所有几何轮廓自动有线(卡通渲染管线常用)。
// 全屏 Pass 核心
float d = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler, uv).r);
float dN = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler, uv + _Texel).r);
float edge = saturate((dN - d) / (d * _Threshold)); // 相对深度差
col = lerp(col, _LineColor, edge);
坑:共面物体无线(深度无突变);同深度相邻物体只有法线线;宽度靠 texel 偏移,分辨率相关;需要引擎开启 depth texture(URP Renderer 勾选,见 /notes/unity/engineering-1/)。
做法四:贴图/美术描边
原理:线画进贴图(UV 边界留出血、画黑边),或模型侧做「轮廓壳」资产。零 shader 技巧,风格最可控(粗细变化、断点都是手绘的)。
坑:只对有 UV 且规划了边界的资产有效;轮廓随视角不变,转角度可能「线不在轮廓上」。
选型表
| 做法 | 粒度 | 移动端 | 硬边模型 | 典型场景 |
|---|---|---|---|---|
| 倒模 | 每物体 | 可用(×2 几何) | 需处理法线 | 卡通角色主力 |
| 后处理 mask | 全屏/分组 | 固定全屏成本 | 无关 | 选中高亮、分组描边 |
| 深度/法线检测 | 全场景 | 一次全屏 + 两张纹理 | 无关 | 全场景卡通风 |
| 贴图 | 每资产 | 最便宜 | 无关 | 风格化硬需求、道具 |
多 Pass 的 Draw Call 代价按遍数算,见 /notes/unity/performance-1/;Cull Front 的状态语义见 /notes/unity/structure-3/。
自检三问
- 倒模描边在立方体角上开裂,根因是什么?
- 深度边缘检测为什么对「贴地贴花」无线?
- 选中一个敌人描边,哪种做法成本最可控?为什么不是全屏后处理?