Files
XCEngine/docs/used/Scene选中描边彻底修复计划_2026-04-08.md

292 lines
8.5 KiB
Markdown
Raw Blame History

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