1. 项目概述从“描边”到“空间感知”的材质艺术在UE4或UE5里折腾过材质的朋友肯定都动过给模型加描边的念头。这玩意儿看着简单不就是给模型外轮廓加一圈颜色嘛但真上手做你会发现从简单的卡通渲染到复杂的场景高亮再到数字孪生里对设备状态的视觉强调描边都是个绕不开的基础又核心的技术点。我见过不少项目初期随便找个教程抄个后处理描边结果一到复杂场景就性能吃紧或者边缘闪烁后期重构成本巨大。描边材质远不止是“加个边”。它本质上是一种对模型空间关系的视觉化增强手段。在游戏里它能让角色在混乱的场景中脱颖而出在交互应用或数字孪生中比如你提到的“智慧工厂”精准、稳定的描边能清晰指示出可交互物体或异常设备是提升用户体验和操作效率的关键。今天我就结合自己踩过的坑和项目经验把UE4里描边材质的几种主流实现原理、各自的“脾气秉性”以及怎么根据你的项目需求是手机游戏还是高端数字孪生来选择和优化给你彻底讲透。我们会从最基础的网格扩张法讲到更灵活但稍复杂的后处理法最后聊聊一些高级混合技巧和避坑指南。2. 描边核心原理与方案选型知其所以然在动手写任何一行材质节点之前我们必须搞清楚我们到底要识别和渲染什么答案是“边缘”。在三维空间中“边缘”通常指代两种东西一是模型自身的轮廓Silhouette Edge即背向摄像面的部分与面向摄像面部分的交界二是模型表面的尖锐折痕Crease Edge。不同的描边技术核心就在于用不同的方法去检测和凸显这些“边缘”。2.1 主流实现方案深度对比市面上主流的UE4描边方案归根结底就三大类每一类都有其鲜明的优缺点和适用场景选错了后期会非常痛苦。方案一基于网格扩张的材质描边Mesh Expansion这是最经典、最直观的方法。思路很简单复制一个原始网格用材质让它沿着顶点法线方向“膨胀”一圈只渲染这个膨胀体的背面从而形成轮廓边。原理在材质中利用World Position OffsetWPO或Vertex Normal驱动顶点位置沿法线外扩。通常我们会将原网格和膨胀网格渲染两次原网格正常渲染膨胀网格只渲染背面通过Two Sided材质并设置Cull Mode为CW或CCW并赋予一个纯色。优点原理简单控制精细每个模型独立计算描边粗细、颜色可以按材质、甚至按模型顶点颜色进行个性化控制非常适合角色、道具等需要差异化表现的物体。稳定性高描边是模型的一部分不受场景深度复杂度影响几乎不会出现闪烁或断裂。性能可预测开销主要来自多渲染一次网格Draw Call增加和顶点变换对于描边物体数量不多的情况如主要角色和关键物品非常高效。缺点粗细不均在模型表面曲率变化大的地方如角色鼻尖、耳朵由于法线方向发散描边会不自然地变粗。无法处理遮挡膨胀的网格如果被其他物体遮挡描边也会被硬生生切断看起来不自然。不适合复杂场景如果场景中成百上千个物体都需要描边每个都渲染两遍Draw Call会爆炸。方案二基于后处理的屏幕空间描边Post Process这是目前大型场景和开放世界最常用的方案。它不再关心单个模型而是站在整个屏幕的视角通过分析渲染完成的画面信息深度、法线来找出边缘。原理在后处理材质Post Process Material中获取当前像素的场景深度Scene Depth和场景法线Scene Normal。通过对比当前像素与周围像素的深度值和法线方向如果发现差异超过某个阈值就判定该像素位于“边缘”上然后对其进行着色。深度边缘检测能捕捉所有物体的轮廓和物体间的遮挡边界。法线边缘检测能捕捉物体表面本身的尖锐棱角。 通常两者结合使用效果最全面。优点全局性与一致性一次性处理整个屏幕的所有边缘无论物体多少性能开销相对恒定与屏幕分辨率相关。效果统一整个画面的描边风格颜色、粗细是完全统一的视觉上很协调。天然处理遮挡基于深度信息能正确处理物体间的遮挡关系。缺点控制粒度粗难以对场景中某个特定物体进行差异化描边比如主角用红色敌人用黄色。虽然可以通过Custom Depth和Custom Stencil通道来实现物体级别的控制但这需要额外的渲染开销和设置。依赖画面信息在屏幕空间信息不足或失效的区域如透明物体、粒子效果后面描边会消失或出错。性能与分辨率绑定高分辨率下全屏像素处理开销不小。方案三基于Sobel算子的边缘检测这其实是后处理方案的一种具体实现算法但因为太经典且高效值得单独拿出来说。Sobel算子是一种图像处理中的边缘检测算子通过两个3x3的卷积核分别检测水平和垂直方向的梯度来快速计算图像中每个像素点的梯度幅值梯度大的地方就是边缘。在UE4中的实现在后处理材质中对Scene Depth或Scene Normal纹理进行Sobel滤波。具体操作是对当前像素及其周围8个像素进行采样按照Sobel算子的权重进行加权计算得到梯度值。这个值越大说明该点变化越剧烈越可能是边缘。优点计算高效效果清晰是屏幕空间边缘检测的“标准答案”。缺点继承了所有后处理描边的缺点并且对深度/法线纹理的噪声比较敏感容易产生“毛刺”。实操心得怎么选选网格扩张如果你的项目是角色/道具导向的如ACT、RPG需要差异化、高质量、稳定的描边且描边物体数量可控50个这就是你的首选。很多二次元风格化渲染的角色轮廓光本质就是它的变种。选后处理Sobel如果你的项目是场景/环境导向的如开放世界、RTS、数字孪生需要为大量物体提供统一的视觉高亮或轮廓提示那么后处理方案是唯一可行的选择。数字孪生中高亮整个设备或区域用它就对了。高级操作混合使用成熟项目往往会混合使用。例如用后处理处理场景建筑、地形的整体轮廓保证性能同时用网格扩张法为关键交互角色和物品赋予更精美、可定制的描边保证品质。这需要良好的架构设计。3. 核心细节解析与实操要点选定方案后我们深入每个方案的实现细节。这里藏着大量的“魔鬼”参数调不好效果就是天壤之别。3.1 网格扩张法顶点偏移的艺术与陷阱创建一个新的材质命名为M_Outline_MeshExpansion。关键步骤如下材质基础设置Material Domain设置为Surface。Blend Mode设置为Opaque。Shading Model设置为Unlit因为我们只需要纯色描边。最关键的一步勾选Two Sided并将Two Sided Sign连接到后续计算中可选用于控制方向。在Details面板的Rendering部分将Cull Mode设置为Two Sided。但更精细的控制是复制原始网格时在材质的Customized UVs或通过参数控制让复制体的材质Cull Mode设为CW顺时针剔除而原始网格材质保持CCW逆时针剔除默认。这样复制体只渲染背面即膨胀出去的部分。顶点偏移World Position Offset实现 这是核心中的核心。我们需要将顶点沿着其法线方向向外移动。获取顶点在世界空间中的法线使用TransformVector节点将Vertex Normal WS或Absolute World Position派生出的向量从世界空间转换到切线空间不这里通常需要的是视图空间View Space的法线因为扩张方向最好相对于摄像机。更常见的做法是直接使用CameraVector节点不对。标准做法是使用Pixel Normal WS吗也不是。对于网格扩张我们通常使用Object Normal或Vertex Normal。推荐节点链Vertex Normal WS-Normalize-Multiplyby aScalar Parameter(比如叫Outline Thickness) - 连接到World Position Offset。但这样扩张是均匀的会带来“粗细不均”问题。为了解决这个问题我们需要一个“基于摄像机视角的缩放因子”。高级技巧视角相关扩张计算顶点到摄像机的方向向量Camera Position-World Position点乘Dot顶点法线。当法线朝向摄像机时点乘结果接近1我们减少扩张量当法线垂直于视线时点乘结果接近0即轮廓边缘我们保持或增加扩张量。这能有效缓解曲面上的描边过粗问题。// 伪代码思路在材质蓝图中实现 float3 ViewDir normalize(CameraPosition - WorldPosition); float NdotV 1 - abs(dot(VertexNormalWS, ViewDir)); // 边缘处值大正面处值小 float3 FinalOffset VertexNormalWS * OutlineThickness * NdotV * (可选的控制曲线);将这个FinalOffset输出到World Position Offset。颜色与渲染Base Color连接一个Vector Parameter如Outline Color。Opacity设置为 1。Metallic,Specular,Roughness全部设为0。注意事项与避坑深度冲突Z-fighting膨胀的网格和原始网格距离太近会导致深度测试时像素闪烁。务必让膨胀网格的渲染深度比原始网格稍微靠后。有两种方法1在材质中轻微增加World Position Offset的偏移量使其略微远离相机2更可靠的是在复制网格的材质上设置一个很小的Depth Bias例如0.01到0.05。性能开销每个描边物体至少增加1个Draw Call。对于静态物体考虑是否可以将描边材质合并到同一张Draw Call中通过材质参数集合或纹理数组。对于动态物体这是不可避免的开销。复杂网格问题对于有复杂内部结构或非常薄的网格如纸张背面扩张可能会穿帮。可能需要调整扩张方向或使用Two Sided Sign进行更精细的控制。3.2 后处理Sobel法屏幕空间的卷积计算创建一个后处理材质M_PP_Outline_Sobel并将其添加到项目的后处理体积Post Process Volume中或通过C/蓝图动态添加。材质基础设置Material Domain设置为Post Process。Blendable Location设置为Before Tonemapping在色调映射前处理颜色更准确。获取屏幕纹理我们需要Scene Texture节点。分别获取Scene Depth和World Normal或Separate Translucency通道的法线质量更高。Scene Depth是核心。实现Sobel滤波Sobel算子需要当前像素周围3x3区域共9个像素的值。我们需要定义一个偏移量这个偏移量是单个像素的屏幕UV大小。1.0 / Viewport Size可以得到这个值。创建9个Scene Texture采样节点分别对应当前像素 (0,0) 和其上下左右及四个对角方向的偏移位置。例如Top-Left: UV (-1, -1) * PixelSizeTop: UV (0, -1) * PixelSizeTop-Right: UV (1, -1) * PixelSize... 以此类推深度边缘检测分别采样这9个点的深度值。Sobel算子有两个3x3卷积核Gx [-1, 0, 1] Gy [-1, -2, -1] [-2, 0, 2] [ 0, 0, 0] [-1, 0, 1] [ 1, 2, 1]在材质蓝图中我们需要手动计算卷积。这有点繁琐但逻辑清晰计算水平梯度Gx_depth (TopRight 2Right BottomRight) - (TopLeft 2Left BottomLeft)计算垂直梯度Gy_depth (BottomLeft 2Bottom BottomRight) - (TopLeft 2Top TopRight)深度边缘强度EdgeDepth sqrt(Gx_depth² Gy_depth²)法线边缘检测同理对9个点的法线向量取RGB通道进行同样的Sobel卷积计算。但由于法线是向量我们需要分别对R、G、B三个分量做卷积然后合并计算梯度幅值。计算量更大但能捕捉表面棱角。合并边缘FinalEdge max(EdgeDepth, EdgeNormal)。或者用lerp进行加权混合。阈值化与描边生成计算出的FinalEdge是一个连续值。我们需要通过一个阈值Threshold将其二值化或平滑化。float OutlineMask smoothstep(Threshold, Threshold Softness, FinalEdge);这里的Softness参数可以实现描边的抗锯齿平滑过渡避免生硬的锯齿边。应用颜色与混合float3 OutlineColor ...;float3 FinalColor lerp(OriginalSceneColor, OutlineColor, OutlineMask);将FinalColor输出到Emissive Color或直接作为最终颜色。注意事项与避坑深度纹理精度移动平台或远距离物体深度缓冲精度不足会导致边缘检测出现噪点或断裂。可以尝试对深度值进行非线性重映射或使用更高的精度格式如果平台支持。性能优化全屏Sobel滤波采样9次开销不小。优化方法降低采样率可以先对深度/法线纹理进行半分辨率或四分之一分辨率的下采样在低分辨率下进行边缘检测然后再上采样回原分辨率。边缘通常是大块区域对分辨率不敏感。分离渲染不是所有物体都需要后处理描边。可以为需要描边的物体单独渲染Custom Depth然后在后处理中只对Custom Depth进行边缘检测大幅减少计算量。这是实现物体差异化描边的关键透明物体处理标准的Scene Depth不包含透明物体。如果需要为透明物体加描边需要额外渲染透明物体的深度或使用Custom Depth。4. 实操过程与核心环节实现让我们以一个具体的、更高级的需求为例来串联整个实操流程在一个数字孪生智慧工厂项目中我们需要实现1对所有设备进行统一的白色细描边2对处于“报警”状态的设备进行闪烁的红色粗描边。这个需求混合了后处理统一描边和网格扩张/自定义深度差异化描边的思想。我们将采用“后处理为主Custom Depth通道为辅”的混合方案。4.1 场景与材质准备为所有工厂设备模型创建一个共用的主材质M_Device_Base包含基础的PBR属性。创建描边专用材质M_PP_Outline_Global后处理材质用于实现全局白色细描边基于Scene Depth和Scene Normal的Sobel检测。M_Outline_Alert用于报警设备的网格扩张材质或用于渲染到Custom Depth通道的特殊材质。4.2 全局白色描边实现后处理按照3.2节的步骤创建M_PP_Outline_Global。这里重点讲参数调节Outline Thickness通过控制Sobel采样偏移的倍数PixelSize * ThicknessScale来间接控制描边粗细。值越大采样范围越广检测到的边缘越“粗”。Depth Threshold和Normal Threshold分别控制深度和法线边缘的灵敏度。在工厂场景中设备棱角分明可以适当提高Normal Threshold来减少不必要的内部线条设备间可能有微小间隙Depth Threshold要设得足够小才能捕捉到。Outline Color设为白色(1,1,1)。Intensity控制描边颜色的强度设为0.8左右避免过于刺眼。将这个材质添加到主后处理体积中。4.3 报警红色描边实现Custom Depth通道法这是实现差异化描边的关键。我们不希望为每个报警设备单独渲染两遍网格性能差而是利用UE4的Custom Depth和Custom Stencil通道。启用报警设备的Custom Depth渲染选中所有可能报警的设备模型或在其基类蓝图中设置。在它们的Rendering属性中勾选Render CustomDepth Pass。可以进一步设置Custom Depth Stencil Value。例如设定报警设备为1其他物体为默认0。这样我们就用模板值区分了物体类型。创建报警描边后处理材质新建一个后处理材质M_PP_Outline_Alert。在这个材质里我们不采样Scene Depth而是采样Custom Depth纹理 (Scene Texture节点选择Custom Depth)。同样对Custom Depth进行Sobel边缘检测。但关键点来了在采样Custom Depth时我们可以同时采样Custom Stencil纹理。逻辑判断在边缘检测计算完成后我们增加一个判断如果当前像素对应的Custom Stencil Value等于我们设定的报警值例如1并且它位于边缘上EdgeMask Threshold那么我们才应用红色描边。实现闪烁添加一个基于游戏时间Time节点的正弦波或方波运算输出一个在0和1之间周期性变化的Pulse值。用这个Pulse值去调制描边颜色的Alpha或强度或者控制边缘检测阈值的动态变化就能实现“呼吸”或“闪烁”效果。// 伪代码思路 float EdgeMask Sobel(CustomDepthTex, UV); float StencilValue Sample(CustomStencilTex, UV).r; float AlertPulse 0.5 0.5 * sin(Time * FlashSpeed); // 闪烁控制 if (EdgeMask Threshold StencilValue 1.0) { float3 AlertOutlineColor float3(1.0, 0.0, 0.0) * AlertPulse; // 混合到最终颜色... // 注意这里可以设置比全局描边更大的ThicknessScale实现“更粗”的效果 }混合两个后处理效果可以将M_PP_Outline_Global和M_PP_Outline_Alert的逻辑合并到一个后处理材质中通过参数开关控制。更清晰的做法是创建两个独立的后处理材质通过Blendable的优先级顺序叠加。将报警描边材质的优先级设得更高确保它覆盖在全局描边之上。4.4 性能与质量平衡点下采样策略对于智慧工厂这种静态场景居多的应用可以对Custom Depth和Scene Normal纹理进行1/2下采样边缘检测计算量降至1/4在1080P屏幕上几乎看不出质量损失性能提升显著。距离剔除通过后处理材质的Sphere Mask或计算像素的世界位置距离可以实现远距离物体不渲染描边或使用更简化的描边进一步节省性能。报警描边的优化报警设备通常是少数。我们可以动态地将报警设备的Custom Depth渲染开关打开非报警状态则关闭避免一直渲染Custom Depth带来的开销。5. 常见问题与排查技巧实录即使原理都懂实操中还是会遇到各种妖魔鬼怪。下面是我总结的“排坑手册”。5.1 网格扩张法常见问题问题1描边在特定角度或模型部分消失。排查检查模型的法线。可能是模型本身法线有问题如翻转或不连续。在建模软件中检查并重新计算平滑组或法线。技巧在材质中尝试将顶点偏移的基准从Vertex Normal WS切换为Camera Vector与某个固定向量的混合作为保底偏移方向确保无论如何都有一个最小幅度的扩张。问题2描边闪烁Z-fighting。解决这是最常见的问题。立即检查并调整描边材质的Depth Bias深度偏移属性。从0.01开始尝试逐渐增加直到闪烁消失。同时确保World Position Offset的扩张量不要太小至少0.5-1个世界单位以上具体看模型比例。问题3曲面模型如球体描边粗细极端不均。解决必须实现“视角相关扩张”如3.1节所述。核心是NdotV法线与视线点乘因子。可以尝试1 - abs(dot(N, V))或pow(1 - abs(dot(N, V)), EdgeFalloff)来控制EdgeFalloff参数用于控制衰减曲线。5.2 后处理描边法常见问题问题1描边出现大量“毛刺”或噪点尤其在平坦表面上。排查这是深度纹理精度不足或场景中有细微深度变化的物体如细小网格、粒子导致的。解决提高阈值适当增加Depth Threshold过滤掉微小的深度变化。深度滤波在对Scene Depth进行Sobel采样前先对其进行一次简单的3x3或5x5的高斯模糊单独采样模糊不要用后处理的模糊平滑深度值的突变。这能有效去除噪点但会让边缘稍微变软。法线权重增加法线边缘检测的权重。因为平坦表面的法线是均匀的不会被检测为边缘可以中和掉深度噪点。问题2透明物体如玻璃、粒子没有描边。原因Scene Depth默认不包含透明物体的深度。解决对于重要透明物体单独为其开启Render CustomDepth Pass并在后处理中同时结合Scene Depth和Custom Depth进行边缘检测。使用Separate TranslucencyUE4的Separate Translucency通道提供了半透明物体的法线信息如果材质启用了Output Velocity。可以在后处理中采样Separate Translucency Normal来为半透明物体添加基于法线的边缘。问题3描边在物体边缘内部“渗出”或太粗遮盖了本体。排查Sobel采样步长ThicknessScale设置过大。解决减小ThicknessScale。更精细的方法是使用“距离场边缘检测”的变种。不是简单比较相邻像素而是计算当前像素深度与周围一小块区域比如5x5内最大/最小深度的差值。这能产生更准确、更“细”的边缘线但计算开销更大。问题4性能开销大移动端帧率下降严重。解决首要方案下采样。将边缘检测的全过程在1/2甚至1/4分辨率下进行这是最有效的优化手段。减少采样次数尝试使用Roberts Cross或Prewitt算子它们只需要4个或6个采样点比Sobel的9个点更快虽然边缘质量稍差。分帧渲染如果不是每帧都需要更新描边比如场景变化缓慢可以尝试每两帧或每三帧计算一次描边中间帧复用上一帧的结果。限制范围使用遮罩如通过Scene Color的亮度或饱和度只对画面中重要的部分如中心区域进行全精度边缘检测其他区域使用低精度或直接关闭。5.3 混合方案专属问题问题Custom Depth描边与全局Scene Depth描边重叠、错位或闪烁。原因Custom Depth的渲染时机和默认的Scene Depth可能略有不同或者深度值范围不一致。解决深度值统一确保在着色器中对两者使用相同的线性化深度转换公式。优先级处理在着色器逻辑中明确优先级。例如当Custom Depth检测到边缘报警设备时直接覆盖或混合掉该位置的全局描边结果避免双重描边。检查渲染顺序确保报警设备在渲染Custom Depth时其深度写入是正确的没有被其他透明物体干扰。描边效果的实现从简单的功能到生产级的稳定、高效应用中间隔着无数个细节和权衡。没有一种方案是银弹最好的方案永远是贴合你项目具体需求、经过充分测试和优化的那一个。理解原理大胆尝试细致调试你就能掌控这条“线”让它为你的场景赋予清晰的空间层次和精准的视觉引导。