UE5 DBuffer Decal自定义法线插值全流程:从管线原理到节点配置
2026/9/16 23:18:36 网站建设 项目流程

上个月做场景细节时,我在墙面叠一张带裂纹的贴花,要求裂纹区域的法线跟着贴图走,但裂纹边缘要和墙面原有法线自然过渡。调了半天,默认Decal的Normal输出根本不给我这种自由度——要不整块替换,要不不改,中间的控制权一点都没有。后来切到DBuffer方案,自己写了套法线插值逻辑,才算把这个问题彻底解决。这篇就把DBuffer Decal做自定义Normal插值的完整流程、节点配置和踩坑记录整理出来,给同样被法线混合折磨过的朋友一条可复现的路。

先说明适用人群:正在用UE5做场景贴花、地形补丁、墙面破损细节,对默认Decal的法线结果不满意,或者想实现“边缘淡入淡出、法线跟着蒙版走”这类需求的TA和场景美术,这篇的内容会比较对口。程序向的读者也能从渲染管线的角度理解DBuffer到底帮我们省了什么。

1. 一个项目里“不够用”的Normal混合,让我翻了车

1.1 默认Decal处理Normal的老路子

UE5里的Deferred Decal,说白了就是把一个贴花投射体当成一个光源那样去绘制:渲染时遍历所有Decal,把片元从投射体空间转换到贴花UV上,采样贴图,然后直接把采样到的颜色、粗糙度、法线写进GBuffer的对应通道。

这个流程本身很成熟,做普通贴花——比如地上的标志、墙上的污渍——完全够用。但问题出在法线上。UE的Decal材质里,Normal引脚输出的东西会被直接写进GBuffer的Normal通道。对一个普通的不透明材质来说,GBuffer里的法线代表的是“这个像素最终应该以什么朝向被光照”,Decal一旦写入,就相当于把这个像素的表面朝向整个换掉了。

如果你只是给墙面贴一张完全不改变朝向的平面贴花,那没什么感觉。可一旦贴花法线跟墙体法线差异大,比如墙面是平的,裂纹贴图的法线却有明显的凹凸起伏,光源一照,裂纹区域的光影就和周围环境“脱节”了。边缘处尤其明显,因为没有过渡,没有衰减,新法线和旧法线之间就是一刀切的突变。

默认的Blend Mode虽然提供了一些透明度混合选项,但那些主要是控制Decal整体显隐的,并非针对法线方向的插值权重。也就是说,引擎没给你“在这个区域内,把物体原始法线和贴花法线按某个规则混合”的接口,你只能接受“全新法线”或“原来法线”这两种极端状态。

1.2 我想要的“自定义Normal插值”到底是什么

我这次要做的效果,是在竞技场地面上加一块带裂纹的破损贴花,裂纹部分要有立体感,但边缘要渐变消失,最终和地面原法线完全融合。需求的本质拆出来其实很简单:

  • 裂纹区域:使用贴花自带法线的细节。
  • 裂纹边缘:从贴花法线平滑过渡到地面原始法线。
  • 过渡带形状:由一张蒙版或参数控制,而不是固定值。

要做到第二点,我得在Decal材质里同时拿到“地面原始法线”和“贴花法线”,然后做一个带权重的插值。但前面说了,默认Decal的Normal引脚只是单向输出,它并不告诉你这个像素原本的法线是什么。想拿到“原始表面法线”,就要在Decal真正写入GBuffer之前,把信息截留下来。DBuffer就是干这个的。

2. DBuffer到底动了什么“蛋糕”

2.1 DBuffer在渲染管线里的位置,以及它解决了什么

DBuffer(Decal Buffer)是渲染管线里一组额外的Render Target,在BasePass之前生成。它的工作方式和传统Decal最大的区别在于:传统Decal是边画边往GBuffer里写,多个Decal谁后画谁覆盖谁;DBuffer则是先让所有Decal把它们的效果写进一组独立的缓冲区里,等到BasePass渲染不透明物体时,再把DBuffer里累积的贴花数据拿出来,跟物体原本的材质属性合并。

你可以把DBuffer理解成一个“待结算账单”。餐厅里每来一桌客人,账房先生不直接跑来改总账本,而是先把每一笔消费记在便签纸上,到晚上一起入账。这样既不会出现两个服务员同时改总账改乱了,也不会为了改一小笔钱就把整个账本掀了重写。

