那个让我差点翻车的描边需求
去年夏天,我接了个二次元风格的动作游戏外包,客户要求角色在被锁定或交互时,身上有一圈干净利落的描边,颜色还要能随状态动态变化。我心想这还不简单?UE5里用后期材质加个Sobel边缘检测不就完事了?结果一上真机,角色在复杂场景里描边糊成一团,头发丝和背景混在一起,性能还掉了15帧。客户直接甩过来一句:“这描边还没我们美术用PS画的干净。”那一刻我知道,通用边缘检测这条路走不通了。于是,我转向了模板缓冲(Stencil Buffer)——这个在UE5里藏得有点深,但一旦用对就所向披靡的利器。

为什么Sobel边缘检测救不了我?
先说说我最初踩的坑。后期材质做描边,原理是对深度和法线纹理做卷积,检测梯度突变。听起来很美好,但有两个致命伤:第一,它无法区分“我想描边的物体”和“背景里恰好深度突变的杂物”,比如角色站在草丛前,草叶的边缘也会被描出来;第二,它对半透明材质(头发、纱裙)几乎无能为力,因为半透明物体不写深度。而模板缓冲的核心思想是:我让你描,你才能描。通过Stencil值给物体打上“标记”,后期阶段只处理标记区域,精准控制,绝无误伤。
在UE5里,Stencil Buffer是深度缓冲的一个附加通道,每个像素额外8位(0-255)。默认情况下,所有不透明物体都写入Stencil=0。我们可以通过自定义深度/模板(Custom Depth/Stencil)通道,在渲染管线中给特定Actor打上Stencil值(1-255),然后在后期材质里读取这个值来做判断。这本质上是一种基于掩码的渲染策略,比全屏边缘检测高效得多,因为你可以把计算量集中到真正需要描边的像素上。
技巧:Custom Depth Stencil默认在项目设置里是关闭的,记得在“渲染”分类下勾选“自定义深度-模板通道”。另外,Stencil值在后期材质中读取时,需要开启“Custom Depth Stencil”选项,否则读出来全是0。
技术选型:Stencil描边的三种实现路径
确定用Stencil后,具体怎么描?我试了三种方案,各有优劣,最终选了第三种。先看对比表格:
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 后期材质边缘检测 | 在后期阶段读取Stencil纹理,对Stencil值做卷积,检测边界 | 实现简单,不增加Draw Call | 边缘宽度受分辨率影响,细线容易断裂 | 快速原型,低精度需求 |
| 网格膨胀法 | 复制角色网格,沿法线外扩,用Stencil标记后渲染纯色 | 描边粗细均匀,不受分辨率影响 | 需要额外Draw Call,外扩网格可能穿插 | 高质量角色描边 |
| Stencil+后期混合法 | 先通过Stencil标记物体,在后期用基于深度的边缘检测,但仅对标记区域生效 | 兼顾性能与质量,可动态调整 | 需要处理半透明和深度冲突 | 复杂场景中的动态描边 |
我最终选了第三种,因为项目里角色有大量半透明部件(头发、披风),网格膨胀法会产生明显的穿插破绽。而纯后期边缘检测又不够精细。混合法的思路是:用Stencil标记角色,然后在后期材质里,只对Stencil>0的区域计算深度和法线边缘,同时结合Stencil的边界信息来强化轮廓。这样既避免了全屏计算,又保留了边缘检测的灵活性。
实战踩坑:Stencil值冲突与半透明难题
方案定了,开干。第一步,给角色蓝图添加Custom Depth Stencil组件,设置Stencil值为1。但紧接着问题来了:场景里其他物体(比如可交互的箱子)我也想要描边,设了Stencil=2。结果后期材质里,我写的判断是“Stencil>0就描边”,导致所有标记物体都用了同一种描边颜色,没法区分。更坑的是,有些物体的Stencil值在后期材质里读出来居然是0!
排查后发现两个原因:第一,Custom Depth Stencil的渲染优先级问题——某些物体(比如贴花、粒子)默认不写入Stencil,需要手动在材质里勾选“允许自定义深度”。第二,后期材质里读取Stencil时,我用了“SceneTexture:CustomStencil”节点,但它的输出是0-255的整数,而材质里默认是浮点运算,需要先除以255归一化,否则判断会出错。

半透明的问题更棘手。UE5里半透明物体默认不写入Custom Depth,所以头发和披风根本不会被标记。解决方案是:在角色材质里,将混合模式改为“半透明”的同时,勾选“允许自定义深度”,并确保“自定义深度-模板”通道开启。但这样又带来新问题:半透明物体写入深度后,会和后面不透明物体的Stencil值产生重叠,导致描边断裂。最后的解决办法是,在后期材质里对Stencil做一次膨胀操作(Dilate),把边界向外扩几个像素,弥补半透明造成的缝隙。
注意:Stencil膨胀会略微增加性能开销,建议在材质里用“质量开关”控制膨胀半径,移动端可以关掉。
性能优化:从掉帧15到稳60帧
最初版本在GTX 1660上跑,描边一开,帧数从75掉到60。分析后发现,后期材质里用了多次SceneTexture采样(深度、法线、Stencil),每个采样都是全屏的。优化策略是:第一,利用Stencil值做早期退出——在材质里先采样Stencil,如果当前像素Stencil=0且周围8个像素Stencil也都为0,直接返回原图,跳过后续计算。这样在角色占屏幕比例较小时,能省下大量像素着色。第二,把边缘检测的卷积核从3×3降到2×2(对角线采样),虽然精度略降,但肉眼几乎看不出区别,性能提升20%。第三,关闭不必要的Stencil通道写入,比如远景物体不参与描边,就不写Stencil。
经过这三步,帧数回到68,基本满足要求。后来我又加了一个LOD策略:当角色距离相机超过一定距离时,自动降低描边精度或关闭描边,进一步稳定性能。
交付成果与行业启示
最终交付时,客户对描边效果非常满意——角色在场景中轮廓清晰,半透明头发也有细腻的描边,而且切换颜色时毫无延迟。这个项目让我深刻体会到,模板缓冲不是万能药,但它是控制渲染精度的手术刀。在UE5里,很多看似复杂的效果,其实缺的就是一个精准的掩码。

后来我把这套方案抽象成了可复用的后期材质函数,支持多Stencil分组、动态颜色、距离衰减,甚至能用在UI元素上。如果你也在做风格化渲染,不妨试试Stencil描边,它比你想的更强大。
关于火星人教育
这篇文章的技术细节,来自我在火星人教育带学员做项目时的真实复盘。火星人教育创办24年来,一直专注数字艺术与AIGC实战职业技能培训,课程覆盖AI漫剧创作、AIGC视觉设计、UE5游戏特效、室内全屋定制、MAYA三维动画、C4D动态设计、工业设计、NUKE影视后期、摄影摄像实训等方向。我们在北京、深圳双校区开设线下面授班,面向美术生、在校大学生及意向转行的职场人群,以项目化实训教学,夯实专业技能,助力就业与职业提升。如果你也想亲手实现这样的描边效果,欢迎咨询:010-65918670。








评论(0)