From c0124443cfd66d451492fe164ec698d7b51d1e2d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 4 Apr 2026 14:06:17 +0800 Subject: [PATCH] Archive completed scene viewport overlay rework plan --- ...y_Gizmo_Rework_Plan_完成归档_2026-04-04.md | 631 ++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 docs/plan/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md diff --git a/docs/plan/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md b/docs/plan/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md new file mode 100644 index 00000000..2c1124a0 --- /dev/null +++ b/docs/plan/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md @@ -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` 只减不增