Archive completed scene viewport overlay rework plan

This commit is contained in:
2026-04-04 14:06:17 +08:00
parent 0c52245055
commit c0124443cf

View File

@@ -0,0 +1,631 @@
# Scene Viewport Overlay 与 Gizmo 正规化重构方案
日期:`2026-04-02`
## 0. 当前进度 Checkpoint
截至 `2026-04-02`,本方案已有以下落地结果:
- `Phase 1` 已完成:
- `CameraRenderRequest` 已新增 `overlayPasses`
- `CameraRenderer` 已在 builtin postprocess 之后执行 `overlayPasses`
- `ViewportHostService` 已接入 editor overlay pass sequence
- `Phase 2` 已完成首批迁移:
- `camera frustum`
- `directional light gizmo`
- `camera/light scene icon`
- 上述内容已不再走 ImGui world draw而是走 renderer overlay pass
- `scene icon` 的命中数据已开始收口:
- `SceneViewPanel` 不再自己扫描 scene 构建 icon draw data
- icon hit test 已改为消费 `SceneViewportOverlayBuilder::Build()` 产出的同类 frame data
- `transform gizmo` 的统一命中已开始接线:
- `SceneViewportEditorOverlayData.h` 已扩展为通用 `handleRecords`
- `SceneViewportOverlayHandleBuilder.h` 已可把 move/rotate/scale gizmo draw data 转为 canonical handle records
- `SceneViewPanel` 中 gizmo 的 hover / click-begin 已开始走统一 `HitTestSceneViewportOverlayHandles(...)`
- `transform gizmo` 的绘制迁移已开始接线:
- `SceneViewportEditorOverlayPass` 已支持 screen-space triangle primitive
- `ViewportHostService` 已可在 host 侧根据 `SceneViewPanel` 提交的 overlay 与 gizmo handle build inputs 构建 transient transform overlay frame data
- transform gizmo 的 handle build inputs 组装 helper 已开始从 `SceneViewPanel``SceneViewportOverlayHandleBuilder.h` 收口
- transform gizmo 的 selection/context/refresh/cancel helper 已开始从 `SceneViewPanel``SceneViewportTransformGizmoFrameBuilder.h` 收口
- move / rotate / scale gizmo 已不再直接依赖 `DrawSceneViewportOverlay()` 的 ImGui gizmo 绘制分支出图
- `SceneViewportOverlayRenderer.cpp` 已收缩回 HUD/orientation 责任,不再承担 transform gizmo / scene icon / scene line 的 ImGui world draw
- `SceneViewPanel` 内部交互前命中与交互后绘制的 gizmo 刷新链路已开始复用同一套 helper重复的 context/update/submit 逻辑已明显收缩
- interaction overlay frame 已改为 host 按传入的 transform gizmo inputs 现场组合,`SceneViewPanel` 不再为 hit test 预先写入 transient overlay 缓存
- render 阶段使用的 transient transform gizmo frame data 也已改为 host 基于缓存的原始 overlay + inputs 现场构建
当前仍未完成的关键点:
- `transform gizmo` 的 drag solver / 变换求解仍然保留在各自 gizmo 类中
- `SceneViewPanel` 里仍保留 transform gizmo 的 draw data 生成、交互仲裁与 transient overlay 提交逻辑
- `SceneViewPanel` 仍直接控制 transform gizmo 的最终绘制 overlay 提交时机host 尚未完全接管这一层 frame orchestration
- `ViewportHostService` 的 canonical overlay frame data 仍未直接承载 transform gizmo尚未收敛到单帧单份 canonical overlay 数据
当前阶段结论:
**方案方向已经验证正确,下一步不应该回头继续扩写 ImGui world overlay而应该继续推进 canonical overlay data 与统一命中系统。**
## 1. 方案结论
当前 `Scene Viewport` 的问题,不是某一个 `Directional Light Gizmo` 画丑了,而是整条 editor overlay 链路本身没有收口:
- `grid` 已经是正规 renderer pass
- `transform gizmo / camera icon / light icon / camera frustum / directional light gizmo` 仍然是 ImGui overlay
- 输入命中和绘制几何不是同一份数据
- `SceneViewPanel.cpp` 同时承担 panel UI、输入调度、世界转屏幕、overlay 构建、命中仲裁,职责已经失控
结论只有一个:
**不能再继续往 `SceneViewPanel.cpp` 和 ImGui world overlay 上堆功能。必须把场景中的 editor 可视化正式收口成一套 renderer 级 overlay pass 和统一 handle 数据。**
---
## 2. 当前链路梳理
### 2.1 正规链路Grid
当前 `grid` 的路径是正规的 renderer pass
`ViewportHostService -> SceneRenderer -> CameraRenderer -> BuiltinPostProcessPassSequenceBuilder -> BuiltinInfiniteGridPass`
关键文件:
- `editor/src/Viewport/ViewportHostService.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `engine/src/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder.cpp`
- `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp`
这条链路的特征是:
- 在 scene 几何渲染完成后,由 GPU pass 正式叠加
- 有明确的 render request 输入
- 有独立 pass 边界
- 不依赖 ImGui draw list
### 2.2 非正规链路Editor World Overlay
当前绝大多数 editor 可视化不是 renderer pass而是
1. `ViewportHostService` 渲出 scene viewport 纹理
2. `RenderViewportPanelContent()` 在 ImGui 面板中显示这张纹理
3. `SceneViewPanel.cpp` 在这张纹理之上继续用 ImGui draw list 手搓 world overlay
关键文件:
- `editor/src/panels/ViewportPanelContent.h`
- `editor/src/panels/SceneViewPanel.cpp`
- `editor/src/Viewport/SceneViewportOverlayRenderer.cpp`
当前放在这条链上的内容包括:
- move gizmo
- rotate gizmo
- scale gizmo
- camera icon / light icon
- camera frustum
- directional light gizmo
- orientation gizmo
其中 `orientation gizmo` 本质上是固定在右上角的 HUD放在 ImGui 问题不大。真正失控的是那些锚定在世界空间中的 overlay。
---
## 3. 当前架构的核心问题
### 3.1 绘制职责放错层
`SceneViewPanel.cpp` 不应该知道:
- camera frustum 怎么构造几何
- directional light gizmo 怎么构造几何
- icon 如何转屏幕矩形
- 各种 gizmo 如何排序、如何遮挡
这些本质上都属于 viewport overlay 系统,不属于 panel UI。
### 3.2 命中和绘制分裂
当前很多交互是:
- 一套代码负责画
- 另一套代码负责 hover / click / drag
这会导致:
- 看见的和能点的不是同一个东西
- 改样式时经常忘改命中
- 优先级只能靠条件链硬拼
### 3.3 世界空间对象被当成 2D UI 处理
camera/light icon、frustum、light gizmo、transform gizmo 本质上都是世界空间 editor overlay。
但它们现在被塞进 ImGui draw list 后,就天然失去:
- 正规的渲染顺序语义
- 稳定的深度/遮挡策略
- 统一的 primitive 渲染方式
- GPU 级别的扩展能力
### 3.4 `SceneViewPanel.cpp` 已经过胖
当前它同时负责:
- tools/top bar UI
- tool mode 切换
- gizmo context 组装
- gizmo hover/click 仲裁
- icon hit test
- overlay 世界几何构建
- scene picking
- scene camera 输入
这已经不再是“面板”,而是一个巨型调度器加半个渲染系统。
### 3.5 视觉风格无法稳定收敛
Directional Light 这次暴露得最明显:
- 需求是“圆形底盘上的光线分布”
- 当前实现却是在 panel 里临时拼几根线
这不是调几个参数能根治的问题,而是底层 primitive 表达和系统边界不对。
---
## 4. 重构目标
这次重构的目标不是“顺手把几个 gizmo 再修漂亮一点”,而是把 Scene Viewport overlay 彻底正规化。
最终目标如下:
### 4.1 分离两类 overlay
#### A. HUD 类 overlay
固定在面板坐标系的内容继续留在 ImGui
- 顶部工具栏
- 左侧 tools 按钮
- 右上角 orientation gizmo
- 状态提示文字
#### B. World Anchored Overlay
锚定在世界空间里的 editor 可视化统一进入 renderer overlay pass
- move / rotate / scale gizmo
- camera / light scene icon
- camera frustum
- directional light gizmo
- 后续 collider bounds / helper shapes / volume gizmo
### 4.2 统一绘制数据与命中数据
所有可交互 gizmo handle 必须来自同一份 canonical data
- 画什么
- 颜色是什么
- 层级优先级是什么
- handle id 是什么
- 哪里可以点
都不能再分散在不同类里各算一套。
### 4.3 建立 renderer 级 editor overlay pass
目标顺序应为:
`Scene Geometry -> ObjectId -> Builtin Post Process(Grid/Outline) -> Editor Overlay Pass -> ImGui HUD`
也就是说editor 世界 overlay 必须成为正式 render stage而不是纹理上的二次手绘。
### 4.4 让 gizmo 类回归“控制器/求解器”角色
`Move / Rotate / Scale Gizmo` 类应主要负责:
- drag 状态机
- 轴向约束
- plane 约束
- 变换求解
- 交互反馈求解
不再继续兼任:
- 实际几何绘制器
- 实际命中主仲裁器
---
## 5. 推荐的目标架构
## 5.1 Render Request 层新增 `overlayPasses`
当前 `CameraRenderRequest` 里有:
- `preScenePasses`
- `postScenePasses`
- `builtinPostProcess`
但没有真正适合 editor world overlay 的最后一层。
建议新增:
- `overlayPasses`
执行顺序调整为:
1. `preScenePasses`
2. scene geometry
3. object id
4. `postScenePasses`
5. `builtinPostProcess`
6. `overlayPasses`
这样 world overlay 才能稳定压在 grid 和 outline 之上,再由 ImGui 负责最后的 HUD。
### 5.2 新建 `SceneViewportOverlayFrameData`
建议新增一个独立的 frame data 结构,承载这一帧 Scene Viewport 的 editor overlay 数据。
建议字段:
- `linePrimitives`
- `trianglePrimitives`
- `billboardSprites`
- `handleRecords`
- `renderLayer`
- `depthMode`
- `screenSpaceThickness`
其中:
- primitive 用于绘制
- handle record 用于命中
- 两者共享相同的 `handleId`
### 5.3 新建 `SceneViewportOverlayBuilder`
职责:
- 接收 scene overlay context
- 收集 selected objects
- 构建 camera frustum
- 构建 directional light 圆形底盘 gizmo
- 构建 scene icons
- 构建 transform gizmo handles
- 输出统一的 `SceneViewportOverlayFrameData`
建议位置:
- `editor/src/Viewport/SceneViewportOverlayBuilder.h`
- `editor/src/Viewport/SceneViewportOverlayBuilder.cpp`
### 5.4 新建 `SceneViewportOverlayHitTester`
职责:
- 基于 `SceneViewportOverlayFrameData::handleRecords` 做统一命中
- 输出唯一的 hovered handle
- 同一份数据同时服务 hover / click / drag begin
建议位置:
- `editor/src/Viewport/SceneViewportOverlayHitTester.h`
- `editor/src/Viewport/SceneViewportOverlayHitTester.cpp`
### 5.5 新建 `SceneViewportEditorOverlayPass`
职责:
- 读取 `SceneViewportOverlayFrameData`
- 用 GPU 绘制 line / fill / billboard
- 负责世界空间 editor overlay 的正式渲染
建议位置:
- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h`
- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp`
### 5.6 `SceneViewPanel` 的目标职责
重构后 `SceneViewPanel` 只负责:
- 顶部栏和 tools UI
- tool mode / pivot / local-global 状态
- 输入汇总
- 与 viewport host service 交互
- 显示固定 HUD
不再负责:
- world overlay 几何构建
- icon 转屏幕
- frustum / light gizmo 线框拼装
- world overlay 主渲染
---
## 6. 模块职责重新划分
### 6.1 `SceneViewPanel`
保留:
- 面板 UI
- 工具模式切换
- 快捷键
- 鼠标/键盘输入汇总
- orientation gizmo
移出:
- world overlay primitive 构建
- scene icon world/screen 几何生成
- camera/light 线框绘制逻辑
### 6.2 `ViewportHostService`
新增职责:
- 组装 `SceneViewportOverlayFrameData`
- 把 overlay pass 接到 render request
保留职责:
- scene view camera
- viewport render target 管理
- scene render request 提交
- object id picking
### 6.3 Gizmo 求解器
`SceneViewportMoveGizmo / RotateGizmo / ScaleGizmo` 保留:
- 拖拽求解
- 激活态
- 交互反馈数据
逐步移除:
- 直接操作 ImGui draw data 的职责
- 各自内部封闭的 hit test 决策权
### 6.4 Overlay Pass
只负责 GPU 绘制,不处理业务判断。
输入必须已经是“可直接画”的 primitive 数据,避免 pass 内部再知道 camera/light/gizmo 业务语义。
---
## 7. 推荐执行阶段
这次重构不能一次性大爆炸改完,但必须每一步都朝最终架构收敛,不能做过渡性屎层。
### Phase 0冻结错误扩展方向
目标:
- 停止继续向 `SceneViewPanel.cpp` 添加新的 world overlay 绘制逻辑
- 停止继续扩写 `SceneViewportOverlayRenderer.cpp` 为 ImGui world renderer
产出:
- 文档确认
- 后续新增 world gizmo 一律走 overlay builder / pass 方向
### Phase 1打通 `overlayPasses` 通道
目标:
-`CameraRenderRequest` 新增 `overlayPasses`
-`CameraRenderer` 中调整执行顺序
-`ViewportHostService` 中接入 editor overlay pass sequence
产出:
- renderer 支持 scene 之后再画 editor overlay
- 这一步允许先画空 pass不做功能迁移
验收:
- 不影响现有 scene / grid / selection outline
- `overlayPasses` 具备独立初始化、执行、释放边界
### Phase 2迁移纯显示型 world overlay
优先迁移:
- camera frustum
- camera/light scene icon
- directional light gizmo
原因:
- 这些内容交互复杂度低
- 最容易先把 `SceneViewPanel.cpp` 中世界几何拼装代码减掉
Directional Light 本阶段的目标形态:
- 圆形底盘
- 光线分布在圆环或圆盘采样点上
- 明确的世界朝向
- 稳定屏幕线宽
验收:
- 这些 overlay 不再由 ImGui draw list 直接绘制
- `SceneViewPanel.cpp` 不再负责生成对应线框
### Phase 3迁移 Transform Gizmo 的绘制
目标:
- move / rotate / scale gizmo 的显示改为 overlay pass primitive
- gizmo 类转为 handle builder + drag solver
当前进度:
- transform gizmo 已开始转成 transient overlay frame data
- overlay pass 已可消费一批 screen-space triangle primitive 来绘制 gizmo 屏幕几何
- 但 transient gizmo overlay 仍然由 `SceneViewPanel` 在帧末提交,尚未完全收口到 host / builder 的 canonical 路径
说明:
- 这一阶段只迁移“怎么画”
- 可以暂时保留现有 drag 求解逻辑
验收:
- transform gizmo 已不依赖 ImGui world draw
- gizmo 视觉反馈仍然可用
### Phase 4统一命中系统
目标:
- 所有 world overlay 的 hover / click / drag begin 统一使用 `handleRecords`
- 彻底删除 panel 里的多套 hit test 拼接逻辑
当前进度:
- `scene icon` 已完成
- `transform gizmo` 的 hover / click-begin 已经开始接入统一 `handleRecords`
- `drag begin` 之后的求解与 active 状态仍然保留在 gizmo 类与 `SceneViewPanel` 现有链路中
涵盖对象:
- transform gizmo
- scene icon
- 后续 camera/light/world helper handles
验收:
- 命中结果只由一份 canonical handle 数据决定
- 不再依赖大量互相屏蔽的布尔条件
### Phase 5删除旧世界 overlay 路径
目标:
- 删除 `SceneViewPanel.cpp` 中遗留的 world overlay 构建逻辑
- 缩减 `SceneViewportOverlayRenderer.cpp` 到只保留 HUD 类渲染或直接拆分
最终保留:
- ImGui HUD
- renderer world overlay
最终移除:
- ImGui world overlay
---
## 8. 第一阶段建议改动范围
如果按最小风险起步,第一轮只做下面这些:
- `engine/include/XCEngine/Rendering/CameraRenderRequest.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `editor/src/Viewport/ViewportHostService.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- 新增 `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.*`
第一轮不碰:
- move/rotate/scale 的求解逻辑
- Scene icon 的交互优先级逻辑
- SceneViewPanel 的大面积交互重写
这样可以先把 renderer 通道立住,再分批迁移业务内容。
---
## 9. 方案边界
这次重构明确不做的内容:
- 不顺手做 local mode 新行为
- 不顺手做 runtime 通用 debug draw 系统
- 不顺手把所有 editor widget 一次性改成 pass
- 不在本轮引入 render graph
这次只做一件事:
**把 Scene Viewport 中锚定世界空间的 editor overlay从 ImGui 手搓模式收口为正式 renderer overlay 系统。**
---
## 10. 风险与控制
### 10.1 风险:一次性迁移过大
控制方式:
- 先打通 pass 通道
- 再迁静态 overlay
- 最后迁 transform gizmo 与命中
### 10.2 风险:命中系统重写影响交互稳定性
控制方式:
- handle record 和旧逻辑短期并存
- 先让新系统服务 hover
- 再切 click / drag begin
### 10.3 风险D3D12-only pass 与后端扩展
控制方式:
- 第一阶段允许 editor overlay pass 先只支持 D3D12
- 但数据结构和 pass 边界必须 backend-neutral
### 10.4 风险:继续出现“画的是一个,点的是另一个”
控制方式:
- 明确规定 overlay primitive 和 hit proxy 必须同源
- 不能再允许绘制与命中分别各算一套几何
---
## 11. 最终验收标准
当以下条件全部满足时,说明收口完成:
- `SceneViewPanel.cpp` 不再承担 world overlay 几何构建职责
- world anchored editor overlay 全部进入 renderer pass
- transform gizmo / scene icon / camera frustum / light gizmo 使用统一 overlay frame data
- hover / click / drag begin 使用统一 handle record
- ImGui 只负责 HUD不再负责世界空间 gizmo 主绘制
- 新增一种 world gizmo 时,不需要再把世界转屏幕逻辑写回 panel
---
## 12. 对本次 Directional Light Gizmo 的直接指导
在该重构方案下Directional Light Gizmo 的正确实现方式应是:
- 它属于 `World Anchored Overlay`
- 它的几何由 `SceneViewportOverlayBuilder` 负责生成
- 它的线段/圆环由 `SceneViewportEditorOverlayPass` 负责绘制
- 它的样式使用“圆形底盘 + 圆周分布光线”,不再在 panel 中临时拼矩形列线
也就是说Directional Light Gizmo 不应该被当成一个局部修补任务继续留在 `SceneViewPanel.cpp`
---
## 13. 本文后的执行原则
在本方案审核通过之前:
- 允许修 bug
- 不建议继续扩写新的 ImGui world overlay 逻辑
在本方案审核通过之后:
- 新增 world overlay 功能,优先接入 overlay builder / overlay pass
- `SceneViewPanel.cpp` 只减不增