editor camera: dedicated Scene-view camera decoupled from camera entities

EditorCamera (UE FEditorViewportClient semantics) owned by SceneRenderer:
own TransformComponent + reused Camera controller. BuildFrame dispatches
render source by display mode — editor form always renders from the
editor camera (usable in empty scenes), game form renders from the
scene's primary camera entity. AlignTo copies an entity pose into the
editor camera ("Align Viewport to Camera" button in Inspector). F focus
and viewport gizmo now follow the same dispatch. main.cpp stripped of
dangling sandbox camera/spinner state; onSceneLoaded hook removed.
This commit is contained in:
2026-10-03 15:52:28 +08:00
parent e2a9c21e68
commit fae6614e70
12 changed files with 192 additions and 229 deletions
-6
View File
@@ -367,10 +367,4 @@ void Editor::DoLoadScene()
"keeping current", envPath);
}
}
// 应用层重解析悬空句柄(相机等沙盒状态)
if (m_Ctx.onSceneLoaded)
{
m_Ctx.onSceneLoaded();
}
}
+4 -3
View File
@@ -12,6 +12,7 @@
#include <Engine/ECS/Scene.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/EditorCamera.h>
#include <Engine/Renderer/SceneTypes.h> // kDisplayViewport / kDisplayDirect
#include <functional>
@@ -37,12 +38,12 @@ struct EditorContext {
Engine::ECS::Scene *scene = nullptr;
Engine::Assets::AssetManager *assets = nullptr;
Engine::Application *app = nullptr;
// 编辑器相机(Scene view 专用,归 Renderer 持有——与场景相机实体
// 无关;面板经此做 F 焦点/Gizmo 视图/对齐跳转)
Engine::EditorCamera *editorCamera = nullptr;
std::function<int()> getEnvIndex; // 当前环境表下标
std::function<void(int)> setEnvIndex;
// 场景加载成功后由 SandboxApp 重解析悬空句柄(registry 清场重建后
// 应用层持有的 EntityHandle 全部悬空,见 Scene::Clear 注释)
std::function<void()> onSceneLoaded;
};
#endif //HELLOVULKAN_EDITORCONTEXT_H
+13 -1
View File
@@ -198,7 +198,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
if (ImGui::Checkbox("primary", &cam->primary) && cam->primary)
{
// primary 互斥:启用本相机时清掉其余实体的 primary
//(引擎取第一个 primary 作为渲染视角)
//(引擎取第一个 primary 作为游戏形态渲染视角)
reg.view<Engine::ECS::CameraComponent>().each(
[&](entt::entity e, Engine::ECS::CameraComponent& c)
{
@@ -208,6 +208,18 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
}
});
}
// 对齐跳转:把本实体位姿复制给编辑器相机,Scene view 立即
// 从该位置观看(只取位姿,不接管渲染——渲染源分派在
// SceneRenderer,编辑器形态恒用编辑器相机)
if (ImGui::Button("Align Viewport to Camera") && ctx.editorCamera)
{
if (auto* tr = reg.try_get<Engine::ECS::TransformComponent>(
selected))
{
ctx.editorCamera->AlignTo(*tr);
}
}
}
}
+15 -6
View File
@@ -19,18 +19,27 @@ void StatsPanel::Draw(const EditorContext& ctx)
auto& renderer = *ctx.renderer;
auto& app = *ctx.app;
// ---- Camera:主相机位姿(只读遥测;位姿编辑走 Inspector)----
// ---- Camera:视图相机位姿(只读遥测;跟随渲染源分派)----
{
Engine::ECS::TransformComponent* camT = nullptr;
if (ctx.scene->GetPrimaryCamera(&camT) != nullptr && camT != nullptr)
if (ctx.renderer->GetDisplayMode() == kDisplayViewport && ctx.editorCamera)
{
const glm::vec3& pos = camT->GetPosition();
ImGui::Text("Camera pos");
const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition();
ImGui::Text("Editor camera pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
}
else
{
ImGui::TextDisabled("(no primary camera)");
Engine::ECS::TransformComponent* camT = nullptr;
if (ctx.scene->GetPrimaryCamera(&camT) != nullptr && camT != nullptr)
{
const glm::vec3& pos = camT->GetPosition();
ImGui::Text("Game camera pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
}
else
{
ImGui::TextDisabled("(no primary camera)");
}
}
ImGui::Separator();
}
+28 -14
View File
@@ -66,6 +66,22 @@ namespace
}
return p;
}
// 视图相机分派(必须与 SceneRenderer::BuildFrame 渲染源同一规则,
// 否则 Gizmo 杆与画面错位):编辑器形态 = EditorCamera(Scene view
// 专用,与相机实体无关);游戏形态 = primary 相机实体。
// 返回 nullptr = 游戏形态且无 primary 相机(本帧无 Gizmo)
Engine::Camera* ViewCamera(const EditorContext& ctx,
ECS::TransformComponent*& outT)
{
if (ctx.renderer->GetDisplayMode() == kDisplayViewport)
{
outT = &ctx.editorCamera->GetTransform();
return &ctx.editorCamera->GetController();
}
auto* cc = ctx.scene->GetPrimaryCamera(&outT);
return cc ? &cc->camera : nullptr;
}
}
std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
@@ -143,14 +159,10 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
if (ImGui::IsKeyPressed(ImGuiKey_F) && tr != nullptr) {
// F = 焦点选中实体(frame-selection):环绕 pivot 设
// 为选中实体位置,相机沿视线平移对准(环绕距离沿用
// 当前值,滚轮可调)
ECS::TransformComponent *camT = nullptr;
if (auto *camComp = ctx.scene->GetPrimaryCamera(&camT);
camComp != nullptr && camT != nullptr) {
camComp->camera.FocusOn(*camT, tr->GetPosition());
}
// F = 焦点选中实体(frame-selection):编辑器相机
// 环绕 pivot 设为选中实体位置,沿视线平移对准(环
// 绕距离沿用当前值,滚轮可调)
ctx.editorCamera->FocusOn(tr->GetPosition());
}
}
@@ -158,14 +170,16 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
if (tr != nullptr) {
// 相机矩阵:primary 相机 view + Vulkan proj(转 GL 形式),
// 参数与渲染侧一致(aspect = 渲染分辨率、near/far 0.1/100)
// 相机矩阵:跟随渲染源(编辑器形态 = EditorCamera,游戏
// 形态 = primary 相机实体;与 SceneRenderer::BuildFrame
// 同一分派),参数与渲染侧一致(aspect = 渲染分辨率、
// near/far 0.1/100)
ECS::TransformComponent *camT = nullptr;
if (const auto *camComp = ctx.scene->GetPrimaryCamera(&camT);
camComp != nullptr && camT != nullptr) {
const glm::mat4 view = camComp->camera.GetView(*camT);
if (Engine::Camera *cam = ViewCamera(ctx, camT);
cam != nullptr && camT != nullptr) {
const glm::mat4 view = cam->GetView(*camT);
const glm::mat4 projGL = ProjVulkanToGL(
camComp->camera.GetProjection(aspect, 0.1f, 100.0f));
cam->GetProjection(aspect, 0.1f, 100.0f));
ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
// 杆画在 Image 区域内(等比适配后的 item rect)