UE5材质系统实现轮廓发光:深度与法线双检测技术详解 1. 项目概述与核心思路最近在做一个UE5的Demo需要给场景里的特定物体加上一个高亮的轮廓发光效果比如可交互的机关、重要的任务物品。一开始我琢磨着是不是得找插件或者写个复杂的后处理材质。但转念一想这种基础又高频的需求引擎内置的材质系统应该能搞定。果然研究了一下深度缓冲和法线缓冲发现不用任何插件纯用材质蓝图5分钟就能搭出一个效果相当不错的轮廓发光。而且只用深度检测容易在复杂场景里“误伤”背景只用法线检测又可能漏掉平滑曲面所以我这里把深度和法线检测结合起来做了一个“双保险”的方案稳定性和效果都提升了不少。这个方案特别适合游戏里的高亮提示、技能范围指示或者解谜元素强调无论你是场景美术、TA还是想学点材质技巧的程序都能快速上手。2. 核心原理深度与法线缓冲的双重检测要理解怎么做出轮廓得先明白引擎是怎么“看”世界的。我们屏幕上每一个像素的颜色最终是由一大堆信息混合出来的其中有两个后台的“隐藏图层”至关重要就是深度缓冲Depth Buffer和法线缓冲Normal Buffer。2.1 深度缓冲记录“谁在前谁在后”你可以把深度缓冲想象成一张黑白的高度图。离摄像机越近的物体它在深度缓冲里对应的像素就越白值越小通常规范化为0-1近处接近0离得越远的物体像素就越黑值接近1。当我们渲染一个物体时引擎会检查当前要画的这个像素它的深度值比深度缓冲里已经记录的那个像素更靠前吗如果是就覆盖它如果不是就被遮挡。轮廓发光的关键就在这里一个物体的边缘恰恰是它的表面像素与背景或其他物体的深度值发生剧烈变化的地方。比如一个盒子放在地面上盒子侧面的像素和它紧挨着的地面像素深度值就差了很多。我们只要在材质里采样当前像素周围一圈的深度值看看它们和当前像素的深度值相差是否超过一个阈值就能判断这个像素是不是位于物体的边界上。这就是深度检测的基本原理。注意深度检测有个小问题。如果两个物体紧挨着但它们的表面几乎在同一深度平面上比如墙上贴着一张海报深度差很小深度检测就可能失效轮廓线会断开。这就是我们需要法线检测来补足的原因。2.2 法线缓冲记录“面朝哪里”法线缓冲存储的是每个像素所对应物体表面的法线向量方向。这个向量通常用RGB颜色来表示红色分量代表法线指向左右X轴绿色代表上下Y轴蓝色代表前后Z轴。一个朝向正右方的平面它在法线缓冲里可能就是纯红色1,0,0。物体的轮廓边缘同样也是法线方向发生突变的地方。还是那个盒子它侧面的法线是指向侧方的而它旁边的背景或地面的法线是指向上方的这两个法线向量点乘的结果衡量方向相似度会很小。通过采样并比较当前像素与周围像素的法线方向我们也能捕捉到边界。2.3 双检测融合优势互补单独使用任何一种检测都有其局限性纯深度检测对深度变化敏感能很好地勾勒出物体与背景的边界。但对于共面的不同物体如地板上的瓷砖缝或者平滑曲面上的轮廓可能无法识别。纯法线检测对表面朝向变化敏感能捕捉到同一个物体上不同面之间的边界如立方体的棱甚至能画出平滑曲面上的轮廓。但对于两个法线方向相近但深度不同的物体边界如平行放置的两块板子可能失效。所以最稳妥的办法是两者结合取并集。只要一个像素在深度上或者法线上被判定为边缘我们就把它算作轮廓像素。这样既能保证物体外轮廓清晰也能在物体自身结构变化处产生描边效果更鲁棒。3. 材质蓝图实战一步步构建轮廓理论说完了我们直接上引擎操作。在内容浏览器里右键创建一个新的材质我给它起名叫M_Outline_Glow。3.1 获取核心纹理采样节点首先我们需要把深度和法线缓冲“抓取”到材质里。在材质图表空白处右键搜索SceneTexture。拖入两个SceneTexture节点。在第一个节点的属性面板中将Scene Texture Id设置为Post Process Input0。这里是个关键点在UE5的移动管线Mobile和前向渲染器Forward Renderer中深度信息通常从这里获取。如果是延迟渲染Deferred Renderer可能需要选择CustomDepth或CustomStencil但为了通用性我们使用Post Process Input0它包含了经过处理后的场景深度。在第二个SceneTexture节点的属性面板中将Scene Texture Id设置为World Normal。这样我们就拿到了世界空间下的法线信息。为了后续计算方便我们通常需要把采样到的原始数据转换一下。对于深度我们可以连一个LinearizeDepth节点需要搜索添加将其转换为线性的、易于比较的深度值。对于法线因为World Normal输出的是0-1范围我们需要用*2 - 1的运算将其转换回 (-1, 1) 范围的向量。3.2 实现边缘检测算法边缘检测的本质是比较一个像素和它邻居的差异。我们采用经典的索贝尔Sobel算子思路但为了简单快速这里用一个简化版采样上下左右四个相邻像素。我们需要用到TextureCoordinate节点获取当前UV和TexelSize节点获取一个像素的UV大小。具体操作如下计算偏移量创建四个二维向量常量分别代表向左、向右、向上、向下偏移一个像素的UV值。例如向左偏移(-TexelSize.X, 0)。采样邻居将当前UV分别与这四个偏移量相加得到上、下、左、右四个邻居的UV坐标。然后用这些UV坐标分别去采样LinearizeDepth后的深度值和转换后的法线向量。计算差异深度差异分别计算当前像素深度与四个邻居深度的绝对值差然后取这四个差值中的最大值作为该像素的“深度边缘强度”。法线差异分别计算当前像素法线与四个邻居法线的点积Dot节点。因为点积结果越接近1表示方向越相同越接近-1表示方向相反。我们用1 - DotResult来得到差异度同样取四个方向差异度的最大值作为“法线边缘强度”。设定阈值与合并深度和法线的差异值都在0到某个数之间。我们创建两个标量参数比如Depth_Threshold和Normal_Threshold。将计算出的深度边缘强度与Depth_Threshold比较大于阈值则输出1否则为0可以用Step或If节点更简单的是用Saturate配合乘除运算来模拟。法线边缘强度同理处理。最后将这两个二值化0或1的结果用Max节点合并只要有一个为1最终输出就是1表示该像素是轮廓。3.3 设计发光外观与颜色得到轮廓掩码0或1后我们要把它变成好看的发光效果。轮廓扩张与柔化直接使用二值化的轮廓会非常生硬。我们可以对上面计算出的“深度边缘强度”和“法线边缘强度”在二值化之前先进行一次SmoothStep处理让边缘有一个平滑的过渡。更好的办法是对最终的轮廓掩码进行一点点模糊。添加一个Blur节点或通过多次采样取平均模拟设置一个很小的半径如0.005这能让轮廓线变宽变柔和产生光晕感。将这个模糊后的结果作为发光的强度Alpha。颜色与叠加创建一个颜色参数Glow_Color让美术可以自由调整发光颜色。将颜色与上一步得到的发光强度相乘。然后最关键的一步是如何将这个发光叠加到原场景。我们需要使用Blend节点。选择混合模式在材质的根节点即最终输出节点上将Blend Mode设置为Additive相加混合并将Shading Model设置为Unlit无光照。这样这个材质输出的颜色就会直接加到背景画面上产生发光效果。最终连接将处理好的颜色连接到Emissive Color引脚将发光强度连接到Opacity引脚。对于Additive混合模式Opacity实际上控制着叠加的强度。3.4 完整材质节点图与参数化把上述所有步骤连起来你的材质图大概会包含以下几个功能模块纹理采样与数据转换模块、深度边缘检测模块、法线边缘检测模块、阈值判断与合并模块、后处理模糊与颜色混合模块。为了灵活控制我们应该把所有可调节的部分都做成参数Depth_Threshold深度检测灵敏度。值越小对微小的深度变化越敏感轮廓越粗可能包含更多噪点。Normal_Threshold法线检测灵敏度。值越小对细微的法线变化越敏感。Glow_Color发光颜色。Glow_Intensity发光强度可以乘在颜色或Alpha上。Outline_Width轮廓宽度可以通过控制模糊半径或采样偏移倍数来实现。完成后你的材质资产大概长这样材质M_Outline_Glow 混合模式Additive 着色模型Unlit 参数组 - [Threshold] - Depth_Threshold (Scalar, Default: 0.02) - Normal_Threshold (Scalar, Default: 0.4) - [Appearance] - Glow_Color (Vector3, Default: (0, 1, 1) // 青色) - Glow_Intensity (Scalar, Default: 5.0) - Outline_Width (Scalar, Default: 1.0)4. 场景应用与材质实例化材质做好了怎么用到物体上呢这里有两种主流方法推荐第二种。4.1 方法一直接应用于模型材质创建一个材质实例Material Instance基于M_Outline_Glow。然后将这个材质实例赋给需要发光的模型。这种方法最直接但有个大问题发光的轮廓可能会被模型自身遮挡。因为这是在模型表面绘制的如果摄像机看到的是模型的背面或者模型自身结构复杂轮廓光可能显示不正确。4.2 方法二通过后期处理盒子推荐这是更稳定、更通用的方法。我们不在物体本身的材质上画而是在整个屏幕的后处理阶段来画。在场景中放置一个Post Process Volume。将这个体积框设置为Infinite Extent无限范围影响整个场景。在体积的Rendering Features-Post Process Materials数组中添加我们的M_Outline_Glow材质。但是这样所有物体都会有轮廓。我们需要告诉材质“只给特定的物体画轮廓”。这就需要用到一个关键技巧自定义深度Custom Depth。4.3 结合自定义深度实现精准控制自定义深度是引擎为选定的物体单独渲染的一层深度缓冲。我们可以利用它作为“标记”。标记物体在需要显示轮廓的物体静态网格体或角色骨骼网格体的细节面板中找到Rendering部分勾选Render CustomDepth Pass。修改材质回到我们的M_Outline_Glow材质。我们不再或不仅与普通的场景深度比较而是与自定义深度比较。添加第三个SceneTexture节点设置其Scene Texture Id为CustomDepth。修改边缘检测逻辑。我们可以将原来的深度检测分支改为同时检测“当前像素有自定义深度即是要高亮的物体而它的邻居像素没有自定义深度或自定义深度值相差很大”。这样就精确地只在高亮物体与周围环境的边界处产生轮廓。甚至可以简化直接采样自定义深度缓冲然后对其做边缘检测得到的边缘就是高亮物体的轮廓。因为只有被标记的物体才会写入这个缓冲。优化性能只对需要轮廓的物体开启Render CustomDepth Pass避免不必要的渲染开销。在后期处理材质中因为基于全屏缓冲操作无论场景中有多少个高亮物体都只执行一次材质计算效率很高。5. 性能优化与常见问题排查一个效果再好如果太耗性能也白搭。这里分享几个优化点和踩坑记录。5.1 性能优化要点控制采样次数我们的方案采样了中心点、上、下、左、右共5个点。这是性能和质量的平衡点。如果追求极致性能可以只采样左右和上下用4次采样。但不要低于4次否则边缘可能不连续。降低渲染分辨率轮廓发光通常不需要全分辨率渲染。可以在Post Process Volume中将M_Outline_Glow材质添加到Post Process Materials时通过Blendable Location设置为Before Tonemapping等阶段并考虑配合一个半分辨率或四分之一分辨率的渲染目标来实现大幅减少像素计算量。模糊操作本身也有降分辨率的效果。使用材质实例参数所有调节项阈值、颜色、强度都必须通过材质实例参数暴露。这样美术可以在运行时动态调整而无需重新编译材质。这也是为什么我之前强调要参数化。避免每帧动态计算偏移TexelSize是常量不要放在复杂的动态计算网络中。确保它只被计算一次。5.2 常见问题与解决方案问题现象可能原因排查与解决思路轮廓闪烁或抖动深度阈值Depth_Threshold设置过低在摄像机或物体微动时深度值的微小波动被误判为边缘。适当提高Depth_Threshold值。检查深度值是否已线性化非线性深度在远处精度不足更容易抖动。轮廓线太粗或包含噪点边缘检测的采样偏移量一个像素的UV偏移过大或者模糊半径Outline_Width太大。深度/法线阈值太低将非边缘信息也包含了进来。减小采样偏移倍数保持为1倍TexelSize。先调高Depth_Threshold和Normal_Threshold让轮廓线变细、干净再用模糊来柔和和加宽。轮廓在特定角度消失法线检测分支可能出了问题。当物体表面法线与视线方向平行时屏幕空间法线变化不剧烈。确保法线检测的阈值Normal_Threshold设置合理通常0.3-0.5。可以适当调低或增加法线检测的权重。归根结底这正是需要深度法线双检测的原因一个失效时另一个能补上。没有轮廓效果材质混合模式未设置为Additive或Translucent。后期处理体积未启用或优先级不够。自定义深度未开启。1. 检查材质根节点Blend Mode。2. 检查Post Process Volume的Enabled和Priority确保大于其他可能覆盖的体积。3. 检查目标物体是否勾选Render CustomDepth Pass。4. 在材质中连一个Emissive Color为纯色的测试节点看后期材质是否生效。轮廓颜色不亮或发灰发光强度Glow_Intensity不够。在Additive混合下颜色是线性叠加的需要较高的值如5.0, 10.0才在屏幕上显得亮。HDR和Tonemapping也会影响。提高Glow_Intensity。检查场景是否启用了HDRGlow_Color可以尝试使用超过1.0的亮度值。确保颜色连接到了Emissive Color并且Opacity有正确的强度输入。移动端上效果差或性能低下移动设备对全屏后处理和非必需的高精度计算敏感。可能使用了高消耗的节点如DDX/DDY屏幕空间导数。简化方案考虑在移动端只使用深度检测或使用更低的采样数。确保所有计算都在Shader Model 5或移动端支持的特性内。使用材质质量开关为移动设备创建简化版的材质实例。5.3 一个实用的调试技巧当效果不对时可以快速隔离问题。创建三个简单的测试材质深度可视化材质直接将LinearizeDepth的结果作为灰度色输出。观察深度变化是否连续、合理。法线可视化材质将World Normal转换后用ComponentMask拆分RGB查看或直接作为颜色输出。轮廓掩码可视化材质将最终合并前的深度边缘强度/法线边缘强度或最终的二值化掩码作为灰度色输出。将这三种材质分别作为后期材质应用你就能清晰地看到到底是深度信息有问题还是法线信息有问题或者是边缘检测算法本身的计算逻辑出了问题。