资讯详情

资讯详情

UE 高亮插件 HighLightActors:基于 Custom Depth Stencil 的 Actor 描边方案

1. 为什么我要自己写一个高亮插件在 Unreal Engine 项目里做交互开发尤其是涉及编辑器工具、关卡设计辅助或者调试可视化的时候物体高亮几乎是一个绕不开的需求。你可能想快速定位某个 Actor想在编辑器里一眼看出哪些对象被选中了或者想在运行时给玩家一个明确的视觉反馈。UE 本身提供了自定义深度模板Custom Depth/Stencil这套机制但真到用的时候你会发现从零搭一套稳定、可复用、不污染项目代码的高亮方案其实要踩不少坑。HighLightActors 这个插件就是我在反复折腾这些需求之后沉淀下来的一个完全开源、纯免费的解决方案。它的核心目标很明确用最少的接入成本给任意 Actor 加上可配置的高亮描边效果同时保证在编辑器视口和运行时 PIE 里都能正常工作。它不依赖任何第三方库纯 C 实现基于 UE 的 Custom Depth Stencil 通道做渲染性能开销可控适合中小型项目直接拿来用也适合想学习 UE 渲染管线接入方式的开发者参考。这篇文章我会从设计思路、核心原理、代码结构、实操接入、参数调优到常见问题排查完整拆一遍。如果你正在做 UE 的编辑器工具链、关卡交互或者调试可视化这个插件应该能帮你省下不少时间。即使你只是想搞清楚 UE 里高亮描边到底是怎么实现的下面的内容也能让你少走弯路。2. 高亮描边的核心原理与方案选型2.1 为什么选 Custom Depth Stencil 而不是后处理材质硬写UE 里实现物体描边高亮常见路子有这么几条一是用后处理材质配合场景深度做边缘检测二是用 Custom Depth Stencil 标记目标物体再在后处理里读取三是直接改材质做发光或者叠加。第一种方案对全屏做边缘检测容易把场景里所有物体的边缘都描出来想只高亮特定对象就得额外做遮罩逻辑复杂且性能不划算。第三种方案侵入性太强每个要高的物体都得改材质维护成本高。Custom Depth Stencil 的优势在于它是 UE 原生支持的逐物体标记机制。你只需要把目标 Actor 的 Mesh 组件设置SetRenderCustomDepth(true)再指定一个 Stencil 值后处理材质里就能通过SceneTexture:CustomStencil精确读取到这个值从而只对标记过的像素做描边处理。这样高亮逻辑和物体本身的材质完全解耦想高亮谁就标记谁取消也只是一行代码的事。HighLightActors 正是基于这套机制。插件内部封装了 Stencil 值的分配、Mesh 组件的遍历、后处理材质的动态加载和参数传递对外只暴露简单的接口比如HighlightActor(Actor)和UnHighlightActor(Actor)。你不需要关心底层 Stencil 怎么分配也不需要手动去挂后处理体积。2.2 Stencil 值分配策略与冲突规避Custom Stencil 是一个 0 到 255 的整数值UE 默认把 0 当作“未标记”。理论上你可以给每个高亮对象分配不同的 Stencil 值然后在后处理里根据值做不同颜色的描边。但实际项目里Stencil 通道可能已经被其他系统占用了比如某些描边插件、选中反馈、特殊渲染效果。如果大家各用各的很容易冲突。我的做法是在插件里维护一个 Stencil 值池默认从某个高位段开始分配比如 200 到 255尽量避开常见系统占用的低位段。插件初始化时会扫描当前场景里已经使用了 Custom Stencil 的组件记录已占用的值然后从池子里取空闲的分配。释放时把值归还池子保证复用。这样即使项目里还有其他系统在用 Stencil也能最大程度减少冲突。注意如果你的项目里已经有其他系统在用 Custom Stencil建议先确认它们占用的值范围然后在插件配置里把起始值调到不重叠的区间。这个配置我放在了插件设置里改起来很方便。2.3 编辑器视口与运行时的一致性处理很多高亮方案在编辑器里看着没问题一进 PIE 就失效或者反过来。原因通常是后处理体积的挂载方式不对或者渲染标记没有在正确的时机设置。HighLightActors 的做法是区分编辑器世界和运行世界分别管理后处理体积的创建和销毁。在编辑器视口里插件会往编辑器世界场景里动态添加一个后处理体积范围覆盖整个视口保证编辑器里也能看到高亮效果。运行时则在当前 Game World 里创建对应的后处理体积跟随玩家相机或者固定在场景里。两者的后处理材质是同一套只是挂载的 World 不同。这样无论你在编辑器里调试还是打包后运行高亮表现都是一致的。3. 插件代码结构与核心模块拆解3.1 模块划分与文件组织HighLightActors 的代码结构尽量保持扁平方便阅读和二次修改。主要分为这几个部分HighLightActors.uplugin插件描述文件声明模块、依赖和加载阶段。Source/HighLightActors/核心模块目录包含 Public 和 Private 两个子目录。Public/HighLightActorsSubsystem.h对外暴露的子系统接口负责高亮管理的入口。Public/HighLightActorTypes.h定义高亮配置结构体、枚举和委托。Private/HighLightActorsSubsystem.cpp子系统实现包含 Stencil 分配、Actor 标记、后处理体积管理等核心逻辑。Private/HighLightActorPostProcess.cpp后处理材质的动态加载和参数设置。Content/存放后处理材质资源插件自带一套默认描边材质。这种划分的好处是接口和实现分离使用者只需要包含 Public 头文件不需要关心 Private 里的实现细节。如果你要扩展功能比如增加不同颜色的高亮也只需要在 Public 接口上做文章底层逻辑不用大改。3.2 子系统设计为什么用 WorldSubsystemUE 的 Subsystem 机制是管理全局逻辑的好工具。HighLightActors 选择继承UWorldSubsystem而不是 GameInstanceSubsystem 或者 EditorSubsystem原因是高亮逻辑和 World 强相关。每个 World 有自己的场景、自己的 Actor、自己的后处理体积用 WorldSubsystem 可以天然地做到按 World 隔离编辑器世界和运行世界互不干扰。子系统在Initialize阶段会做几件事加载默认的后处理材质、初始化 Stencil 值池、注册 World 的 Actor 销毁回调用于自动清理已销毁 Actor 的高亮状态。在Deinitialize阶段则负责释放所有资源、销毁后处理体积、归还 Stencil 值。这样即使 World 切换或者编辑器关闭也不会留下悬挂的渲染标记。3.3 后处理材质的动态加载与参数传递插件自带的后处理材质是一个基于 Custom Stencil 的描边材质。它的核心逻辑是采样SceneTexture:CustomStencil如果当前像素的 Stencil 值在有效范围内就根据周围像素的 Stencil 差异计算边缘然后叠加描边颜色。材质里暴露了几个参数描边颜色、描边宽度、是否启用发光、发光强度。动态加载方面插件在初始化时通过LoadObject加载材质资源然后创建动态材质实例MID把 MID 设置到后处理体积的WeightedBlendables里。每次高亮配置变化时只需要更新 MID 的参数不需要重新创建材质实例。这样性能开销很小切换高亮颜色几乎是即时的。实操心得如果你要自定义描边效果建议直接复制插件自带的材质改完之后在插件设置里指向你的材质路径。不要直接改插件原始材质否则插件更新时你的修改会被覆盖。4. 完整接入流程与实操步骤4.1 插件安装与项目配置拿到插件后第一步是把它放到项目的Plugins目录下。如果项目还没有 Plugins 目录手动建一个。目录结构应该是YourProject/Plugins/HighLightActors/里面包含HighLightActors.uplugin和Source、Content等文件夹。放好之后右键项目文件生成工程文件或者直接打开编辑器。UE 会自动检测到新插件并提示编译。编译通过后在编辑器的插件列表里启用 HighLightActors重启编辑器生效。如果你用的是 C 项目还需要在项目的Build.cs里添加对 HighLightActors 模块的依赖PublicDependencyModuleNames.AddRange(new string[] { Core, CoreUObject, Engine, HighLightActors });这一步很多人会忘结果代码里 include 头文件报找不到。加上之后重新生成工程文件即可。4.2 在 C 中调用高亮接口插件对外的主要接口都在UHighLightActorsSubsystem里。获取子系统的方式是UHighLightActorsSubsystem* HighlightSubsystem GetWorld()-GetSubsystemUHighLightActorsSubsystem();拿到之后就可以调用高亮和取消高亮// 高亮一个 Actor HighlightSubsystem-HighlightActor(MyActor); // 取消高亮 HighlightSubsystem-UnHighlightActor(MyActor); // 取消所有高亮 HighlightSubsystem-UnHighlightAllActors();如果你需要自定义描边颜色和宽度可以用带配置参数的版本FHighLightConfig Config; Config.Color FLinearColor::Red; Config.Width 3.0f; Config.bEnableGlow true; HighlightSubsystem-HighlightActor(MyActor, Config);配置结构体里还支持设置优先级当同一个 Actor 被多次高亮时优先级高的配置会覆盖低的。这个在复杂交互场景里很有用比如选中高亮和警告高亮同时存在时警告应该优先显示。4.3 在蓝图中使用高亮功能不是所有项目都方便写 C所以插件也暴露了蓝图接口。你可以在蓝图里通过Get World Subsystem节点拿到 HighLightActorsSubsystem然后调用对应的函数。蓝图节点的参数和 C 接口一一对应配置结构体在蓝图里也可以直接编辑。一个常见的用法是在 Actor 的BeginPlay里注册高亮在EndPlay里取消。或者在做交互检测时射线打到的 Actor 调用高亮离开时取消。蓝图里操作起来很直观不需要写一行代码。注意蓝图里调用子系统接口时确保传入的 Actor 是有效的并且它的 Mesh 组件已经注册到场景里。如果 Actor 还没 BeginPlay 或者 Mesh 还没创建高亮可能会失败。建议在 Actor 初始化完成之后再调用。4.4 编辑器视口高亮的启用方式编辑器视口里的高亮默认是开启的但如果你发现编辑器里看不到效果先检查这几个地方一是插件的编辑器模式是否启用二是后处理体积是否被正确添加到编辑器世界三是当前视口是否开启了后处理显示。在编辑器视口的显示设置里确保Post Processing是勾选的。如果你只想在运行时高亮不想在编辑器里看到可以在插件设置里关掉编辑器视口支持。这样插件就不会往编辑器世界添加后处理体积减少不必要的开销。5. 参数调优与视觉效果打磨5.1 描边宽度与分辨率的适配关系描边宽度这个参数很多人直接给个固定值结果在不同分辨率下表现不一致。原因是后处理材质里的描边计算是基于屏幕像素的分辨率越高同样的像素宽度看起来越细。我的建议是把描边宽度和屏幕高度关联起来比如宽度值乘以ViewSize.y / 1080这样在 1080p 和 4K 下视觉粗细基本一致。插件里默认做了这个适配但如果你自己改材质记得把这个逻辑加进去。具体做法是在后处理材质里用ViewSize节点获取当前视口尺寸然后和基准分辨率做比值乘到采样偏移上。5.2 颜色选择与场景对比度高亮颜色不是随便选一个亮色就行。如果场景本身很亮你用亮黄色描边可能根本看不出来。如果场景偏暗用深色描边也会糊成一团。我的经验是先看场景的主色调然后选一个对比度高的颜色。比如森林场景偏绿用橙红或者品红描边就很跳工业场景偏灰用青色或者亮黄效果不错。另外描边颜色最好带一点自发光这样在暗处也能看清。插件里的bEnableGlow和GlowIntensity就是干这个的。发光强度不要太高否则会过曝一般 1.5 到 2.5 之间比较合适。5.3 多对象高亮时的性能考量同时高亮大量对象时性能主要花在后处理的边缘检测上。因为后处理是全屏的不管高亮一个还是十个对象全屏采样的开销是一样的。真正增加的开销在于 Custom Depth 的渲染每个被标记的 Mesh 都要多渲染一遍深度。如果高亮几百个高面数模型深度渲染的开销会明显上升。优化思路有几个一是限制同时高亮的对象数量比如只高亮视野内的二是对于远处的小物件可以降低高亮优先级或者直接不高亮三是如果项目允许可以把 Custom Depth 的分辨率调低减少深度渲染的像素量。插件里提供了最大高亮数量的配置超过之后会自动取消最早的高亮避免无限增长。6. 常见问题排查与避坑指南6.1 高亮不显示或显示异常这是最常见的问题排查顺序可以按下面这个表来现象可能原因排查方法完全看不到高亮后处理体积未创建检查 World 里是否有 HighLightActors 的后处理体积高亮闪烁或不稳定Stencil 值冲突查看是否有其他系统占用了相同 Stencil 值描边颜色不对MID 参数未更新确认配置变更后调用了 UpdateConfig编辑器里正常PIE 里没有后处理体积挂载 World 错误检查运行时 World 的后处理体积是否存在只有部分 Mesh 高亮组件遍历不完整确认 Actor 下所有 PrimitiveComponent 都被标记我遇到最多的是 Stencil 值冲突。项目里如果有其他描边或者选中效果它们可能也在用 Custom Stencil。解决办法是先把其他系统的 Stencil 占用范围搞清楚然后把插件的起始值调到不重叠的区间。插件设置里可以改这个起始值改完重启编辑器生效。6.2 打包后高亮失效的处理编辑器里好好的一打包就失效通常是因为后处理材质没有被正确打包。UE 在打包时只会包含被引用的资源如果插件是通过代码动态加载材质路径而该路径没有被任何地方硬引用打包时可能会被忽略。解决办法是在插件设置里把材质路径配置成一个硬引用或者在插件的启动模块里用ConstructorHelpers或者FSoftObjectPath显式引用一下。另外确保插件的 Content 目录被正确包含在打包范围内。可以在打包设置里检查一下插件资源的包含规则。6.3 与其他渲染插件的兼容性如果你的项目里还有其他改渲染管线的插件比如自定义后处理、描边、抗锯齿替换等可能会和 HighLightActors 冲突。冲突点主要在两个方面一是后处理体积的 Blendable 顺序二是 Custom Stencil 的占用。后处理体积的 Blendable 是有顺序的如果两个插件都往同一个后处理体积里加 Blendable顺序不同可能导致效果叠加异常。HighLightActors 默认创建自己的后处理体积不和其他插件共用这样能减少冲突。但如果你的项目强制要求所有后处理走同一个体积就需要手动调整 Blendable 的插入位置。Custom Stencil 的冲突前面说过了这里再强调一下如果两个系统用了相同的 Stencil 值后处理材质读取到的值就是一样的会导致不该高亮的物体也被描边。所以 Stencil 值池的管理很重要插件里做了自动分配但前提是其他系统也遵守类似的分配规则。如果其他系统是硬编码的你就得手动避让。6.4 移动端和主机平台的注意事项Custom Depth Stencil 在移动端和主机平台上的支持情况不太一样。移动端默认可能不开启 Custom Depth需要在项目设置里手动启用。具体路径是Project Settings - Rendering - Mobile - Enable Custom Depth。启用之后会增加一定的渲染开销需要根据目标机型评估。主机平台一般支持没问题但要注意后处理材质的复杂度。主机上后处理是全屏的如果材质指令数太多可能会成为性能瓶颈。建议在主机平台上简化描边算法比如减少采样次数或者用更低精度的计算。实操心得在移动端上如果高亮对象不多可以考虑不用后处理直接用材质发光或者叠加一个高亮 Mesh 来替代。虽然后处理效果更好但移动端的性能预算很紧能省则省。7. 扩展思路与二次开发建议7.1 增加多种高亮样式插件默认只提供一种描边样式但实际项目里可能需要多种比如选中高亮、警告高亮、任务目标高亮每种颜色和粗细不同。扩展方式很简单在FHighLightConfig里增加一个样式枚举然后在后处理材质里根据 Stencil 值或者额外的参数通道来区分样式。具体做法是给每种样式分配不同的 Stencil 值段后处理材质里根据 Stencil 值范围选择不同的颜色和宽度。这样一次后处理就能处理多种样式不需要多个后处理体积。缺点是 Stencil 值段有限样式太多会不够用。如果样式超过五六种建议改用额外的参数纹理来传递样式信息。7.2 与选中系统、交互系统的联动高亮很少单独存在通常和选中、交互、任务系统联动。比如玩家看向某个物体时高亮按下交互键后高亮变成另一种颜色任务完成后高亮消失。这些逻辑都可以在子系统之上封装一层管理器来实现。我的建议是把高亮状态和业务状态分开管理。高亮子系统只负责“谁被高亮了、什么样式”业务系统负责“什么时候高亮、什么时候取消”。两者通过事件或者接口通信不要耦合在一起。这样业务逻辑变化时高亮部分不用改。7.3 性能分析与优化方向如果项目里高亮用得很多建议用 Unreal Insights 或者 Stat 命令看一下开销。重点看PostProcessing和CustomDepth这两项。如果 CustomDepth 开销大说明高亮对象太多或者面数太高需要做裁剪。如果 PostProcessing 开销大说明后处理材质太重需要简化算法。一个实用的优化是给高亮对象做距离裁剪超过一定距离就不标记 Custom Depth。这个逻辑可以在子系统里做每帧或者每隔几帧检查一次高亮对象和相机的距离超出范围就临时取消标记进入范围再重新标记。这样远处的高亮不参与深度渲染能省不少性能。7.4 源码阅读与学习建议如果你想通过这个插件学习 UE 的渲染接入建议按这个顺序读代码先看HighLightActorsSubsystem.h了解对外接口然后看HighLightActorsSubsystem.cpp里的HighlightActor和UnHighlightActor实现理解 Stencil 分配和组件标记的逻辑。接着看HighLightActorPostProcess.cpp理解后处理体积和 MID 的创建过程。最后看后处理材质的节点图理解描边算法。读的时候重点关注几个问题Stencil 值是怎么分配和回收的后处理体积是在哪个 World 创建的MID 参数是怎么传递的Actor 销毁时高亮状态是怎么清理的。把这几个问题搞清楚你对 UE 的 Custom Depth 高亮方案就基本通了。这个插件我一直在自己的项目里用从 UE4 到 UE5 都跑过稳定性没问题。代码量不大但该有的都有适合直接拿来用也适合当学习材料。如果你在使用中遇到问题或者有更好的实现思路欢迎一起交流。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →