
上个月做场景细节时我在墙面叠一张带裂纹的贴花要求裂纹区域的法线跟着贴图走但裂纹边缘要和墙面原有法线自然过渡。调了半天默认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在渲染管线里的位置以及它解决了什么DBufferDecal 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时有几种模式可选常见的是None、Standard、All。区别在于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 Decals从None改成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不会写入法线数据后面SceneTexture的DecalNormal读出的是空值。Blend Mode的具体选择要看你的贴花需求。DBuffer Decal (Stain)偏向污渍类不怎么改变表面形状DBuffer Decal (Color Normal Roughness)就是全都要适合我做裂纹效果这种需要法线和粗糙度协同变化的场景。这里有个细节容易踩坑选了DBuffer混合模式之后材质细节面板里的Decal Blend相关选项可能就不再像普通模式那样好用了。参数节点里有的值会显示为Default意思是“这个属性由DBuffer模式接管”。你在材质里配置的Roughness、Metallic引脚值能不能生效取决于混合模式的组合方式。所以如果发现某些属性怎么调都没反应先检查是不是没有输出对应的引脚或者混合模式没选对。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 引脚这里Alpha0时输出完全保留DBuffer里的法线Alpha1时完全替换成自定义贴花的法线中间值就是过渡带。但直接用一个固定Alpha往往不够用。我一般会在Alpha链路上加一层控制让过渡带更精准。比如用Pow节点调整Alpha的GammaPow(Mask, 2)会把Alpha整体压暗让过渡更集中在贴花中心区域Pow(Mask, 0.5)则会让过渡更早开始、更柔和。还可以加一个Saturate节点保证Alpha不会因为后续运算溢出到0到1之外。另一个进阶玩法是根据角度控制插值范围。比如我只想让法线插值在“接近水平”的表面生效不希望在墙面上也这么明显就可以加一个基于WorldNormal点积的MaskWorldNormalSceneTexture里没有用VertexNormalWS? 或者用DecalNormal做近似 Dot (WorldNormal, 某个朝向向量) - Saturate - 作为Alpha的乘数注意Decal材质里WorldNormal相关节点拿到的一般是贴花投射方向相关的法线而不是背景物体表面法线。如果要做基于角度的Mask优先从DBuffer的DecalNormal里去取或者干脆在你的材质里把插值前的DBuffer法线单独引出来参与运算这样更接近真实底色。最后记得在插值结果后接一个Normalize。线性插值两个模长为1的法线中间结果模长会小于1虽然大多数光照公式会重新归一化但为了让显示稳定、不出现奇怪的暗部还是手动归一下保险。3.6 一个可以直接照抄的节点网络示例假设你的需求是“裂纹区域法线用法线贴图裂纹边缘平滑过渡到原表面”你最终的节点网络大概是这样的TextureSample采样一张法线贴图CrackNormal。把CrackNormal的RGB连入TransformTangentSpaceToWorld。Transform的TBN数据从VertexTangentWS、VertexBinormalWS、VertexNormalWS三个节点获取。Transform输出经过Normalize记为CNCustom Normal。新建SceneTexture节点Scene Texture Id DecalNormal输出记为DNDBuffer Normal。新建TextureSample采样一张蒙版贴图CrackMask或者直接用一个Scalar Parameter做强度。把CN和DN连到LinearInterpolate的B和A输入Alpha接CrackMask。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 Group和Compression Settings要选择Normalmap法线贴图相关选项导入后UE会自动选择BC5压缩格式采样时数据才会被正确解码。如果贴图用了默认的sRGB压缩法线数据被当成颜色处理整个插值结果必然乱七八糟。另一个检查点是材质预览里开Buffer Visualization的Normal通道看看。如果法线可视化是五颜六色、没有明显方向感大概率是贴图压缩设置不对重新导入贴图就能解决。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切到DBufferPC上的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或用传统DecalDBuffer开All后帧率下降明显带宽占用过高降为Standard或裁剪不需要的贴花数量最后再分享一个我个人的实操习惯DBuffer Decal不是越复杂越好。这个方案更适合在需要法线、颜色、粗糙度协同变化的贴花上使用比如地面破损、墙壁裂缝、弹孔补丁。单纯一个颜色类贴花用传统Translucent方式反而更省没必要为了秀技术把所有贴花都换成DBuffer。项目里把两种方式结合着用性能和效果都能拿到。