Files
HelloVulkan/App/main.cpp
T
atdunbg b65856b8a5 feat: ImGui 调试面板分组整理
Scene/Rendering/Environment/GPU Timings/Frame 五个 CollapsingHeader;
功能开关用 checkbox 条件显隐联动参数(Bloom 勾选才出现 threshold/
strength),无参数开关横排平铺。
2026-09-24 13:44:15 +08:00

398 lines
15 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 派生类并运行它。
// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理;
// 这里只写"程序逻辑":创建实体、逐帧更新组件、事件响应。
//
#include <Engine/Core/Application.h>
#include <Engine/Core/Log.h>
#include <Engine/Core/Stats.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 <imgui.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>
using namespace Engine;
using namespace Engine::ECS;
namespace
{
// 可切换环境(IBL 动态切换演示):路径 → AssetManager 缓存去重,
// 重复选择同一环境零成本;首选路径触发一次 compute 预积分
constexpr const char* kEnvPaths[] = {
"assets/textures/venice_dawn_1_4k.hdr",
"assets/textures/rooitou_park_4k.hdr",
"assets/textures/pink_sunrise_4k.hdr",
"assets/textures/birchwood_4k.hdr",
};
constexpr const char* kEnvNames[] = {
"Venice Dawn", "Rooitou Park", "Pink Sunrise", "Birchwood",
};
constexpr int kEnvCount = 4;
constexpr int kDefaultEnv = 0;
class SandboxApp final : public Application
{
public:
SandboxApp()
: Application("HelloVulkan", 800, 600)
{
}
protected:
// ---- 生命周期 ----
void OnInit() override
{
Scene& scene = GetScene();
// ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义)
EnableImGui();
// 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级),
// 其余环境在 ImGui 里按需 lazy 加载后切换
const auto env = GetAssets().LoadEnvironment(
kEnvPaths[kDefaultEnv]);
if (env.Valid()) {
GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env));
}
// 模型资产(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");
m_Spinners.push_back(scene.CreateEntity("backpack"));
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
m_Spinners[0].GetComponent<TransformComponent>().SetScale(glm::vec3{1.0});
// 地面:程序化纯色平面(半边长 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, 0.0f, 7.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(唯一允许出现 ImGui 控件的地方)----
void OnRenderOverlay() override
{
ImGui::Begin("Scene Debug");
auto& renderer = GetRenderer();
// ---- Scene:场景内容(灯/动画) ----
if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
{
// 阴影光源(无开关,控件平铺聚拢)
if (auto* light = m_KeyLight.TryGetComponent<LightComponent>())
{
ImGui::Text("Key Light (shadow caster)");
ImGui::SliderFloat3("direction",
&light->direction.x, -1.0f, 1.0f);
ImGui::SliderFloat("intensity", &light->color.a, 0.0f, 20.0f);
if (ImGui::Button("reset"))
{
light->direction = glm::vec3(-0.5f, -0.6f, -0.35f);
light->color.a = 2.0f;
}
}
ImGui::Checkbox("Pause Animation", &m_PauseAnim);
}
// ---- Rendering:渲染管线参数与开关 ----
if (ImGui::CollapsingHeader("Rendering", ImGuiTreeNodeFlags_DefaultOpen))
{
// Tonemap(无开关,平铺)
f32 exposure = renderer.GetExposure();
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f))
{
renderer.SetExposure(exposure);
}
bool aces = renderer.GetUseACES();
if (ImGui::Checkbox("ACES", &aces))
{
renderer.SetUseACES(aces);
}
// Bloom:开关即显隐——勾上才显示参数
bool bloom = renderer.GetBloomEnabled();
if (ImGui::Checkbox("Bloom", &bloom))
{
renderer.SetBloomEnabled(bloom);
}
if (bloom)
{
f32 threshold = renderer.GetBloomThreshold();
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f))
{
renderer.SetBloomThreshold(threshold);
}
f32 strength = renderer.GetBloomStrength();
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f))
{
renderer.SetBloomStrength(strength);
}
}
// 无参数的纯开关:横排平铺
bool shadows = renderer.GetShadowsEnabled();
if (ImGui::Checkbox("Shadows", &shadows))
{
renderer.SetShadowsEnabled(shadows);
}
ImGui::SameLine();
bool taa = renderer.GetTAAEnabled();
if (ImGui::Checkbox("TAA", &taa))
{
renderer.SetTAAEnabled(taa);
}
}
// ---- Environment:IBL 环境 + 调试视图(决定"看什么") ----
if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen))
{
// 动态环境切换(Assets 层加载 → Renderer 换绑;缓存去重)
ImGui::Text("Environment (IBL)");
int envIdx = m_EnvIndex;
if (ImGui::Combo("env", &envIdx, kEnvNames, kEnvCount))
{
const auto ref = GetAssets().LoadEnvironment(kEnvPaths[envIdx]);
if (ref.Valid())
{
renderer.SetEnvironment(GetAssets().GetEnvironment(ref));
m_EnvIndex = envIdx;
}
}
ImGui::Text("(switch = compute pre-integrate, ms-level)");
// 调试视图:单看某个 pass 的中间输出(类 UE pass debug)
const auto& viewNames = renderer.GetDebugViewNames();
int view = renderer.GetDebugView();
if (ImGui::Combo("Debug View", &view, viewNames.data(),
static_cast<int>(viewNames.size())))
{
renderer.SetDebugView(view);
}
}
// ---- GPU Timings:只读遥测(默认折叠,不占面板高度) ----
if (ImGui::CollapsingHeader("GPU Timings"))
{
// Pass 级 GPU 耗时(RG timestamp)
ImGui::Text("instances: %u main / %u shadow",
renderer.GetMainInstanceCount(),
renderer.GetShadowInstanceCount());
const auto& names = renderer.GetPassNames();
const auto& times = renderer.GetPassGpuMs();
for (size_t i = 0; i < names.size() && i < times.size(); ++i)
{
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
}
}
// ---- Frame:帧率控制与显示 ----
if (ImGui::CollapsingHeader("Frame", ImGuiTreeNodeFlags_DefaultOpen))
{
auto& app = *this;
bool vsync = app.GetVSync();
if (ImGui::Checkbox("VSync", &vsync))
{
app.SetVSync(vsync);
if (!vsync)
{
const f32 target = app.GetTargetFps();
app.SetTargetFps(target);
}
}
if (!vsync)
{
f32 target = app.GetTargetFps();
if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f"))
{
app.SetTargetFps(target);
}
}
ImGui::Text("fps: %.1f frame: %llu",
1.0f / GetDeltaTime(),
static_cast<unsigned long long>(GetFrameCount()));
}
ImGui::End();
// 自定义重载事件: 重载关闭事件模态框
{
if (m_ShowQuitConfirm)
{
ImGui::OpenPopup("Should quit?");
m_ShowQuitConfirm = false;
}
ImGui::SetNextWindowPos(
ImGui::GetMainViewport()->GetCenter(),
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Should quit?", nullptr,
ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::TextUnformatted("Quit the application?");
ImGui::Separator();
if (ImGui::Button("Yes", ImVec2(120, 0)))
{
m_QuitConfirmed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No", ImVec2(120, 0)))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (m_QuitConfirmed)
{
m_QuitConfirmed = false;
Close();
}
}
}
void OnWindowClose(const Event& event) override
{
SetImGuiEnabled(true);
m_ShowQuitConfirm = true;
}
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);
}
private:
bool m_ShowQuitConfirm = false;
bool m_QuitConfirmed = false;
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
EntityHandle m_Ground{};
EntityHandle m_KeyLight{};
std::vector<EntityHandle> m_Spinners;
f32 m_Time = 0.0f;
bool m_PauseAnim = true; // 冻结动画时钟(帧历史 diff 验证用)
int m_EnvIndex = kDefaultEnv;
};
}
int main()
{
SandboxApp app;
try
{
app.Run();
}
catch (const std::exception& e)
{
ENGINE_FATAL("Exception: {}", e.what());
return -1;
}
return 0;
}