对应到渲染上,这个设计解决了两个核心问题。第一,overdraw,也就是同一像素被多个Decal反复覆盖的消耗降低了,Decal不再需要多次写GBuffer,而是在DBuffer里累积后一次合成。第二,可编程性好,因为DBuffer数据在BasePass阶段可以被材质读取,我们有了在Shader里自己决定怎么合并法线、颜色、粗糙度的机会。

在UE5的材质编辑器里,这个“读取”的入口就是SceneTexture节点,把Scene Texture Id切到DecalNormal,就能拿到当前像素在DBuffer里累积的法线数据。这一步是整个自定义Normal插值的基石。

2.2 DBuffer的三种模式与数据取舍

在项目设置里开启DBuffer时,有几种模式可选,常见的是NoneStandardAll。区别在于DBuffer里到底存了哪些通道的数据。Standard模式下,DBuffer主要存颜色(BaseColor)和法线;All模式则会额外把Metallic、Specular、Roughness也放进去。

我这次因为要做法线插值,并且希望贴花同时影响粗糙度,所以直接把DBuffer开到了All。代价是带宽消耗更大,移动端谨慎使用。如果只是用法线插值,不开AllStandard也可以,但要说清楚:如果你需要同时拿到DecalNormal和改变Roughness,就得上All

从性能角度看,DBuffer不是免费的午餐。它多了一组RT,等于渲染管线的带宽占用多了一份。PC上问题不大,主机平台要看分辨率,移动端尽量保持Standard或更低,否则掉帧会很明显。

3. 完整配置流程:从项目设置到材质节点

3.1 第一步:项目设置里打开DBuffer

这一步必须在项目层面开启,材质里怎么调都替代不了。路径:

Project Settings -> Rendering -> Decals -> DBuffer Decals

DBuffer DecalsNone改成All或者Standard,我这里选了All。设置完之后,再创建或修改Decal材质时,Blend Mode里才会出现DBuffer相关的选项。如果你发现材质的Decal混合模式里没有DBuffer开头的项,先去检查这里是不是没开。

开启后会有一个比较直观的验证方式:放置一个Decal Actor,把它的材质临时换成带DBuffer混合模式的材质,然后用ProfileGPU抓一帧,看看渲染流程里是否多了DBuffer相关的Pass。能看到就说明管线已经激活了。

3.2 第二步:Decal材质的基础开关

材质部分,先创建新材质或打开现有的Decal材质,做这几件事:

  • Material Domain设为Deferred Decal
  • Blend Mode选择以DBuffer开头的模式。比如DBuffer Decal (Translucent)DBuffer Decal (Stain)DBuffer Decal (Color Normal Roughness)等。
  • 材质里至少要输出一个Normal引脚,否则DBuffer不会写入法线数据,后面SceneTextureDecalNormal读出的是空值。

Blend Mode的具体选择,要看你的贴花需求。DBuffer Decal (Stain)偏向污渍类,不怎么改变表面形状;DBuffer Decal (Color Normal Roughness)就是全都要,适合我做裂纹效果这种需要法线和粗糙度协同变化的场景。

这里有个细节容易踩坑:选了DBuffer混合模式之后,材质细节面板里的Decal Blend相关选项可能就不再像普通模式那样好用了。参数节点里有的值会显示为Default,意思是“这个属性由DBuffer模式接管”。你在材质里配置的RoughnessMetallic引脚值能不能生效,取决于混合模式的组合方式。所以如果发现某些属性怎么调都没反应,先检查是不是没有输出对应的引脚,或者混合模式没选对。

3.3 第三步:读取DBuffer里的DecalNormal

在材质图表里添加一个SceneTexture节点,把Scene Texture Id设置成DecalNormal,这时它输出的就是DBuffer里累积的法线数据,空间是世界空间。

怎么验证这个数据对不对?一个常用技巧:把这个节点的输出直接连到Emissive Color,在材质预览里放一个球体或平面,你能看到像法线可视化一样的蓝紫色效果。如果画面看起来是统一的颜色,说明DBuffer没有写入法线,基本上是上一步没输出Normal引脚,或者DBuffer Decal没有设成对应的混合模式。

从这一步开始,我们拿到的DecalNormal就是“当前DBuffer里累积的法线”。理论上它包含了场景中原有Decal写入的数据。如果你只是单一Decal,它就是你当前这个Decal准备写进去的法线。有了它,就可以放手做自定义插值了。

3.4 第四步:把自定义法线贴图转成世界空间

