diff --git a/App/main.cpp b/App/main.cpp index 1f04c9e..67df9fe 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -28,289 +28,283 @@ using namespace Engine; using namespace Engine::ECS; -class SandboxApp final : public Application +namespace { -public: - SandboxApp() - : Application("HelloVulkan", 800, 600) + class SandboxApp final : public Application { - } - -protected: - // ---- 生命周期 ---- - - void OnInit() override - { - Scene& scene = GetScene(); - - // ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义) - EnableImGui(); - - // 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄) - // SunTemple:FBX + DDS 压缩贴图(DdsLoader 验证场景) - const auto model = GetAssets().LoadModel("assets/models/viking_room/viking_room.obj"); - const auto temple = GetAssets().LoadModel("assets/models/SunTemple/SunTemple.fbx"); - - m_Spinners.push_back(scene.CreateEntity("backpack")); - m_Spinners[0].AddComponent(MeshRendererComponent{model}); - - // 地面:程序化纯色平面(半边长 15,中心 y=-2) - const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); - - m_Ground = scene.CreateEntity("Ground"); - m_Ground.GetComponent() - .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose - m_Ground.AddComponent(MeshRendererComponent{ground}); - - // 相机实体(primary = 当前渲染视角;位姿存实体 Transform) - m_Camera = scene.CreateEntity("MainCamera"); - auto& camera = m_Camera.AddComponent(); - camera.primary = true; - m_Camera.GetComponent() - .SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)); - - // 神庙实体 × 1( FBX 单位较大,缩放到场景尺度) + public: + SandboxApp() + : Application("HelloVulkan", 800, 600) { - auto e = scene.CreateEntity("SunTemple"); - e.GetComponent() - .SetScale(glm::vec3(0.05f)); - e.AddComponent(MeshRendererComponent{temple}); } - // 灯光实体(第一个方向光 = 阴影光源) - m_KeyLight = scene.CreateEntity("KeyLight"); - m_KeyLight.AddComponent( - LightComponent{ - .type = LightComponent::Type::Directional, - .direction = glm::vec3(-0.5f, -0.6f, -0.35f), - .color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f) - }); + protected: + // ---- 生命周期 ---- - scene.CreateEntity("FillLight") - .AddComponent( - LightComponent{ - .type = LightComponent::Type::Directional, - .direction = glm::vec3(0.5f, -0.5f, 0.3f), - .color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f) - }); - - // 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减) - auto fire = scene.CreateEntity("FirePitLight"); - fire.GetComponent().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f)); - fire.AddComponent( - LightComponent{ - .type = LightComponent::Type::Point, - .color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙 - .radius = 8.0f, - }); - - auto cool = scene.CreateEntity("CoolFillLight"); - cool.GetComponent().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f)); - cool.AddComponent( - 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) + void OnInit() override { - m_Time += deltaTime; + Scene& scene = GetScene(); + + // ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义) + EnableImGui(); + + // 模型资产(PBR 验证场景:backpack 为旧式贴图资产, + // 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5) + const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); + + m_Spinners.push_back(scene.CreateEntity("backpack")); + m_Spinners[0].AddComponent(MeshRendererComponent{model}); + + // 地面:程序化纯色平面(半边长 15,中心 y=-2) + const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); + + m_Ground = scene.CreateEntity("Ground"); + m_Ground.GetComponent() + .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose + m_Ground.AddComponent(MeshRendererComponent{ground}); + + // 相机实体(primary = 当前渲染视角;位姿存实体 Transform) + m_Camera = scene.CreateEntity("MainCamera"); + auto& camera = m_Camera.AddComponent(); + camera.primary = true; + m_Camera.GetComponent() + .SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)); + + // 灯光实体(第一个方向光 = 阴影光源) + m_KeyLight = scene.CreateEntity("KeyLight"); + m_KeyLight.AddComponent( + LightComponent{ + .type = LightComponent::Type::Directional, + .direction = glm::vec3(-0.5f, -0.6f, -0.35f), + .color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f) + }); + + scene.CreateEntity("FillLight") + .AddComponent( + LightComponent{ + .type = LightComponent::Type::Directional, + .direction = glm::vec3(0.5f, -0.5f, 0.3f), + .color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f) + }); + + // 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减) + auto fire = scene.CreateEntity("FirePitLight"); + fire.GetComponent().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f)); + fire.AddComponent( + LightComponent{ + .type = LightComponent::Type::Point, + .color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙 + .radius = 8.0f, + }); + + auto cool = scene.CreateEntity("CoolFillLight"); + cool.GetComponent().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f)); + cool.AddComponent( + LightComponent{ + .type = LightComponent::Type::Point, + .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝 + .radius = 10.0f, + }); } - // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform) - m_Camera.GetComponent() - .camera.Update(GetWindow(), - m_Camera.GetComponent(), - deltaTime); - - // 旋转动画:编辑 Transform 分量后 Compose 重算 world - for (size_t i = 0; i < m_Spinners.size(); ++i) + void OnUpdate(f32 deltaTime) override { - auto& t = m_Spinners[i].GetComponent(); - t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast(i) * 40.0f, 0.0f)); - t.SetScale(glm::vec3(0.5f)); - } - } - - // ---- 事件 ---- - - // ---- 叠加层 UI(唯一允许出现 ImGui 控件的地方)---- - - void OnRenderOverlay() override - { - ImGui::ShowDemoWindow(); - - ImGui::Begin("Scene Debug"); - - // 阴影光源方向:拖动滑条实时看影子变化 - if (auto* light = m_KeyLight.TryGetComponent()) - { - ImGui::Text("Key Light (shadow caster)"); - ImGui::SliderFloat3("direction", - &light->direction.x, -1.0f, 1.0f); - if (ImGui::Button("reset")) + // 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff + // 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到) + if (!m_PauseAnim) { - light->direction = glm::vec3(-0.5f, -0.6f, -0.35f); + m_Time += deltaTime; + } + + // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform) + m_Camera.GetComponent() + .camera.Update(GetWindow(), + m_Camera.GetComponent(), + deltaTime); + + // 旋转动画:编辑 Transform 分量后 Compose 重算 world + for (size_t i = 0; i < m_Spinners.size(); ++i) + { + auto& t = m_Spinners[i].GetComponent(); + t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast(i) * 40.0f, 0.0f)); + t.SetScale(glm::vec3(0.5f)); } } - // Tonemap + Bloom 参数(HDR 后处理输出端) - auto& renderer = GetRenderer(); - ImGui::Separator(); - ImGui::Text("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); - } + // ---- 事件 ---- - ImGui::Text("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); - } + // ---- 叠加层 UI(唯一允许出现 ImGui 控件的地方)---- - // 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确) - bool temporalDiff = renderer.GetShowTemporalDiff(); - if (ImGui::Checkbox("Temporal Diff View", &temporalDiff)) + void OnRenderOverlay() override { - renderer.SetShowTemporalDiff(temporalDiff); - } - ImGui::Checkbox("Pause Animation", &m_PauseAnim); + ImGui::ShowDemoWindow(); - // Pass 级 GPU 耗时(RG timestamp) - ImGui::Separator(); - ImGui::Text("GPU Timings"); - 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]); - } + ImGui::Begin("Scene Debug"); - // 帧率控制 - auto& app = *this; - ImGui::Separator(); - ImGui::Text("Frame"); - bool vsync = app.GetVSync(); - if (ImGui::Checkbox("VSync", &vsync)) - { - app.SetVSync(vsync); + // 阴影光源方向:拖动滑条实时看影子变化 + if (auto* light = m_KeyLight.TryGetComponent()) + { + ImGui::Text("Key Light (shadow caster)"); + ImGui::SliderFloat3("direction", + &light->direction.x, -1.0f, 1.0f); + if (ImGui::Button("reset")) + { + light->direction = glm::vec3(-0.5f, -0.6f, -0.35f); + } + } + + // Tonemap + Bloom 参数(HDR 后处理输出端) + auto& renderer = GetRenderer(); + ImGui::Separator(); + ImGui::Text("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); + } + + ImGui::Text("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 temporalDiff = renderer.GetShowTemporalDiff(); + if (ImGui::Checkbox("Temporal Diff View", &temporalDiff)) + { + renderer.SetShowTemporalDiff(temporalDiff); + } + ImGui::Checkbox("Pause Animation", &m_PauseAnim); + + // Pass 级 GPU 耗时(RG timestamp) + ImGui::Separator(); + ImGui::Text("GPU Timings"); + 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]); + } + + // 帧率控制 + auto& app = *this; + ImGui::Separator(); + ImGui::Text("Frame"); + bool vsync = app.GetVSync(); + if (ImGui::Checkbox("VSync", &vsync)) + { + app.SetVSync(vsync); + if (!vsync) + { + const f32 target = app.GetTargetFps(); + app.SetTargetFps(target); + } + } 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::Separator(); - ImGui::Text("fps: %.1f frame: %llu", - 1.0f / GetDeltaTime(), - static_cast(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))) + f32 target = app.GetTargetFps(); + if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f")) { - m_QuitConfirmed = true; - ImGui::CloseCurrentPopup(); + app.SetTargetFps(target); } - ImGui::SameLine(); - if (ImGui::Button("No", ImVec2(120, 0))) - { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); } - if (m_QuitConfirmed) + ImGui::Separator(); + ImGui::Text("fps: %.1f frame: %llu", + 1.0f / GetDeltaTime(), + static_cast(GetFrameCount())); + + ImGui::End(); + + + // 自定义重载事件: 重载关闭事件模态框 { - m_QuitConfirmed = false; - Close(); + 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) + void OnWindowClose(const Event& event) override { - SetImGuiEnabled(!GetImGuiEnabled()); + SetImGuiEnabled(true); + m_ShowQuitConfirm = true; } - if (event.key == GLFW_KEY_F1) + + void OnKeyPress(const Event& event) override { - STATS.PrintComparison(); + 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 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 m_Spinners; - f32 m_Time = 0.0f; - bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用) -}; + private: + bool m_ShowQuitConfirm = false; + bool m_QuitConfirmed = false; + EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 + EntityHandle m_Ground{}; + EntityHandle m_KeyLight{}; + std::vector m_Spinners; + f32 m_Time = 0.0f; + bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用) + }; +} int main() { diff --git a/Engine/include/Engine/Core/Window.h b/Engine/include/Engine/Core/Window.h index cb958a6..f71612e 100644 --- a/Engine/include/Engine/Core/Window.h +++ b/Engine/include/Engine/Core/Window.h @@ -44,7 +44,7 @@ namespace Engine { m_MouseCapture = std::move(mouse); } - vk::SurfaceKHR CreateSurface(vk::Instance instance); + vk::SurfaceKHR CreateSurface(vk::Instance instance) const; void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface); u32 GetWidth() const { return m_Width; } @@ -63,7 +63,7 @@ namespace Engine { // 输入事件系统 bool IsKeyPressed(int key) const; - void GetMouseDelta(float& dx, float& dy); + void GetMouseDelta(float& dx, float& dy) const; diff --git a/Engine/include/Engine/Renderer/Material.h b/Engine/include/Engine/Renderer/Material.h index c394083..69f7c91 100644 --- a/Engine/include/Engine/Renderer/Material.h +++ b/Engine/include/Engine/Renderer/Material.h @@ -1,46 +1,58 @@ // // Created by atdunbg on 2026/9/21. // -// Material:着色材质 = 一组纹理槽 + 一个描述符集。 +// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。 // -// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致): -// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性) -// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB) +// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致,glTF +// metallic-roughness 工作流): +// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性) +// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定) +// 3 emissive(自发光,sRGB) 4 材质参数 UBO(fragment) // -// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理 -//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。 +// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。 +// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上——无 MR 图的 +// 旧资产退化为纯因子(电介质 + 常数粗糙度)。 // #ifndef HELLOVULKAN_MATERIAL_H #define HELLOVULKAN_MATERIAL_H #include +#include #include #include #include -namespace Engine -{ +namespace Engine { + class Device; + class MemoryAllocator; class DescriptorManager; class Texture; + class UniformBuffer; - class Material - { + // 材质因子(乘 MR 贴图;无贴图时即最终值) + struct MaterialParams { + glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f}; // x=metallic y=roughness + }; + + class Material { public: // 槽位序号(与 binding 对齐) enum Slot : u32 { Albedo = 0, Normal = 1, - Specular = 2, + MetallicRoughness = 2, Emissive = 3, - kSlotCount = 4, + kSlotCount = 4, // 纹理槽数 + kParamsBinding = 4, // 参数 UBO binding }; - Material(Device& device, + Material(Device& device, MemoryAllocator& allocator, DescriptorManager& descriptorManager, vk::DescriptorSetLayout layout, - const std::array& textures); + const std::array& textures, + const MaterialParams& params); ~Material(); Material(const Material&) = delete; @@ -49,12 +61,14 @@ namespace Engine vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; } Texture* GetTexture(Slot slot) const { return m_Textures[slot]; } - // 材质缓存键:同贴图组合共享一份(Model 层使用) - static std::string MakeCacheKey(const std::string paths[kSlotCount]); + // 材质缓存键:同贴图组合+同因子共享一份(Model 层使用) + static std::string MakeCacheKey(const std::string paths[kSlotCount], + const MaterialParams& params); private: std::array m_Textures{}; vk::DescriptorSet m_DescriptorSet; + Scope m_ParamsBuffer; // per-slot 参数 ring(创建时写死) }; } // namespace Engine diff --git a/Engine/include/Engine/Renderer/Model.h b/Engine/include/Engine/Renderer/Model.h index 10c12b4..8ec168b 100644 --- a/Engine/include/Engine/Renderer/Model.h +++ b/Engine/include/Engine/Renderer/Model.h @@ -64,10 +64,11 @@ namespace Engine Texture* GetOrLoadTexture(const std::string& path, bool srgb); - // 材质缓存入口:同贴图组合共享同一 Material(一个描述符集), + // 材质缓存入口:同贴图组合+因子共享同一 Material(一个描述符集), // 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。 - // paths 下标 = Material::Slot(albedo/normal/specular/emissive) - Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]); + // paths 下标 = Material::Slot;params = metallic/roughness 因子 + Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount], + const MaterialParams& params); Device& m_Device; MemoryAllocator& m_Allocator; diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index ed12c3f..feb32d5 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -49,7 +49,7 @@ namespace Engine { class Renderer { public: - Renderer(Window& window); + explicit Renderer(Window& window); ~Renderer(); Renderer(const Renderer&) = delete; diff --git a/Engine/include/Engine/Vulkan/CommandManager.h b/Engine/include/Engine/Vulkan/CommandManager.h index 559bb7a..a60a985 100644 --- a/Engine/include/Engine/Vulkan/CommandManager.h +++ b/Engine/include/Engine/Vulkan/CommandManager.h @@ -28,10 +28,10 @@ namespace Engine { CommandManager& operator=(const CommandManager&) = delete; // 开始录制指定帧槽位的命令缓冲 - vk::CommandBuffer BeginFrame(u32 frameSlot); + vk::CommandBuffer BeginFrame(u32 frameSlot) const; // 结束录制 - void EndFrame(u32 frameSlot); + void EndFrame(u32 frameSlot) const; vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; } diff --git a/Engine/include/Engine/Vulkan/DescriptorManager.h b/Engine/include/Engine/Vulkan/DescriptorManager.h index 28d19dc..1141cd4 100644 --- a/Engine/include/Engine/Vulkan/DescriptorManager.h +++ b/Engine/include/Engine/Vulkan/DescriptorManager.h @@ -28,21 +28,21 @@ namespace Engine { DescriptorManager(const DescriptorManager&) = delete; DescriptorManager& operator=(const DescriptorManager&) = delete; - vk::DescriptorSetLayout CreateLayout(const std::vector& bindings); + vk::DescriptorSetLayout CreateLayout(const std::vector& bindings) const; vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout layout); void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding, - vk::Buffer buffer, u64 size); + vk::Buffer buffer, u64 size) const; void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding, - vk::Buffer buffer, u64 size); + vk::Buffer buffer, u64 size) const; void UpdateTexture(vk::DescriptorSet set, u32 binding, - vk::Sampler sampler, vk::ImageView view); + vk::Sampler sampler, vk::ImageView view) const; // 单个 set layout 的旧接口(保留) vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout, - u32 pushConstantSize); + u32 pushConstantSize) const; // 多 set layout 的新接口。 // push constant range 按程序级 vertex|fragment 声明(与 @@ -50,7 +50,7 @@ namespace Engine { // push 调用点必须带同样的双 stage(VUID-01796) vk::PipelineLayout CreatePipelineLayout( const std::vector& layouts, - u32 pushConstantSize); + u32 pushConstantSize) const; private: Device& m_Device; @@ -64,7 +64,7 @@ namespace Engine { // 每块的容量(set 数;各类型描述符数同值) static constexpr u32 kChunkSets = 4096; - vk::DescriptorPool CreateChunk(); + vk::DescriptorPool CreateChunk() const; }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/ShaderManager.h b/Engine/include/Engine/Vulkan/ShaderManager.h index d3b8cd6..f37fc46 100644 --- a/Engine/include/Engine/Vulkan/ShaderManager.h +++ b/Engine/include/Engine/Vulkan/ShaderManager.h @@ -39,12 +39,12 @@ namespace Engine { ShaderObject CreateFromSPIRV(const std::vector& spirv, vk::ShaderStageFlagBits stage, const std::vector& layouts = {}, - u32 pushConstantSize = 0); + u32 pushConstantSize = 0) const; - void Destroy(const ShaderObject& shader); + void Destroy(const ShaderObject& shader) const; private: - std::vector ReadSPIRV(const std::string& path); + static std::vector ReadSPIRV(const std::string& path); Device& m_Device; }; diff --git a/Engine/include/Engine/Vulkan/SyncManager.h b/Engine/include/Engine/Vulkan/SyncManager.h index 356502d..e1fee6f 100644 --- a/Engine/include/Engine/Vulkan/SyncManager.h +++ b/Engine/include/Engine/Vulkan/SyncManager.h @@ -35,8 +35,8 @@ namespace Engine { vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; } // 等待并重置这一帧的 Fence - void WaitFence(u32 frameIndex); // 只等,不 reset - void ResetFence(u32 frameIndex); // 只 reset + void WaitFence(u32 frameIndex) const; // 只等,不 reset + void ResetFence(u32 frameIndex) const; // 只 reset private: Device& m_Device; diff --git a/Engine/include/Engine/Vulkan/UniformBuffer.h b/Engine/include/Engine/Vulkan/UniformBuffer.h index c3c89c9..d8fa848 100644 --- a/Engine/include/Engine/Vulkan/UniformBuffer.h +++ b/Engine/include/Engine/Vulkan/UniformBuffer.h @@ -39,7 +39,7 @@ namespace Engine { u64 GetSize() const { return m_Size; } // 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用) - void Update(u32 frameSlot, const void* data, u64 size); + void Update(u32 frameSlot, const void* data, u64 size) const; private: std::vector> m_Buffers; diff --git a/Engine/src/Core/Stats.cpp b/Engine/src/Core/Stats.cpp index bc97caf..2837565 100644 --- a/Engine/src/Core/Stats.cpp +++ b/Engine/src/Core/Stats.cpp @@ -18,7 +18,7 @@ namespace Engine // ============ 资源计数 ============ - void Stats::AddTexture(u64 bytes) + void Stats::AddTexture(const u64 bytes) { std::lock_guard lock(m_Mutex); m_TextureCount++; @@ -27,7 +27,7 @@ namespace Engine m_TotalAllocation += bytes; } - void Stats::RemoveTexture(u64 bytes) + void Stats::RemoveTexture(const u64 bytes) { std::lock_guard lock(m_Mutex); if (m_TextureCount > 0) m_TextureCount--; @@ -36,27 +36,27 @@ namespace Engine m_TotalDeallocation += bytes; } - void Stats::AddMesh(u64 vertexBytes, u64 indexBytes) + void Stats::AddMesh(const u64 vertexBytes, const u64 indexBytes) { std::lock_guard lock(m_Mutex); m_MeshCount++; - u64 total = vertexBytes + indexBytes; + const u64 total = vertexBytes + indexBytes; m_MeshBytes += total; m_FrameAllocation += total; m_TotalAllocation += total; } - void Stats::RemoveMesh(u64 vertexBytes, u64 indexBytes) + void Stats::RemoveMesh(const u64 vertexBytes, const u64 indexBytes) { std::lock_guard lock(m_Mutex); if (m_MeshCount > 0) m_MeshCount--; - u64 total = vertexBytes + indexBytes; + const u64 total = vertexBytes + indexBytes; if (m_MeshBytes >= total) m_MeshBytes -= total; m_FrameDeallocation += total; m_TotalDeallocation += total; } - void Stats::AddBuffer(u64 bytes) + void Stats::AddBuffer(const u64 bytes) { std::lock_guard lock(m_Mutex); m_BufferCount++; @@ -65,7 +65,7 @@ namespace Engine m_TotalAllocation += bytes; } - void Stats::RemoveBuffer(u64 bytes) + void Stats::RemoveBuffer(const u64 bytes) { std::lock_guard lock(m_Mutex); if (m_BufferCount > 0) m_BufferCount--; @@ -74,25 +74,25 @@ namespace Engine m_TotalDeallocation += bytes; } - void Stats::AddMemoryBlock(u64 bytes) + void Stats::AddMemoryBlock(const u64 bytes) { std::lock_guard lock(m_Mutex); m_MemoryBlockBytes += bytes; } - void Stats::RemoveMemoryBlock(u64 bytes) + void Stats::RemoveMemoryBlock(const u64 bytes) { std::lock_guard lock(m_Mutex); if (m_MemoryBlockBytes >= bytes) m_MemoryBlockBytes -= bytes; } - void Stats::AddMemoryUsed(u64 bytes) + void Stats::AddMemoryUsed(const u64 bytes) { std::lock_guard lock(m_Mutex); m_MemoryUsedBytes += bytes; } - void Stats::RemoveMemoryUsed(u64 bytes) + void Stats::RemoveMemoryUsed(const u64 bytes) { std::lock_guard lock(m_Mutex); if (m_MemoryUsedBytes >= bytes) m_MemoryUsedBytes -= bytes; @@ -111,14 +111,14 @@ namespace Engine { } - void Stats::AddAllocation(u64 bytes) + void Stats::AddAllocation(const u64 bytes) { std::lock_guard lock(m_Mutex); m_FrameAllocation += bytes; m_TotalAllocation += bytes; } - void Stats::AddDeallocation(u64 bytes) + void Stats::AddDeallocation(const u64 bytes) { std::lock_guard lock(m_Mutex); m_FrameDeallocation += bytes; @@ -206,13 +206,13 @@ namespace Engine // ============ 打印 ============ - static double MB(u64 bytes) { return bytes / (1024.0 * 1024.0); } + static double MB(const u64 bytes) { return bytes / (1024.0 * 1024.0); } void Stats::Print() const { std::lock_guard lock(m_Mutex); - auto proc = ReadProcessMemory(); + const auto proc = ReadProcessMemory(); ENGINE_INFO("========== Resource Stats =========="); ENGINE_INFO("Textures: {:>4} ({:.2f} MB)", m_TextureCount, MB(m_TextureBytes)); diff --git a/Engine/src/Core/Window.cpp b/Engine/src/Core/Window.cpp index 849815c..8001e56 100644 --- a/Engine/src/Core/Window.cpp +++ b/Engine/src/Core/Window.cpp @@ -156,7 +156,7 @@ namespace Engine glfwPollEvents(); } - vk::SurfaceKHR Window::CreateSurface(vk::Instance instance) + vk::SurfaceKHR Window::CreateSurface(const vk::Instance instance) const { VkSurfaceKHR surface; if (glfwCreateWindowSurface(instance, m_Window, nullptr, &surface) != VK_SUCCESS) @@ -167,7 +167,7 @@ namespace Engine return surface; } - void Window::DestroySurface(vk::Instance instance, vk::SurfaceKHR surface) + void Window::DestroySurface(const vk::Instance instance, const vk::SurfaceKHR surface) { instance.destroySurfaceKHR(surface); } @@ -184,14 +184,14 @@ namespace Engine glfwGetFramebufferSize(m_Window, &width, &height); } - bool Window::IsKeyPressed(int key) const + bool Window::IsKeyPressed(const int key) const { // UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方) if (m_KeyboardCapture && m_KeyboardCapture()) return false; return glfwGetKey(m_Window, key) == GLFW_PRESS; } - void Window::GetMouseDelta(float& dx, float& dy) + void Window::GetMouseDelta(float& dx, float& dy) const { double mx, my; glfwGetCursorPos(m_Window, &mx, &my); diff --git a/Engine/src/Renderer/Material.cpp b/Engine/src/Renderer/Material.cpp index 2646584..d186530 100644 --- a/Engine/src/Renderer/Material.cpp +++ b/Engine/src/Renderer/Material.cpp @@ -7,14 +7,17 @@ #include #include #include +#include +#include #include namespace Engine { - Material::Material(Device& device, + Material::Material(Device& device, MemoryAllocator& allocator, DescriptorManager& descriptorManager, vk::DescriptorSetLayout layout, - const std::array& textures) + const std::array& textures, + const MaterialParams& params) : m_Textures(textures) { m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout); @@ -25,18 +28,31 @@ namespace Engine m_Textures[slot]->GetSampler(), m_Textures[slot]->GetView()); } + + // 材质参数:per-slot ring,创建时写死(参数静态,无每帧更新) + m_ParamsBuffer = CreateScope( + device, allocator, sizeof(MaterialParams), kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_ParamsBuffer->Update(slot, ¶ms, sizeof(params)); + descriptorManager.UpdateUniformBuffer( + m_DescriptorSet, kParamsBinding, + m_ParamsBuffer->Get(slot), m_ParamsBuffer->GetSize()); + } } Material::~Material() = default; - std::string Material::MakeCacheKey(const std::string paths[kSlotCount]) + std::string Material::MakeCacheKey(const std::string paths[kSlotCount], + const MaterialParams& params) { std::string key; for (u32 slot = 0; slot < kSlotCount; ++slot) { - if (slot) key += '\n'; key += paths[slot]; + key += '\n'; } + key += "f:" + std::to_string(params.factors.x) + + "," + std::to_string(params.factors.y); return key; } } // namespace Engine diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index ff54636..3a2a2ee 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -38,13 +38,13 @@ Model::~Model() = default; void Model::InitDefaultTextures() { // 各材质槽位的兜底纹理(1x1,开销可忽略): - // albedo 白(受光) / normal 平法线 (0.5,0.5,1) / specular 黑 / emissive 黑 + // albedo 白 / MR 平参数(roughness 0.5, metallic 0)/ emissive 黑 m_DefaultTextures[Material::Albedo] = Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255); + m_DefaultTextures[Material::MetallicRoughness] = + Texture::CreateSolidColor(m_Device, m_Allocator, 0, 128, 0, false); m_DefaultTextures[Material::Normal] = Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false); - m_DefaultTextures[Material::Specular] = - Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0, false); m_DefaultTextures[Material::Emissive] = Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0); } @@ -74,7 +74,7 @@ Model::Model(Device& device, MemoryAllocator& allocator, // 材质/纹理生命周期随模型(缓存持有) std::string paths[Material::kSlotCount] = {"", "", "", ""}; - Material* mat = GetOrCreateMaterial(paths); // 全默认材质 + Material* mat = GetOrCreateMaterial(paths, MaterialParams{}); // 全默认材质 m_Meshes.push_back(CreateScope( m_Device, m_Allocator, vertices, indices, mat)); @@ -148,9 +148,10 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath, bool srgb) return ptr; } -Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount]) +Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount], + const MaterialParams& params) { - const std::string key = Material::MakeCacheKey(paths); + const std::string key = Material::MakeCacheKey(paths, params); if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) { return it->second.get(); } @@ -169,7 +170,8 @@ Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCoun } auto mat = CreateScope( - m_Device, m_DescriptorManager, m_TextureLayout, textures); + m_Device, m_Allocator, m_DescriptorManager, m_TextureLayout, + textures, params); Material* ptr = mat.get(); m_MaterialCache.emplace(key, std::move(mat)); return ptr; @@ -225,18 +227,20 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) } } - // 读取材质纹理路径(4 槽:albedo/normal/specular/emissive) + // 读取材质纹理路径(PBR metallic-roughness 槽位)+ 金属度/粗糙度因子 std::string texPaths[Material::kSlotCount]; + MaterialParams params; if (mesh->mMaterialIndex >= 0) { aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; const std::filesystem::path modelDir = std::filesystem::path(m_ModelPath).parent_path(); + // glTF 的 metallicRoughness 贴图落在 aiTextureType_UNKNOWN const std::pair slots[] = { {aiTextureType_DIFFUSE, Material::Albedo}, {aiTextureType_NORMALS, Material::Normal}, - {aiTextureType_SPECULAR, Material::Specular}, + {aiTextureType_UNKNOWN, Material::MetallicRoughness}, {aiTextureType_EMISSIVE, Material::Emissive}, }; @@ -247,10 +251,17 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) texPaths[slot] = (modelDir / path.C_Str()).string(); } } + + // PBR 因子(glTF2 材质属性;旧资产无此键 → 默认电介质 0/0.5) + ai_real metallic = 0.0f, roughness = 0.5f; + material->Get(AI_MATKEY_METALLIC_FACTOR, metallic); + material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness); + params.factors = glm::vec4(static_cast(metallic), + static_cast(roughness), 0.0f, 0.0f); } - // 材质缓存:同贴图组合 → 同一 Material(一个描述符集),mesh 数 ≠ 集数 - Material* mat = GetOrCreateMaterial(texPaths); + // 材质缓存:同贴图组合+因子 → 同一 Material(一个描述符集) + Material* mat = GetOrCreateMaterial(texPaths, params); return CreateScope( m_Device, m_Allocator, vertices, indices, mat); diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 9923efb..5c354c3 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -32,14 +32,17 @@ namespace Engine::Passes }; uboSetLayout = descriptors.CreateLayout(uboBindings); - // Set 1: 材质纹理槽(binding = Material::Slot:0 albedo / 1 normal / - // 2 specular / 3 emissive) + // Set 1: PBR 材质纹理槽(binding = Material::Slot:0 albedo / + // 1 normal / 2 metallicRoughness / 3 emissive)+ 参数 UBO(binding 4) std::vector texBindings; for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { texBindings.push_back( {slot, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}); } + texBindings.push_back( + {Material::kParamsBinding, vk::DescriptorType::eUniformBuffer, + vk::ShaderStageFlagBits::eFragment}); textureSetLayout = descriptors.CreateLayout(texBindings); // Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号) diff --git a/Engine/src/Vulkan/Buffer.cpp b/Engine/src/Vulkan/Buffer.cpp index e188efa..056a740 100644 --- a/Engine/src/Vulkan/Buffer.cpp +++ b/Engine/src/Vulkan/Buffer.cpp @@ -14,9 +14,9 @@ namespace Engine { Buffer::Buffer(Device& device, MemoryAllocator& allocator, - vk::DeviceSize size, vk::BufferUsageFlags usage, - vk::MemoryPropertyFlags memProps) - : m_Device(device), m_Allocator(allocator) , m_Size(size) + const vk::DeviceSize size, const vk::BufferUsageFlags usage, + const vk::MemoryPropertyFlags memProps) + : m_Device(device), m_Size(size) , m_Allocator(allocator) { // VMA 惯用法:创建 + 分配 + 绑定一步完成; // HOST_VISIBLE 时持久映射,MappedPtr 直接可用 @@ -33,7 +33,7 @@ namespace Engine { STATS.RemoveBuffer(m_Size); } - void Buffer::Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset) + void Buffer::Upload(const void* data, const vk::DeviceSize size, const vk::DeviceSize offset) const { if (!m_MappedPtr) { ENGINE_ERROR("Buffer::Upload: buffer is not host visible"); diff --git a/Engine/src/Vulkan/CommandManager.cpp b/Engine/src/Vulkan/CommandManager.cpp index 910149a..eb5865c 100644 --- a/Engine/src/Vulkan/CommandManager.cpp +++ b/Engine/src/Vulkan/CommandManager.cpp @@ -35,9 +35,9 @@ namespace Engine { m_Device.GetDevice().destroyCommandPool(m_Pool); } - vk::CommandBuffer CommandManager::BeginFrame(u32 frameSlot) + vk::CommandBuffer CommandManager::BeginFrame(const u32 frameSlot) const { - auto cmd = m_Buffers[frameSlot]; + const auto cmd = m_Buffers[frameSlot]; cmd.reset(); vk::CommandBufferBeginInfo beginInfo; @@ -47,7 +47,7 @@ namespace Engine { return cmd; } - void CommandManager::EndFrame(u32 frameSlot) + void CommandManager::EndFrame(const u32 frameSlot) const { m_Buffers[frameSlot].end(); } diff --git a/Engine/src/Vulkan/DescriptorManager.cpp b/Engine/src/Vulkan/DescriptorManager.cpp index a62bae5..7e8706d 100644 --- a/Engine/src/Vulkan/DescriptorManager.cpp +++ b/Engine/src/Vulkan/DescriptorManager.cpp @@ -18,12 +18,12 @@ namespace Engine DescriptorManager::~DescriptorManager() { - for (auto pool : m_Pools) { + for (const auto pool : m_Pools) { m_Device.GetDevice().destroyDescriptorPool(pool); } } - vk::DescriptorPool DescriptorManager::CreateChunk() + vk::DescriptorPool DescriptorManager::CreateChunk() const { std::array poolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets), @@ -39,7 +39,7 @@ namespace Engine return m_Device.GetDevice().createDescriptorPool(poolInfo); } - vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector& bindings) + vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector& bindings) const { std::vector vkBindings; for (auto& b : bindings) @@ -80,8 +80,8 @@ namespace Engine } } - void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding, - vk::Buffer buffer, u64 size) + void DescriptorManager::UpdateUniformBuffer(const vk::DescriptorSet set, const u32 binding, + const vk::Buffer buffer, const u64 size) const { vk::DescriptorBufferInfo bufferInfo; bufferInfo.setBuffer(buffer).setOffset(0).setRange(size); @@ -97,8 +97,8 @@ namespace Engine m_Device.GetDevice().updateDescriptorSets(write, {}); } - void DescriptorManager::UpdateStorageBuffer(vk::DescriptorSet set, u32 binding, - vk::Buffer buffer, u64 size) + void DescriptorManager::UpdateStorageBuffer(const vk::DescriptorSet set, const u32 binding, + const vk::Buffer buffer, const u64 size) const { vk::DescriptorBufferInfo bufferInfo; bufferInfo.setBuffer(buffer).setOffset(0).setRange(size); @@ -114,8 +114,8 @@ namespace Engine m_Device.GetDevice().updateDescriptorSets(write, {}); } - void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding, - vk::Sampler sampler, vk::ImageView view) + void DescriptorManager::UpdateTexture(const vk::DescriptorSet set, const u32 binding, + const vk::Sampler sampler, const vk::ImageView view) const { vk::DescriptorImageInfo imageInfo; imageInfo.setSampler(sampler) @@ -134,7 +134,7 @@ namespace Engine } vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout, - const u32 pushConstantSize) + const u32 pushConstantSize) const { vk::PipelineLayoutCreateInfo info; info.setSetLayouts(layout); @@ -154,7 +154,7 @@ namespace Engine } vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector& layouts, - u32 pushConstantSize) + const u32 pushConstantSize) const { vk::PipelineLayoutCreateInfo info; info.setSetLayouts(layouts); diff --git a/Engine/src/Vulkan/ShaderManager.cpp b/Engine/src/Vulkan/ShaderManager.cpp index 7f8627b..03b8bdb 100644 --- a/Engine/src/Vulkan/ShaderManager.cpp +++ b/Engine/src/Vulkan/ShaderManager.cpp @@ -21,11 +21,11 @@ ShaderManager::~ShaderManager() } ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath, - vk::ShaderStageFlagBits stage, + const vk::ShaderStageFlagBits stage, const std::vector& layouts, - u32 pushConstantSize) + const u32 pushConstantSize) { - auto spirv = ReadSPIRV(spvPath); + const auto spirv = ReadSPIRV(spvPath); if (spirv.empty()) { ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath); return { nullptr, stage }; @@ -34,9 +34,9 @@ ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath, } ShaderObject ShaderManager::CreateFromSPIRV(const std::vector& spirv, - vk::ShaderStageFlagBits stage, + const vk::ShaderStageFlagBits stage, const std::vector& layouts, - u32 pushConstantSize) + const u32 pushConstantSize) const { vk::ShaderStageFlags nextStage = {}; if (stage == vk::ShaderStageFlagBits::eVertex) { @@ -70,7 +70,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector& spirv, vk::ShaderEXT shader; try { - auto result = m_Device.GetDevice().createShadersEXT(createInfo); + const auto result = m_Device.GetDevice().createShadersEXT(createInfo); shader = result.value[0]; } catch (const vk::SystemError& e) { @@ -81,7 +81,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector& spirv, return { shader, stage }; } -void ShaderManager::Destroy(const ShaderObject& shader) +void ShaderManager::Destroy(const ShaderObject& shader) const { if (shader.Handle) { m_Device.GetDevice().destroyShaderEXT(shader.Handle); @@ -96,7 +96,7 @@ std::vector ShaderManager::ReadSPIRV(const std::string& path) return {}; } - size_t size = file.tellg(); + const size_t size = file.tellg(); std::vector buffer(size / sizeof(u32)); file.seekg(0); file.read(reinterpret_cast(buffer.data()), size); diff --git a/Engine/src/Vulkan/SyncManager.cpp b/Engine/src/Vulkan/SyncManager.cpp index a0c22af..5c2d264 100644 --- a/Engine/src/Vulkan/SyncManager.cpp +++ b/Engine/src/Vulkan/SyncManager.cpp @@ -14,7 +14,7 @@ namespace Engine { SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount) : m_Device(device) { - vk::SemaphoreCreateInfo semInfo; + const vk::SemaphoreCreateInfo semInfo; // 每帧一个 ImageAvailable m_ImageAvailable.resize(maxFramesInFlight); @@ -48,7 +48,7 @@ namespace Engine { for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s); } - void SyncManager::WaitFence(u32 frameIndex) + void SyncManager::WaitFence(const u32 frameIndex) const { auto result = m_Device.GetDevice().waitForFences( m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits::max()); @@ -57,7 +57,7 @@ namespace Engine { } } - void SyncManager::ResetFence(u32 frameIndex) + void SyncManager::ResetFence(const u32 frameIndex) const { m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]); } diff --git a/Engine/src/Vulkan/Texture.cpp b/Engine/src/Vulkan/Texture.cpp index f59a3cf..6610250 100644 --- a/Engine/src/Vulkan/Texture.cpp +++ b/Engine/src/Vulkan/Texture.cpp @@ -219,7 +219,7 @@ namespace Engine // DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责): // 颜色图(BaseColor/Emissive)→ SRGB 变体 // 数据图(Normal/Specular 等线性槽位)→ UNORM - vk::Format ToVkFormat(Assets::Dds::DdsFormat f, bool srgb) + vk::Format ToVkFormat(const Assets::Dds::DdsFormat f, const bool srgb) { using DF = Assets::Dds::DdsFormat; switch (f) { @@ -279,9 +279,10 @@ namespace Engine std::vector offsets(m_MipLevels); { vk::DeviceSize off = 0; + auto* dst = static_cast(stagingAlloc.MappedPtr); for (u32 mip = 0; mip < m_MipLevels; ++mip) { offsets[mip] = off; - std::memcpy(stagingAlloc.MappedPtr + off, + std::memcpy(dst + off, data.Mips[mip].data(), data.Mips[mip].size()); off += data.Mips[mip].size(); } diff --git a/Engine/src/Vulkan/UniformBuffer.cpp b/Engine/src/Vulkan/UniformBuffer.cpp index cbb2ccc..7a84451 100644 --- a/Engine/src/Vulkan/UniformBuffer.cpp +++ b/Engine/src/Vulkan/UniformBuffer.cpp @@ -26,7 +26,7 @@ namespace Engine { ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount); } - void UniformBuffer::Update(u32 frameSlot, const void* data, u64 size) + void UniformBuffer::Update(const u32 frameSlot, const void* data, const u64 size) const { m_Buffers[frameSlot]->Upload(data, size); } diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index f45fd24..2975c61 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -1,6 +1,7 @@ #version 450 const int MAX_LIGHTS = 4; +const float PI = 3.14159265359; struct Light { vec4 color; // rgb = 颜色,a = 强度 @@ -35,18 +36,20 @@ layout(set = 0, binding = 0) uniform UniformBufferObject { // 材质纹理槽(binding = Material::Slot) layout(set = 1, binding = 0) uniform sampler2D albedoMap; layout(set = 1, binding = 1) uniform sampler2D normalMap; -layout(set = 1, binding = 2) uniform sampler2D specularMap; +layout(set = 1, binding = 2) uniform sampler2D metallicRoughnessMap; // G=粗糙度 B=金属度 layout(set = 1, binding = 3) uniform sampler2D emissiveMap; +layout(set = 1, binding = 4) uniform MaterialParams { + vec4 factors; // x = metallic 因子,y = roughness 因子 +} matParams; layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近) layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1 layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远) -// 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。 +// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。 // 级联按视图深度选择;采样器均 clamp-to-border 白色: // 光视锥外自动 1.0(无阴影),省出界分支。 float ShadowFactor(vec3 N, float viewDepth) { - // 级联选择:第一个 viewDepth < split 的级联 int cascade = 2; for (int i = 0; i < 3; ++i) { if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; } @@ -54,7 +57,6 @@ float ShadowFactor(vec3 N, float viewDepth) { vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0); vec3 proj = ls.xyz / ls.w; - // NDC [-1,1] → UV [0,1](ortho 的 w=1) vec2 uv = proj.xy * 0.5 + 0.5; float closest; @@ -62,7 +64,6 @@ float ShadowFactor(vec3 N, float viewDepth) { else if (cascade == 1) closest = texture(shadowMap1, uv).r; else closest = texture(shadowMap2, uv).r; - // 斜率缩放 bias:掠射面用更大的 bias 压 acne vec3 L = normalize(-ubo.lights[0].direction.xyz); float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012); @@ -83,15 +84,39 @@ vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) { return normalize(nT * sampled.x + B * sampled.y + N * sampled.z); } +// ---- Cook-Torrance BRDF 各项 ---- +float DistributionGGX(float NdotH, float roughness) { + float a = roughness * roughness; + float a2 = a * a; + float d = NdotH * NdotH * (a2 - 1.0) + 1.0; + return a2 / (PI * d * d); +} + +float GeometrySmith(float NdotV, float NdotL, float roughness) { + float r = roughness + 1.0; + float k = (r * r) / 8.0; // 解析光源用此 k(IBL 才用 IBL 版本) + float gv = NdotV / (NdotV * (1.0 - k) + k); + float gl = NdotL / (NdotL * (1.0 - k) + k); + return gv * gl; +} + +vec3 FresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + void main() { vec3 albedo = texture(albedoMap, fragUV).rgb; - float specIntensity = texture(specularMap, fragUV).r; + vec4 mr = texture(metallicRoughnessMap, fragUV); + float roughness = clamp(mr.g * matParams.factors.y, 0.045, 1.0); + float metallic = clamp(mr.b * matParams.factors.x, 0.0, 1.0); vec3 emissive = texture(emissiveMap, fragUV).rgb; vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV); vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos); + float NdotV = max(dot(N, V), 1e-4); + vec3 F0 = mix(vec3(0.04), albedo, metallic); // 电介质基准反射率 4% - vec3 result = ubo.ambientColor.rgb * albedo; + vec3 Lo = vec3(0.0); for (int i = 0; i < MAX_LIGHTS; ++i) { // 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减 @@ -100,32 +125,45 @@ void main() { if (ubo.lights[i].params.x > 0.5) { vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos; float d = length(toLight); - if (d >= ubo.lights[i].params.y) continue; // 衰减半径外,跳过 + if (d >= ubo.lights[i].params.y) continue; L = toLight / d; float a = clamp(1.0 - (d * d) / (ubo.lights[i].params.y * ubo.lights[i].params.y), 0.0, 1.0); - atten = a * a; // 平方衰减,边缘更柔和 + atten = a * a; } else { L = normalize(-ubo.lights[i].direction.xyz); } - float diff = max(dot(N, L), 0.0); - // Blinn-Phong 高光(半程向量;specular 图做强度系数) - vec3 H = normalize(L + V); - float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity; + vec3 radiance = ubo.lights[i].color.rgb * ubo.lights[i].color.a * atten; + float NdotL = max(dot(N, L), 0.0); + if (NdotL <= 0.0) continue; + + // Cook-Torrance 镜面项 + vec3 H = normalize(V + L); + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = max(dot(H, V), 0.0); + float D = DistributionGGX(NdotH, roughness); + float G = GeometrySmith(NdotV, NdotL, roughness); + vec3 F = FresnelSchlick(NdotH2, F0); + + vec3 kS = F; + vec3 kD = (1.0 - kS) * (1.0 - metallic); // 金属不参与漫反射 + vec3 specular = (D * G * F) / + max(4.0 * NdotV * NdotL, 1e-4); // 阴影只来自第 0 盏方向光(CSM) float shade = (i == 0 && ubo.lights[0].params.x < 0.5) ? ShadowFactor(N, fragViewDepth) : 1.0; - result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * - (albedo * diff + vec3(spec)) * atten * shade; + Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade; } - // 自发光不经光照/阴影直接叠加 - result += emissive; + // 环境光:无 IBL 的简化版(半球常数);后续 IBL 替换 + vec3 ambient = ubo.ambientColor.rgb * albedo; - outColor = vec4(result, 1.0); + vec3 color = ambient + Lo + emissive; + + outColor = vec4(color, 1.0); outVelocity = fragVelocity; }