ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

虚幻引擎高亮插件HighLightActors:Custom Depth与Stencil实现原理及魔改指南

虚幻引擎高亮插件HighLightActors:Custom Depth与Stencil实现原理及魔改指南 在虚幻引擎项目里做交互反馈物体高亮几乎是绕不开的一环。不管是做关卡编辑器工具、做拾取提示还是做战术射击里的敌人轮廓你都得让某个Actor在场景里亮起来。市面上的方案大致分两派一派是改材质、加描边、走后期效果炫但接入成本高另一派是直接改渲染状态简单粗暴但容易和项目里已有的材质打架。HighLightActors这个插件走的是第二条路而且它把源码完全摊开给你看纯免费、无加密、无授权校验拿来就能改。这篇文章我会从它到底解决了什么问题讲起一路拆到C实现细节、渲染管线里的位置、实际接入时的坑以及怎么按自己的项目需求去魔改它。适合已经会用UE编辑器、能看懂基础C、但还没深入过渲染层自定义的开发者。1. 高亮这件事为什么自己写比找现成的更划算1.1 高亮需求的三种典型场景与它们的真实差异很多人一上来就问有没有现成的高亮插件但高亮这个词在不同场景下指的根本不是同一件事。我把它拆成三类你对号入座一下。第一类是编辑器工具型高亮。比如你在做一个批量选择工具选中一批Actor后希望它们在视口里明显区分出来。这种高亮只在编辑器里生效不进运行时对性能几乎没要求但对不干扰原有显示要求很高——你不能把美术调好的材质给覆盖了。第二类是运行时交互型高亮。玩家准星扫过可交互物体物体边缘亮一圈或者拾取道具时道具闪一下。这种高亮要进游戏循环每帧可能切换目标性能敏感而且要和后处理、抗锯齿、透明物体共存。第三类是战术信息型高亮。比如队友轮廓、敌人透视、任务目标标记。这类高亮往往需要穿透墙体可见或者只显示轮廓不显示内部对渲染层的要求最高通常要动Custom Depth和Stencil。HighLightActors这个插件主要覆盖的是第一类和第二类它的核心思路是通过修改Actor下所有PrimitiveComponent的渲染状态来实现高亮而不是去替换材质。这个选择直接决定了它的优点和局限后面会详细展开。1.2 为什么改材质这条路在通用插件里走不通你可能会想高亮不就是换个发光材质吗理论上没错但放到通用插件里就是灾难。原因很简单你永远不知道用户的Actor上挂的是什么材质。一个静态网格可能用了带顶点动画的材质一个骨骼网格可能用了带布料模拟的材质一个粒子系统可能用了带深度写入的材质。你强行替换成自己的高亮材质轻则丢失原有视觉效果重则直接报错崩溃。更麻烦的是材质实例。UE里大量材质是MaterialInstanceDynamic运行时动态创建的你替换了基础材质这些动态参数全部失效。我见过有项目的高亮插件把角色的皮肤材质替换掉之后角色直接变成了纯色块就是因为没处理好材质实例的继承关系。所以HighLightActors选择了一条更保守的路不动材质只动渲染状态。具体来说它通过设置组件的RenderCustomDepth和CustomDepthStencilValue配合后期材质来实现高亮。这个方案的好处是无论原材质多复杂高亮层都是叠加在上面的不会破坏原有渲染。1.3 纯开源免费这件事在UE生态里的实际价值UE生态里插件收费是常态一个功能完整的高亮插件卖几十美元很常见。但收费插件有个致命问题你没法改。项目需求千奇百怪你可能需要高亮时带脉冲动画可能需要根据距离改变高亮颜色可能需要高亮只对特定摄像机可见。这些需求收费插件要么不支持要么得等作者更新。HighLightActors完全开源意味着你可以直接翻源码找到对应的函数改成你要的逻辑。我实际用下来它的代码结构相当清晰核心逻辑集中在几个文件里没有过度抽象改起来不费劲。而且纯免费意味着你可以在商业项目里随便用不用担心授权问题——当然具体还是要看它采用的许可证类型建议用之前确认一下。2. 插件核心机制Custom Depth与Stencil的配合逻辑2.1 Custom Depth到底是什么为什么它能实现高亮要理解这个插件必须先理解Custom Depth。UE的渲染管线里默认会渲染一遍场景深度用于遮挡计算。但有时候你需要额外渲染一遍特定物体的深度用于特殊效果——这就是Custom Depth。你可以把它想象成主深度图是所有人都画一遍Custom Depth是只有我指定的人再画一遍。画完之后后期处理阶段可以拿到这张额外的深度图做各种文章。比如你可以判断这个像素在Custom Depth里有值说明它属于被标记的物体然后给它加颜色。HighLightActors做的就是把需要高亮的Actor下所有PrimitiveComponent的CustomDepth打开。就这么简单。打开之后这些物体就会出现在Custom Depth图里后期材质就能识别到它们。但光有Custom Depth还不够因为如果场景里有多个高亮组你需要区分它们。这时候Stencil就派上用场了。2.2 Stencil值给高亮分组和分色的关键Stencil是Custom Depth的一个附加通道每个像素除了深度值还能存一个0-255的整数值。你可以给不同的高亮组分配不同的Stencil值后期材质里根据这个值来决定用什么颜色、什么效果。HighLightActors里对Stencil的处理是这样的它允许你为每个高亮请求指定一个Stencil值默认是1。如果你同时高亮多组物体可以给它们不同的Stencil值然后在后期材质里写分支逻辑。这里有个实际项目里很容易踩的坑Stencil值不是随便用的。UE自身和一些插件也会占用Stencil值比如某些描边效果、某些自定义深度写入。如果你随便设一个值可能和已有的效果冲突。我的建议是先梳理项目里所有用到Custom Depth Stencil的地方然后给你的高亮插件分配一个专属区间比如200-210避免撞车。2.3 后期材质如何读取这些信息并输出高亮插件本身只负责标记物体真正让物体看起来亮了的是后期材质。HighLightActors通常会附带一个示例后期材质但你要理解它的原理才能改。后期材质里做高亮的基本流程是这样的采样Custom Depth纹理拿到当前像素的Custom Depth值。采样Custom Stencil纹理拿到当前像素的Stencil值。判断Stencil值是否属于高亮组。如果是根据Stencil值选择颜色和场景颜色混合。如果不是输出原场景颜色。听起来简单但实际写起来有几个细节要注意。比如Custom Depth的采样要用SceneTexture:CustomDepth节点Stencil要用SceneTexture:CustomStencil节点。还有高亮边缘的处理——如果你只判断当前像素是否在高亮物体上那高亮就是实心的没有轮廓感。想要轮廓感需要判断当前像素不在高亮物体上但周围像素在这就要做邻域采样成本会高一些。HighLightActors的示例材质我印象里是做了轮廓效果的它通过比较当前像素和周围像素的Custom Depth差异来提取边缘。这个思路和常见的描边做法一致但因为它是在后期做的所以能穿透物体显示——这正是战术高亮需要的效果。3. 从零接入HighLightActors环境准备与第一个高亮3.1 插件获取与项目放置的正确姿势拿到插件源码后第一步是放到正确的位置。UE插件的标准位置是项目目录下的Plugins文件夹。如果项目还没有这个文件夹手动建一个。把HighLightActors整个文件夹拷进去之后目录结构应该是这样的YourProject/ Plugins/ HighLightActors/ HighLightActors.uplugin Source/ HighLightActors/ Public/ Private/ Content/这里有个细节如果你是从Git仓库克隆的注意检查.uplugin文件里的引擎版本号。有些插件会写死一个版本比如EngineVersion: 5.3.0如果你的引擎是5.4可能会提示版本不匹配。大部分情况下可以直接忽略继续但如果插件用到了版本相关的API就可能编译报错。这时候你需要手动改.uplugin里的版本号或者改源码适配。放好之后重新生成项目文件右键.uproject文件选Generate Visual Studio project files然后编译。编译通过后启动编辑器在Plugins面板里应该能看到HighLightActors已经启用。3.2 在C里调用高亮接口的最小示例插件启用后你需要在代码里调用它的接口。HighLightActors通常会提供一个UHighLightActorsSubsystem或者类似的蓝图函数库。具体接口名以你拿到的源码为准但调用逻辑大同小异。假设它提供了一个HighlightActor函数接收Actor指针和颜色参数调用方式大概是这样// 在你的某个Actor或Component里 #include HighLightActors.h void AMyInteractableActor::OnFocusBegin() { if (UHighLightActorsSubsystem* HighlightSubsystem GetWorld()-GetSubsystemUHighLightActorsSubsystem()) { HighlightSubsystem-HighlightActor(this, FLinearColor::Yellow, 1); } } void AMyInteractableActor::OnFocusEnd() { if (UHighLightActorsSubsystem* HighlightSubsystem GetWorld()-GetSubsystemUHighLightActorsSubsystem()) { HighlightSubsystem-UnhighlightActor(this); } }如果你不想写C插件一般也会暴露蓝图节点。在蓝图里搜索HighLight应该能找到对应的函数。蓝图调用的好处是快速验证坏处是性能略差而且不好做复杂的条件判断。这里我要提醒一个实际项目里常见的错误在Actor的BeginPlay里直接调用高亮然后忘了在EndPlay里取消。如果这个Actor被销毁了但高亮状态没清理Custom Depth的标记可能还留在渲染队列里导致后续帧出现莫名其妙的残留高亮。所以配对调用是必须的最好封装成一个RAII风格的辅助类构造时高亮析构时取消。3.3 验证高亮是否生效的三个检查点调用完接口后怎么确认高亮真的生效了我一般按这三个检查点来排查。检查点一Custom Depth有没有被打开。在编辑器里选中被高亮的Actor看它的PrimitiveComponent的Rendering设置里Render CustomDepth Pass是不是勾上了。如果是运行时调用的可以用控制台命令r.CustomDepth 3来强制开启Custom Depth默认可能是关闭的然后看场景里有没有变化。检查点二后期材质有没有挂上。高亮效果依赖后期材质如果后期材质没挂到PostProcessVolume上或者材质的Blendable Location设错了高亮就不会显示。检查PostProcessVolume的Rendering Features里Post Process Materials数组里有没有你的高亮材质。检查点三Stencil值有没有冲突。如果Custom Depth开了、后期材质也挂了但高亮颜色不对或者时有时无大概率是Stencil值冲突。把Stencil值改成一个没人用的值再试。这三个检查点覆盖了90%的高亮不生效问题。剩下的10%通常是材质逻辑写错了或者渲染线程和游戏线程的同步问题那个就要看具体日志了。4. 实际项目里最容易翻车的几个地方4.1 高亮和半透明物体的渲染顺序冲突半透明物体在UE里的渲染顺序本来就复杂加上Custom Depth之后更容易出问题。具体表现是半透明物体高亮后要么高亮被半透明自身遮挡要么半透明物体穿透了高亮边缘看起来像破了个洞。根本原因是Custom Depth的写入时机和半透明渲染的时机不匹配。不透明物体先渲染写深度半透明物体后渲染不写深度默认。Custom Depth是在不透明渲染之后、半透明渲染之前写入的。所以如果你的高亮物体本身是半透明的它的Custom Depth写入和它的颜色渲染是分开的后期材质拿到Custom Depth时半透明颜色还没画上去。解决方案有两个。一是把半透明物体的材质改成Masked让它参与不透明渲染这样Custom Depth和颜色就同步了。二是接受这个限制半透明物体不做轮廓高亮只做整体染色。我一般选第二个因为改材质影响太大。4.2 骨骼网格高亮时的蒙皮更新延迟骨骼网格的高亮有个隐蔽的坑Custom Depth用的是上一帧的蒙皮结果。因为Custom Depth的渲染是在游戏线程的蒙皮计算之前还是之后取决于引擎版本和具体设置。在某些版本里你会看到高亮轮廓比角色本身慢一帧快速移动时特别明显。这个问题没有完美的解决方案只能缓解。缓解方法是把高亮物体的Custom Depth写入提前或者接受这一帧的延迟。对于大多数游戏来说一帧延迟肉眼几乎看不出来除非你做的是高速动作游戏。我在一个项目里遇到过更极端的情况角色做快速旋转时高亮轮廓和角色本体完全错位。后来发现是那个角色的骨骼网格开了Update Mesh in Render Thread之类的选项导致蒙皮结果和Custom Depth不同步。关掉那个选项就好了但性能会略降。所以这是个取舍。4.3 大量物体同时高亮时的性能开销Custom Depth不是免费的。每多一个物体写入Custom Depth就多一次Draw Call。如果你同时高亮几百个物体Draw Call数量会飙升帧率直接掉下来。HighLightActors本身没有做批量优化它就是逐个组件设置标记。所以如果你的场景里需要大量高亮比如RTS游戏里框选一百个单位你得自己做优化。优化思路有几个。一是合并如果这些物体用的是同一个网格和材质可以用Instanced Static Mesh来渲染Custom Depth也会合并成一次Draw Call。二是分级距离远的物体不做高亮或者用更低成本的方式比如屏幕空间的标记代替。三是限制数量只高亮最近的N个物体超出的用其他方式提示。我实测下来同时高亮50个左右的普通静态网格在主流显卡上帧率影响在2-3帧以内可以接受。超过200个就明显了得想办法。5. 按项目需求魔改插件的几个方向5.1 增加高亮脉冲动画默认的高亮是静态的颜色不变。但很多游戏需要脉冲效果比如呼吸灯一样一闪一闪。这个改起来不难因为高亮颜色是在后期材质里决定的你只需要在材质里加一个基于时间的正弦波去调制颜色强度。具体做法在后期材质里加一个Time节点乘以一个频率参数然后Sin再Remap到0.5-1.0的范围最后乘到高亮颜色上。这样高亮就会周期性地变亮变暗。如果你想让脉冲频率可控可以把这个频率参数暴露成材质参数然后在C里通过动态材质实例来设置。HighLightActors的后期材质如果是Material Instance Dynamic创建的你就能在代码里拿到它并改参数。5.2 支持按距离渐变的高亮强度另一个常见需求是物体越远高亮越弱。这个也是在后期材质里做。你需要拿到当前像素的世界位置然后和摄像机位置算距离根据距离来调制高亮透明度。世界位置可以从SceneTexture:WorldNormal或者SceneTexture:PostProcessInput0反推但更简单的做法是用SceneTexture:SceneDepth拿到深度深度本身就隐含了距离信息。把深度归一化之后映射到高亮强度上就行。这个效果在开放世界游戏里特别有用远处的高亮不会太刺眼近处的又足够明显。5.3 让高亮只对特定摄像机可见有些项目需要分屏或者需要主摄像机看不到高亮但小地图摄像机能看到。这个需求用Custom Depth的默认方案做不到因为Custom Depth是全局的所有摄像机共享。要实现按摄像机区分你得用Custom Depth Stencil的更多位或者干脆用另一套机制比如在渲染目标里单独画一遍高亮物体。这个改动就比较大了超出了HighLightActors的原始设计范围。如果你真有这个需求建议评估一下是否值得因为分屏项目本身就不多。6. 和其他高亮方案的对比与选型建议6.1 对比材质替换方案材质替换方案的优势是效果可控你可以做出非常炫的高亮比如流动的光效、复杂的描边。劣势是通用性差遇到复杂材质就歇菜。HighLightActors的Custom Depth方案优势是通用性强不管什么材质都能高亮而且不破坏原材质。劣势是效果相对朴素主要就是轮廓和染色做不出太花哨的东西。选型建议如果你的项目里物体材质比较统一而且你对高亮效果有很高的定制要求材质替换方案可能更合适。如果你的项目材质五花八门或者你希望高亮功能即插即用Custom Depth方案更稳。6.2 对比引擎自带的HighResScreenshot高亮UE编辑器里有个HighResScreenshot功能可以高亮特定Actor。但那是编辑器功能运行时用不了。而且它的高亮是画在截图上的不是实时的。所以它和HighLightActors不是一个层面的东西没法直接对比。6.3 什么情况下不建议用这个插件如果你的项目满足以下任一条件我建议慎重考虑需要高亮的物体数量极大超过500个同时且没有做合批优化。需要高亮半透明物体且对效果要求很高。需要按摄像机区分高亮可见性。项目用的是非常老的UE版本比如4.20以下插件可能不兼容。这些情况下你可能需要自己写一套更定制化的方案或者组合多种方案。7. 一些实际调试时积累的小技巧调试高亮效果时控制台命令是你的好朋友。r.CustomDepth 3可以强制开启Custom Depth方便你在没有后期材质的情况下先确认标记有没有生效。r.CustomDepthTemporalAAJitter 0可以关掉Custom Depth的TAA抖动让边缘更锐利但可能会有锯齿。还有一个技巧在后期材质里临时把高亮颜色设成纯红或纯绿饱和度拉满这样一眼就能看出哪些像素被高亮了。确认逻辑没问题之后再改成项目需要的颜色。如果你发现高亮边缘有闪烁大概率是TAA在作怪。Custom Depth的边缘和场景边缘的TAA处理方式不同导致边缘像素在帧间跳变。缓解方法是把高亮边缘做一点羽化或者在高亮区域禁用TAA但UE不支持按区域禁用TAA所以只能羽化。最后记得在打包之前检查一下Custom Depth的设置有没有被正确包含。有些项目在编辑器里高亮正常打包后失效就是因为打包配置里把Custom Depth相关的Shader给剔除了。检查Project Settings里的Packaging选项确保Custom Depth Stencil的Shader被包含在打包中。这个插件我前后在三个项目里用过从小的工具项目到中等规模的游戏项目都有。它的代码不复杂但胜在稳定和可改。如果你正好需要一个高亮方案又不想被收费插件绑死花半天时间读一遍它的源码绝对值得。
返回列表