我们的目标是把一张额外的法线贴图跟DBuffer里的法线做混合,这张法线贴图采样出来默认是在切线空间,而DBuffer的法线是世界空间。所以不能直接Lerp,必须先做空间转换。

具体节点连接:

TextureSample (法线贴图) RGB -> Global Vector 2? 实际上直接连RGB -> TransformTangentSpaceToWorld Tangent -> VertexTangentWS Binormal -> VertexBinormalWS Normal -> VertexNormalWS -> Normalize

这里最大的坑在于:很多教程里提供的TransformTangentSpaceToWorld节点,TBN向量用的是物体本身的顶点数据。但在Decal材质里,VertexNormalWS并不是背景物体的表面法线,而是Decal投射方片自身的方向。所以TBN的基准是贴合贴花投射方向的,转换出来的法线才是贴花应该表现的方向。

如果你发现自己采样出的法线总是“翻”的,或者光照下凹凸方向反了,大概率是Binormal方向反了,也就是TBN的叉积方向反了。解决办法是检查VertexBinormalWS节点是不是反了,或者直接在材质里给Binormal乘一个-1

3.5 第五步:按自定义权重做插值与归一化

两个世界空间法线都准备好了,接下来就是插值运算的核心环节。

创建一条插值链路:

SceneTexture(DecalNormal) A TextureSample(法线贴图转世界空间) B LinearInterpolate (Lerp) Alpha = Mask 贴图(或标量参数) -> Normalize -> 输出到 Normal 引脚

这里Alpha=0时,输出完全保留DBuffer里的法线;Alpha=1时,完全替换成自定义贴花的法线;中间值就是过渡带。

但直接用一个固定Alpha往往不够用。我一般会在Alpha链路上加一层控制,让过渡带更精准。比如用Pow节点调整Alpha的Gamma,Pow(Mask, 2)会把Alpha整体压暗,让过渡更集中在贴花中心区域;Pow(Mask, 0.5)则会让过渡更早开始、更柔和。还可以加一个Saturate节点,保证Alpha不会因为后续运算溢出到0到1之外。

另一个进阶玩法,是根据角度控制插值范围。比如我只想让法线插值在“接近水平”的表面生效,不希望在墙面上也这么明显,就可以加一个基于WorldNormal点积的Mask:

WorldNormal(SceneTexture里没有,用VertexNormalWS? 或者用DecalNormal做近似) Dot (WorldNormal, 某个朝向向量) -> Saturate -> 作为Alpha的乘数

注意:Decal材质里,WorldNormal相关节点拿到的一般是贴花投射方向相关的法线,而不是背景物体表面法线。如果要做基于角度的Mask,优先从DBuffer的DecalNormal里去取,或者干脆在你的材质里把插值前的DBuffer法线单独引出来参与运算,这样更接近真实底色。

最后记得在插值结果后接一个Normalize。线性插值两个模长为1的法线,中间结果模长会小于1,虽然大多数光照公式会重新归一化,但为了让显示稳定、不出现奇怪的暗部,还是手动归一下保险。

3.6 一个可以直接照抄的节点网络示例

假设你的需求是“裂纹区域法线用法线贴图,裂纹边缘平滑过渡到原表面”,你最终的节点网络大概是这样的:

  1. TextureSample采样一张法线贴图CrackNormal
  2. CrackNormal的RGB连入TransformTangentSpaceToWorld
  3. Transform的TBN数据从VertexTangentWSVertexBinormalWSVertexNormalWS三个节点获取。
  4. Transform输出经过Normalize,记为CN(Custom Normal)。
  5. 新建SceneTexture节点,Scene Texture Id = DecalNormal,输出记为DN(DBuffer Normal)。
  6. 新建TextureSample采样一张蒙版贴图CrackMask,或者直接用一个Scalar Parameter做强度。
  7. CNDN连到LinearInterpolate的B和A输入,Alpha接CrackMask
  8. Lerp结果过Normalize,最终输出到材质Normal引脚。

需要额外控制力度时,在Alpha链路上加一个Scalar Parameter节点(命名Strength),相乘后进Saturate,再接Lerp

参数建议:

参数推荐范围说明
Strength0 ~ 1整体控制法线替换强度,0为不替换,1为完全替换
Mask Pow0.5 ~ 2控制过渡带的锐利程度,数值越大过渡越集中在中心
Mask对比度0 ~ 1简单权重调整,配合Saturate使用
法线贴图强度0.1 ~ 1如果你用法线贴图自身的Intensity控制凹凸高度

这套网络在PC上实跑,墙面补丁的边缘过渡已经非常自然,光源转动时,裂纹区域的明暗变化和地面周围的光照方向是连续的,不会再出现“一块补丁浮在表面”的违和感。

4. 踩坑记录与排查经验

4.1 平行光照射下Decal法线看起来还是不对

这个是我遇到频次最高的问题。现象是:法线插值算得没问题,但贴花在平行光下的光照表现总是怪怪的,尤其是早上的低角度光,裂纹区域忽明忽暗,甚至像凹陷变成了凸起。

第一次排查我以为是TBN方向反了,反复调Binormal的符号,效果还是不对。后来发现,问题出在GBuffer里法线编码方向。UE的GBuffer法线是经过特定方式打包的,直接拿DecalNormal的SceneTexture输出和自定义法线做插值,有时会因为编码空间不一致产生偏差。

解决方案是:检查你的法线贴图压缩设置。在贴图导入设置里,Texture GroupCompression Settings要选择Normalmap(法线贴图)相关选项,导入后UE会自动选择BC5压缩格式,采样时数据才会被正确解码。如果贴图用了默认的sRGB压缩,法线数据被当成颜色处理,整个插值结果必然乱七八糟。

另一个检查点是材质预览里开Buffer VisualizationNormal通道看看。如果法线可视化是五颜六色、没有明显方向感,大概率是贴图压缩设置不对,重新导入贴图就能解决。

4.2 DBuffer Decal在半透明物体上失效

做场景时经常有半透明物体也要贴花,比如玻璃上的污渍、冰面上的裂纹。DBuffer Decal贴上去,结果在PIE里看什么反应都没有。

这个不是Bug,是管线逻辑决定的。DBuffer的数据是在BasePass阶段和材质属性合并的,半透明物体走的是Translucent Pass,根本不经过BasePass,因此它读不到DBuffer里的贴花数据,自然就不显示。

要在这类物体上显示贴花,有几个思路:

  • 把物体材质改成Masked或Opaque,如果物体本身不需要半透明,这是最简单有效的。
  • 使用传统Translucent Decal,但在UE5里它的法线混合能力比较弱,不推荐用于需要法线细节的场合。
  • 手动在材质里采样贴花纹理,把贴花投影坐标传给半透明材质,自己做混合。这个灵活但工作量大,一般用在必须半透明、又很追求效果的情况下。

4.3 多Decal重叠时的顺序与性能问题

一个像素被多个DBuffer Decal覆盖时,效果会比传统方式稳定得多,因为数据在DBuffer里累积,而不是后写的覆盖先写的。但顺序依然有影响,具体表现是:后创建的Decal Actor在场景里通常会显示在更前面,如果你发现某个贴花被另一个“压住”了,检查Decal Group里的Sort Order属性。

性能方面,DBuffer模式下每个Decal仍然要绘制一次投射体,但写入的是独立的DBuffer RT,不再依赖GBuffer的随机读写。实机测试里,同一场景30个Decal从传统Translucent切到DBuffer,PC上的GPU耗时下降明显,主要省在overdraw减少上。但All模式的满通道DBuffer在主机上会增加不少带宽,需要注意Profile里DBuffer的显存和带宽指标。

4.4 常见问题速查表

现象可能原因检查方法
Blend Mode里没有DBuffer选项项目设置DBuffer Decals未开启检查Project Settings -> Rendering -> Decals
材质输出Normal引脚了但DecalNormal还是空材质Blend Mode不是DBuffer系列确认Blend Mode带DBuffer字样
法线方向颠倒,凹凸变反Binormal方向反了或贴图压缩设置错误调整TBN符号,检查贴图Compression Settings
法线过渡生硬,边缘有突变缺少蒙版或Alpha控制在Lerp的Alpha链路上加Pow/Saturate调整
半透明物体不显示DBuffer Decal半透明Pass不参与DBuffer合成改用Opaque/Masked,或用传统Decal
DBuffer开All后帧率下降明显带宽占用过高降为Standard,或裁剪不需要的贴花数量

最后再分享一个我个人的实操习惯:DBuffer Decal不是越复杂越好。这个方案更适合在需要法线、颜色、粗糙度协同变化的贴花上使用,比如地面破损、墙壁裂缝、弹孔补丁。单纯一个颜色类贴花,用传统Translucent方式反而更省,没必要为了秀技术把所有贴花都换成DBuffer。项目里把两种方式结合着用,性能和效果都能拿到。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询