8.5 KiB
Scene 选中描边彻底修复计划
日期:2026-04-08
1. 文档定位
本文档只解决一个问题:
Scene面板里,选中对象的描边效果需要达到稳定、可扩展、接近商业编辑器的质量。
本文档不讨论:
- 层级面板右键菜单
- 灯光 gizmo 图标
- 运行时高亮
- 游戏内选中反馈
2. 当前问题结论
当前实现不是“可见外轮廓提取”,而是“基于 object-id 的屏幕空间膨胀”。
现状链路如下:
- 主场景正常渲染。
BuiltinObjectIdPass把屏幕上最前面的物体 id 写入objectIdTexture。BuiltinObjectIdOutlinePass在全屏 shader 中检查当前像素周围是否存在“选中对象的 object-id”。- 只要邻域中存在选中 object-id,当前像素就会被涂成 outline。
这个算法的根本缺陷是:
- 它只知道“邻域里有没有选中对象”。
- 它不知道“当前像素前面是不是别的物体挡住了选中对象”。
- 它也不知道“这条边是可见外轮廓,还是选中大平面与前景遮挡物之间的内部接触边界”。
所以当选中一个大平面时,平面上方的 cube、sphere、capsule 这些前景物体边缘也会被错误描边。
这不是数据污染,也不是拾取错误,而是算法层面的必然结果。
3. 根因
根因可以归纳为两条:
3.1 描边输入选错了
当前描边 pass 直接消费 objectIdTexture。
但 objectIdTexture 的职责本质上是:
- 告诉编辑器“当前屏幕像素最前面的对象是谁”,用于 picking。
它不适合作为“选中描边”的唯一依据,因为它不包含:
- 选中对象的可见 mask
- 选中对象自己的深度
- 当前场景深度与选中对象深度之间的遮挡关系
3.2 描边算法缺少深度裁决
当前 outline shader 的判断规则是:
- 当前像素不是选中对象
- 但周围像素里存在选中对象
满足这两个条件就画 outline。
这套规则没有使用场景深度,也没有使用选中对象的可见性信息,因此无法区分:
- 真正应该画的外轮廓
- 不应该画的前景遮挡边界
4. 修复目标
本次彻底修复后的目标是:
- 选中大平面时,前景物体边缘不再被错误染成 outline。
- 选中普通 mesh 时,outline 仍然能稳定出现在可见外轮廓处。
- 被遮挡区域不画“伪外轮廓”。
- picking 与 selection outline 两套能力彻底解耦。
- 后续如果要做 Unity 风格的 x-ray 选中、被遮挡高亮、hover outline,可以在当前结构上继续扩展,而不是推倒重来。
5. 非目标
这次修复不做以下内容:
- 不做运行时游戏内高亮系统。
- 不做完整的“透视遮挡时仍显示淡色轮廓”的 x-ray 选中风格。
- 不借这次机会重写整个 editor render pipeline。
- 不继续在现有
object-id-outline.shader上做只针对单个 case 的临时补丁。
6. 总体方案
正确方案是把“拾取”和“描边”拆开,改成:
object id for pickingvisible selection mask for outlinedepth-aware outline composite
6.1 picking 继续走 object-id
现有 objectIdTexture 继续只用于:
- 场景点击选中
- object-id readback
它不再直接决定 outline 的视觉结果。
6.2 新增 selection mask pass
新增一张仅服务于 outline 的 selectionMaskTexture。
它的规则是:
- 只渲染当前选中的对象。
- 渲染时绑定主场景已有的深度缓冲。
- 依赖深度测试,只把“真正可见的 selected fragment”写进
selectionMaskTexture。
这样得到的不是 picking id 图,而是“选中对象可见区域 mask”。
6.3 scene viewport 暴露深度 SRV
为了做真正的 depth-aware composite,Scene viewport 需要同时持有:
depthViewdepthShaderView
也就是说,Scene viewport 的深度纹理既要能作为 DSV 使用,也要能在全屏 composite shader 中作为 SRV 读取。
底层 RHI 已经具备这条能力,阴影缓存已有相同用法,因此这不是架构冒险,而是 viewport 资源层尚未接线。
6.4 重写 outline composite pass
新的 outline shader 不再直接从 objectIdTexture 做邻域膨胀,而是读取:
selectionMaskTexturesceneDepthTexture
必要时预留:
selectionDepthTexture
新的裁决规则应为:
- 中心像素若属于 selected mask,本像素不直接着色。
- 若邻域存在 selected mask,则该像素可能位于 selected 的边界附近。
- 只有在深度关系证明“当前像素不是更近的前景遮挡物”时,才允许输出 outline。
这一步的本质是:
- 只画可见 selected 外轮廓。
- 不把前景遮挡物边界误判为 selected outline。
7. 具体实施步骤
Phase 1:补齐 viewport 资源
目标:
- Scene viewport 持有可采样深度。
- Scene viewport 持有单独的 selection mask render target。
需要修改:
editor/src/Viewport/ViewportHostRenderTargets.h- 相关 viewport resource reuse / destroy / create 流程
新增资源建议:
depthShaderViewselectionMaskTextureselectionMaskViewselectionMaskShaderViewselectionMaskState
验收标准:
- Scene viewport 创建/销毁/复用逻辑能完整覆盖新资源。
- 编译通过。
- 不影响现有 Scene 视图展示。
Phase 2:新增 selection mask pass
目标:
- 只把当前选中对象的可见区域写入 mask。
实现建议:
- 新建 editor 专用
SelectionMaskPass。 - 输入为选中对象 id 列表。
- 输出到
selectionMaskTexture。 - 渲染时绑定主场景同一套深度。
实现关键点:
- 这个 pass 的语义不是 picking。
- 它只关心“选中对象哪些像素当前可见”。
- 它不需要写 object id,只需要写统一 mask 值或选中组 id。
验收标准:
- 选中对象时可以输出稳定的可见 mask。
- 未选中时 pass 可被跳过。
Phase 3:重写 outline composite
目标:
- outline 只出现在选中对象的可见外轮廓。
实现建议:
- 保留全屏 pass 形式。
- 输入从
objectIdTexture改为selectionMaskTexture + depthShaderView。 - 邻域检查继续保留,但结果必须经过深度裁决。
最低正确规则:
- 中心像素若是别的前景几何,且深度显著近于 selected 边界,不画 outline。
- 中心像素若是背景或更远表面,可以画 outline。
验收标准:
- 选中大平面时,前景 cube/sphere/capsule 边缘不再被错误描边。
- 选中单个 mesh 时,外轮廓仍连续稳定。
Phase 4:移除旧耦合
目标:
- 彻底消除 “outline 直接依赖 object-id picking 纹理” 这一旧逻辑。
需要完成:
- 旧
object-id-outline.shader逻辑下线或重命名。 SceneViewportSelectionOutlinePass的输入契约改为新资源。- 保证
objectIdTexture仅服务于 picking。
验收标准:
- 代码层不再存在“outline 直接读 object id 邻域并膨胀”的核心路径。
8. 测试计划
至少补以下回归验证:
8.1 功能场景
- 选中大平面,前面摆放 cube / sphere / capsule:
- 前景物体边缘不能被错误描边。
- 选中单个 cube,背景是地面:
- outline 能稳定显示在 cube 可见外轮廓上。
- 选中被部分遮挡的物体:
- 只允许可见区域出现 outline。
- 多选相邻物体:
- outline 不串边,不污染第三方前景物体。
8.2 回归类型
- shader 资源加载测试
- render plan 接线测试
- pass 构建与资源存在性测试
- 视口级人工验证截图
9. 风险与边界
9.1 风险
- 仅使用
sceneDepth + selectionMask可能仍有少数边界 case 需要selectionDepth才能更稳。 - Scene viewport 新增 render target 后,资源管理和状态切换复杂度会上升。
- 若后续要兼容 Vulkan / OpenGL,需要同步确认深度 SRV 路径与资源状态语义。
9.2 边界
本计划的第一目标不是“做最炫的 outline”,而是:
- 先把语义做对
- 再把视觉做稳
只要还在复用 picking 的 objectIdTexture 直接做 outline,问题就不可能彻底解决。
10. 最终结论
这次 bug 不能靠继续修补当前 object-id-outline.shader 解决。
彻底方案必须满足三点:
- picking 与 outline 解耦
- 选中对象生成独立可见 mask
- outline composite 引入深度裁决
只有这样,Scene 视图选中描边才会从“屏幕空间膨胀特效”升级为“真正可用的编辑器轮廓系统”。