Files
HelloVulkan/App/main.cpp
T
atdunbg 20f0035baa feat: ImGui docking Editor + 显示链/帧同步/输入语义重构
- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期
- 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报
- 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链
- 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行)
- 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧
- FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点)
- Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
2026-09-30 00:24:53 +08:00

304 lines
11 KiB
C++
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.
//
// Created by atdunbg on 2026/9/20.
//
// SandboxApp:第一个基于 Application 框架的程序。
//
// main 的职责边界:
// - 定义 Application 派生类并运行它
// - "程序逻辑":场景搭建(实体/组件)、逐帧更新、事件响应、
// 异步环境加载
// - UI 全部委托 App/editor/ 的 docking Editor(本文件不再出现
// 任何 ImGui 控件)
//
// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理。
//
#include <Engine/Core/Application.h>
#include <Engine/Core/Window.h> // SetInputCapture(Application.h 只前置声明)
#include <Engine/Core/Log.h>
#include <Engine/Core/Stats.h>
#include <Engine/Core/AsyncTask.h>
#include <Engine/ECS/Scene.h>
#include <Engine/ECS/EntityHandle.h>
#include <Engine/ECS/Components.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Renderer/Model.h>
#include <glm/glm.hpp>
#include <GLFW/glfw3.h>
#include "Editor.h"
#include "EditorContext.h" // kEnvPaths / kDefaultEnv(与 StatsPanel 共用)
using namespace Engine;
using namespace Engine::ECS;
namespace
{
constexpr const char* kTemplePath = "assets/models/SunTemple/SunTemple.fbx";
class SandboxApp final : public Application
{
public:
SandboxApp()
: Application("HelloVulkan", 800, 600)
{
}
~SandboxApp() override
{
}
protected:
// ---- 生命周期 ----
void OnInit() override
{
Scene& scene = GetScene();
Renderer& renderer = GetRenderer();
renderer.SetDisplayMode(kDisplayViewport);
// ImGui 叠加层(框架只做管线桥接;UI 内容 = docking Editor)
EnableImGui();
// Editor 注入:引擎门面 + 沙盒能力回调(环境异步切换等)
InitEditor();
SetEnv();
const auto suntemple = GetAssets().LoadModel("assets/models/demo/asset.fbx");
auto entityhandle = scene.CreateEntity("SunTemple");
entityhandle.AddComponent<MeshRendererComponent>(MeshRendererComponent{suntemple});
scene.CreateEntity();
scene.CreateEntity();
scene.CreateEntity();
const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx");
if (gun.Valid()) {
m_Gun = scene.CreateEntity("M1911");
m_Gun.AddComponent<MeshRendererComponent>(MeshRendererComponent{gun});
m_Gun.AddComponent<AnimationComponent>();
auto& gt = m_Gun.GetComponent<TransformComponent>();
gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f));
} else {
ENGINE_WARN("m1911 model not loaded, animation test skipped");
}
/*
const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx");
if (gun.Valid()) {
m_Gun = scene.CreateEntity("M1911");
m_Gun.AddComponent<MeshRendererComponent>(MeshRendererComponent{gun});
m_Gun.AddComponent<AnimationComponent>();
auto& gt = m_Gun.GetComponent<TransformComponent>();
gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f));
} else {
ENGINE_WARN("m1911 model not loaded, animation test skipped");
}
int countSize = 0;
if (countSize) {
// 模型资产(PBR 验证场景:backpack 为旧式贴图资产,
// 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5)
const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
// const auto model = GetAssets().LoadModel("assets/models/pocket_watch_4k.gltf/pocket_watch_4k.gltf");
for (int i = 0; i < countSize; i ++)
{
for (int j = 0; j < countSize; j ++)
{
auto m = scene.CreateEntity();
m.AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
m.GetComponent<TransformComponent>().SetPosition(glm::vec3{(i - countSize * 0.5) * 2, 0, (j - countSize * 0.5) * 2});
m_Spinners.push_back(m);
}
}
}
// 地面:程序化纯色平面(半边长 15,中心 y=-2)
const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
m_Ground = scene.CreateEntity("Ground");
m_Ground.GetComponent<TransformComponent>()
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
*/
// 相机实体(primary = 当前渲染视角;位姿存实体 Transform)
m_Camera = scene.CreateEntity("MainCamera");
auto& camera = m_Camera.AddComponent<CameraComponent>();
camera.primary = true;
m_Camera.GetComponent<TransformComponent>()
.SetPosition(glm::vec3(0.0f, 12.0f, 22.0f));
m_Camera.GetComponent<TransformComponent>()
.SetRotation(glm::vec3(-25.0f, 0.0f, 0.0f));
// 灯光实体(第一个方向光 = 阴影光源)
m_KeyLight = scene.CreateEntity("KeyLight");
m_KeyLight.AddComponent<LightComponent>(
LightComponent{
.type = LightComponent::Type::Directional,
.direction = glm::vec3(-0.5f, -0.6f, -0.35f),
.color = glm::vec4(1.0f, 0.95f, 0.9f, 5.0f),
.castShadow = true,
});
/*
scene.CreateEntity("FillLight")
.AddComponent<LightComponent>(
LightComponent{
.type = LightComponent::Type::Directional,
.direction = glm::vec3(0.5f, -0.5f, 0.3f),
.color = glm::vec4(0.3f, 0.5f, 1.0f, 1.5f)
});
*/
/*
// 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减)
auto fire = scene.CreateEntity("FirePitLight");
fire.GetComponent<TransformComponent>().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f));
fire.AddComponent<LightComponent>(
LightComponent{
.type = LightComponent::Type::Point,
.color = glm::vec4(1.0f, 0.6f, 0.3f, 1.0f), // 暖橙
.radius = 8.0f,
});
auto cool = scene.CreateEntity("CoolFillLight");
cool.GetComponent<TransformComponent>().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f));
cool.AddComponent<LightComponent>(
LightComponent{
.type = LightComponent::Type::Point,
.color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝
.radius = 10.0f,
});
*/
}
void OnUpdate(f32 deltaTime) override
{
// 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff
// 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到)
if (!m_PauseAnim)
{
m_Time += deltaTime;
}
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)
m_Camera.GetComponent<CameraComponent>()
.camera.Update(GetWindow(),
m_Camera.GetComponent<TransformComponent>(),
deltaTime);
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
for (size_t i = 0; i < m_Spinners.size(); ++i)
{
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast<f32>(i) * 40.0f, 0.0f));
}
}
// ---- UI(全部委托 docking Editor;本文件零 ImGui 控件)----
void OnImGuiRender() override
{
m_Editor->Draw();
}
// ---- 事件 ----
void OnWindowClose(const Event& event) override
{
// 退出确认走 Editor 的模态框(ImGui 必须可见才有 UI)
SetImGuiEnabled(true);
m_Editor->RequestQuit();
}
void OnKeyPress(const Event& event) override
{
if (event.key == GLFW_KEY_GRAVE_ACCENT)
{
SetImGuiEnabled(!GetImGuiEnabled());
}
if (event.key == GLFW_KEY_F1)
{
STATS.PrintComparison();
}
}
void OnWindowResize(const Event& event) override
{
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
}
void SetEnv(int index = 0) {
// 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级),
// 其余环境在 Stats 面板里按需 lazy 加载后切换
const auto env = GetAssets().LoadEnvironment(kEnvPaths[index]);
if (env.Valid()) {
GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env));
}
}
private:
// Editor 注入:门面指针 + 反向能力回调(面板 → 沙盒)
void InitEditor()
{
EditorContext ctx;
ctx.renderer = &GetRenderer();
ctx.scene = &GetScene();
ctx.assets = &GetAssets();
ctx.app = this;
ctx.getEnvIndex = [this] { return m_EnvIndex; };
ctx.setEnvIndex = [this](int index) { m_EnvIndex = index; };
ctx.pauseAnim = &m_PauseAnim;
m_Editor->Init(ctx);
// 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词
//(全屏 dock host 让它恒真,输入全死)。改为 Editor 按帧
// 判定——Viewport hover/focus 拥有输入(Hazel/UE 语义);
// UI 隐藏(反引号)时 Draw 不再执行,直接全放行
GetWindow().SetInputCapture(
[this] { return GetImGuiEnabled() && m_Editor->BlockingKeyboard(); },
[this] { return GetImGuiEnabled() && m_Editor->BlockingMouse(); });
}
private:
std::unique_ptr<Editor> m_Editor = std::make_unique<Editor>();
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
EntityHandle m_KeyLight{};
EntityHandle m_Ground{};
EntityHandle m_Gun{}; // 骨骼动画验证实体(M1911)
std::vector<EntityHandle> m_Spinners;
f32 m_Time = 0.0f;
bool m_PauseAnim = true; // 冻结动画时钟(Editor 面板可开关)
int m_EnvIndex = kDefaultEnv;
// ---- 异步模型加载(SunTemple)----
AsyncTask<ParsedModel> m_TempleTask;
f32 m_TempleScale = 1.0f; // cm 尺度资产 → 米(解析后应用)
};
}
int main()
{
SandboxApp app;
try
{
app.Run();
}
catch (const std::exception& e)
{
ENGINE_FATAL("Exception: {}", e.what());
return -1;
}
return 0;
}