diff --git a/Engine/include/Engine/Assets/AssetHandle.h b/Engine/include/Engine/Assets/AssetHandle.h deleted file mode 100644 index 245346e..0000000 --- a/Engine/include/Engine/Assets/AssetHandle.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by atdunbg on 2026/9/22. -// -// AssetHandle:资产句柄 = (池内索引, 生成号),与 Entity/RG 句柄同一哲学。 -// -// 组件、场景数据只存句柄;所有权归 AssetManager。 -// 悬空句柄(generation 不匹配)会被 Get 显式拒绝,杜绝 use-after-free。 -// - -#ifndef HELLOVULKAN_ASSETHANDLE_H -#define HELLOVULKAN_ASSETHANDLE_H - -#include - -// Model 前向声明(句柄不依赖完整类型) -namespace Engine { class Model; } - -namespace Engine::Assets { - - inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF; - - template - struct AssetHandle { - u32 index = kInvalidAssetIndex; - u32 generation = 0; - - bool Valid() const { return index != kInvalidAssetIndex; } - explicit operator bool() const { return Valid(); } - bool operator==(const AssetHandle& o) const = default; - }; - - // 常用别名 - using ModelHandle = AssetHandle; - -} // namespace Engine::Assets - -#endif //HELLOVULKAN_ASSETHANDLE_H diff --git a/Engine/include/Engine/Assets/AssetManager.h b/Engine/include/Engine/Assets/AssetManager.h index 2daf81c..1e2f3bf 100644 --- a/Engine/include/Engine/Assets/AssetManager.h +++ b/Engine/include/Engine/Assets/AssetManager.h @@ -3,14 +3,9 @@ // // AssetManager:引擎资产层。 // -// 职责划分(对照 Renderer): -// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理 -// - 渲染层:拿已解析资产创建/绑定 GPU 资源 -// -// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的 -// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。 -// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的 -// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。 +// 职责:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理。 +// GPU 创建不在这里:经 IAssetUploader 接口(渲染层提供实现)完成, +// 本类不感知 Vulkan——依赖方向 Assets → 接口,实现由组合根注入。 // // 组件存句柄不存指针的理由: // shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时) @@ -23,7 +18,8 @@ #include #include -#include +#include +#include #include #include @@ -32,11 +28,6 @@ #include namespace Engine { - - class Device; - class MemoryAllocator; - class DescriptorManager; - namespace Assets { // ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ---------- @@ -48,7 +39,7 @@ namespace Engine { using LoaderFn = std::function(const std::string&)>; // 加载(带路径去重)。失败返回无效句柄。 - AssetHandle Load(const std::string& path, const LoaderFn& loader) + AssetRef Load(const std::string& path, const LoaderFn& loader) { if (auto it = m_ByPath.find(path); it != m_ByPath.end()) { return it->second; @@ -60,7 +51,7 @@ namespace Engine { return {}; } - AssetHandle h; + AssetRef h; if (!m_Free.empty()) { h.index = m_Free.back(); m_Free.pop_back(); @@ -77,7 +68,7 @@ namespace Engine { } // 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。 - T* Get(AssetHandle h) const + T* Get(AssetRef h) const { if (!h.Valid() || h.index >= m_Entries.size()) { ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})", @@ -94,7 +85,7 @@ namespace Engine { // 卸载:句柄即刻失效(generation 递增),路径缓存清除。 // 注意:GPU 侧何时安全释放由上层(Renderer fence)保证—— // 本类只管所有权,不管 GPU 生命周期。 - void Unload(AssetHandle h) + void Unload(AssetRef h) { if (!h.Valid() || h.index >= m_Entries.size()) return; @@ -109,7 +100,7 @@ namespace Engine { m_Free.push_back(h.index); } - const std::string& Path(AssetHandle h) const + const std::string& Path(AssetRef h) const { static const std::string kEmpty; if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty; @@ -132,29 +123,27 @@ namespace Engine { std::vector m_Entries; std::vector m_Free; - std::unordered_map> m_ByPath; + std::unordered_map> m_ByPath; }; // ---------- AssetManager:全引擎资产入口 ---------- - // (ModelHandle 别名来自 AssetHandle.h) class AssetManager { public: - // GPU 上下文由组合根(Application)在 Renderer 装配完成后注入 - AssetManager(Device& device, MemoryAllocator& allocator, - DescriptorManager& descriptors, - vk::DescriptorSetLayout textureLayout); + // 上传服务由组合根(Application)注入:渲染层实现接口, + // 资产层不感知 Vulkan 具体类 + explicit AssetManager(IAssetUploader& uploader); ~AssetManager() = default; AssetManager(const AssetManager&) = delete; AssetManager& operator=(const AssetManager&) = delete; - ModelHandle LoadModel(const std::string& path); - Model* GetModel(ModelHandle h) const { return m_Models.Get(h); } - void UnloadModel(ModelHandle h) { m_Models.Unload(h); } + ModelRef LoadModel(const std::string& path); + Model* GetModel(ModelRef h) const { return m_Models.Get(h); } + void UnloadModel(ModelRef h) { m_Models.Unload(h); } // 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。 - ModelHandle CreatePlane(f32 halfSize, u8 r, u8 g, u8 b); + ModelRef CreatePlane(f32 halfSize, u8 r, u8 g, u8 b); AssetCache& Models() { return m_Models; } @@ -162,10 +151,7 @@ namespace Engine { // 各自一个 AssetCache + 便捷方法,模式与 Model 完全一致。 private: - Device& m_Device; - MemoryAllocator& m_Allocator; - DescriptorManager& m_Descriptors; - vk::DescriptorSetLayout m_TextureLayout; + IAssetUploader& m_Uploader; AssetCache m_Models; }; diff --git a/Engine/include/Engine/Assets/IAssetUploader.h b/Engine/include/Engine/Assets/IAssetUploader.h new file mode 100644 index 0000000..6c53c61 --- /dev/null +++ b/Engine/include/Engine/Assets/IAssetUploader.h @@ -0,0 +1,38 @@ +// +// Created by atdunbg on 2026/9/23. +// +// IAssetUploader:资产层 → GPU 的上传接口。 +// +// 资产层(AssetManager/AssetCache)只认这个接口,不感知 Vulkan 具体类 +// (Device/Allocator/DescriptorManager)。实现由渲染层提供 +// (VulkanAssetUploader),组合根(Renderer)装配后注入。 +// +// 对齐 UE:资产系统(AssetManager/StreamableManager)与渲染资源创建 +// (rendering thread / RHI)分离,资产层只拿到"可用的渲染资源"。 +// +// 后续演进点:接口加返回 future / 回调即为异步加载 +// (IO 线程解析 → 上传队列提交),本接口已是正确的接缝。 +// + +#ifndef HELLOVULKAN_IASSETUPLOADER_H +#define HELLOVULKAN_IASSETUPLOADER_H + +#include +#include + +namespace Engine::Assets { + + class IAssetUploader { + public: + virtual ~IAssetUploader() = default; + + // 磁盘模型 → 解析 + GPU 上传(失败返回 nullptr) + virtual Scope LoadModel(const std::string& path) = 0; + + // 程序化纯色平面(size 为半边长) + virtual Scope CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) = 0; + }; + +} // namespace Engine::Assets + +#endif //HELLOVULKAN_IASSETUPLOADER_H diff --git a/Engine/include/Engine/Core/AssetRef.h b/Engine/include/Engine/Core/AssetRef.h new file mode 100644 index 0000000..5bad523 --- /dev/null +++ b/Engine/include/Engine/Core/AssetRef.h @@ -0,0 +1,45 @@ +// +// Created by atdunbg on 2026/9/23. +// +// AssetRef:中性资产引用 = (池内索引, 生成号),与 Entity/RG 句柄同一哲学。 +// +// 放在 Core 层(不依赖 Assets/Vulkan):ECS 组件、序列化数据只存这个 +// 纯值类型。对齐 UE/Unity 的做法——组件持有"资产引用",但不感知资产 +// 系统内部:Unity 的 InstanceID、UE 的 FSoftObjectPath 本质都是中性引用 +// 值,解析归资产系统(AssetManager)。 +// 悬空引用(generation 不匹配)由 AssetCache::Get 显式拒绝。 +// + +#ifndef HELLOVULKAN_ASSETREF_H +#define HELLOVULKAN_ASSETREF_H + +#include + +// 资产类型前向声明(必须放全局层:namespace 内前向声明会创建 +// Engine::Assets::Engine::Model 这类错误嵌套命名空间) +namespace Engine { + class Model; + class Texture; + class Material; +} + +namespace Engine { + + inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF; + + template + struct AssetRef { + u32 index = kInvalidAssetIndex; + u32 generation = 0; + + bool Valid() const { return index != kInvalidAssetIndex; } + explicit operator bool() const { return Valid(); } + bool operator==(const AssetRef& o) const = default; + }; + + // 常用别名(新增资产类型时在这里加一行) + using ModelRef = AssetRef; + +} // namespace Engine + +#endif //HELLOVULKAN_ASSETREF_H diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h index 5151ee6..346ccc6 100644 --- a/Engine/include/Engine/ECS/Components.h +++ b/Engine/include/Engine/ECS/Components.h @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include @@ -62,9 +62,10 @@ namespace Engine::ECS { }; // 网格渲染体:引用共享的 Model 资产。 - // 存句柄不存指针/Ref:所有权归 AssetManager,悬空由句柄 generation 拦截。 + // 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)—— + // ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。 struct MeshRendererComponent { - Assets::ModelHandle model; + ModelRef model; }; // 相机。一个场景中 primary=true 的实体是当前渲染视角。 diff --git a/Engine/include/Engine/Renderer/Passes/BloomPass.h b/Engine/include/Engine/Renderer/Passes/BloomPass.h new file mode 100644 index 0000000..9cc030b --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/BloomPass.h @@ -0,0 +1,44 @@ +// +// Created by atdunbg on 2026/9/23. +// +// BloomPass:Extract(HDR 软阈值)→ BlurH → BlurV,全部半分辨率。 +// 一个"效果"对应多个 RG pass 的例子(Unity 的一个 ScriptableRenderPass +// 常含多个 PassEvent 阶段,同构)。 +// + +#ifndef HELLOVULKAN_BLOOMPASS_H +#define HELLOVULKAN_BLOOMPASS_H + +#include +#include +#include +#include + +namespace Engine::Passes { + + class BloomPass final : public RenderPass { + public: + BloomPass(FullscreenGpu& gpu, PostProcessParams& params, + RG::TextureHandle hdrColor, + RG::TextureHandle bloomA, RG::TextureHandle bloomB); + ~BloomPass() override = default; + + void Register(RG::RenderGraph& rg) override; + void OnTargetsRebuilt(RG::RenderGraph& rg) override; + + private: + FullscreenGpu& m_Gpu; + PostProcessParams& m_Params; + RG::TextureHandle m_HDR; + RG::TextureHandle m_A; // 提取 + 垂直模糊结果 + RG::TextureHandle m_B; // 水平模糊中间结果 + + // per-slot 输入描述符 + std::vector m_ExtractSets; + std::vector m_BlurHSets; + std::vector m_BlurVSets; + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_BLOOMPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/ForwardPass.h b/Engine/include/Engine/Renderer/Passes/ForwardPass.h new file mode 100644 index 0000000..0af73ce --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/ForwardPass.h @@ -0,0 +1,38 @@ +// +// Created by atdunbg on 2026/9/23. +// +// ForwardPass:相机视角渲染到 HDR 颜色图(线性光照值)。 +// 显示变换不在本 pass——HDRColor 经 Tonemap 归一后才写 Backbuffer +// (对齐 UE/Unity:SceneColor(HDR) → tonemapper → UI)。 +// + +#ifndef HELLOVULKAN_FORWARDPASS_H +#define HELLOVULKAN_FORWARDPASS_H + +#include +#include +#include + +namespace Engine::Passes { + + class ForwardPass final : public RenderPass { + public: + ForwardPass(SceneGpu& gpu, FrameSceneData& frame, + RG::TextureHandle hdrColor, RG::TextureHandle depth, + RG::TextureHandle shadowMap) + : m_Gpu(gpu), m_Frame(frame), + m_HDRColor(hdrColor), m_Depth(depth), m_ShadowMap(shadowMap) {} + + void Register(RG::RenderGraph& rg) override; + + private: + SceneGpu& m_Gpu; + FrameSceneData& m_Frame; + RG::TextureHandle m_HDRColor; + RG::TextureHandle m_Depth; + RG::TextureHandle m_ShadowMap; + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_FORWARDPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h b/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h new file mode 100644 index 0000000..2c1c136 --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/FullscreenGpu.h @@ -0,0 +1,78 @@ +// +// Created by atdunbg on 2026/9/23. +// +// FullscreenGpu:全屏后处理共享装配(Tonemap/Bloom 系 pass 共用)。 +// +// 全屏 pass 的共性收口在这里:Set0 = {输入0, 输入1}、push 16B 通用结构 +// (规避 VUID-08878 的"range 定义必须一致")、无顶点输入全屏三角、 +// 公共录制函数。新增全屏效果 = 复用本类 + 新增一个 frag shader。 +// + +#ifndef HELLOVULKAN_FULLSCREENGPU_H +#define HELLOVULKAN_FULLSCREENGPU_H + +#include +#include +#include + +namespace Engine { + + class Device; + class DescriptorManager; + class ShaderManager; + + namespace Passes { + + // 全屏 pass 通用 push(16B):所有全屏 pass 统一声明与推送 + struct FullscreenPush { + f32 exposure; + f32 param0; // 各 pass 自定语义(tonemap: aces 开关) + f32 param1; + f32 param2; + }; + + // 后处理链参数(ImGui 可调;Renderer 持有,pass 按引用读取) + struct PostProcessParams { + f32 exposure = 1.0f; + bool useACES = true; + f32 bloomThreshold = 1.0f; // HDR 域亮度阈值 + f32 bloomStrength = 0.8f; // 叠加强度 + }; + + class FullscreenGpu { + public: + FullscreenGpu(Device& device, DescriptorManager& descriptors, + ShaderManager& shaders); + ~FullscreenGpu(); + + FullscreenGpu(const FullscreenGpu&) = delete; + FullscreenGpu& operator=(const FullscreenGpu&) = delete; + + vk::DescriptorSetLayout setLayout; // Set0 = {输入0, 输入1} + vk::PipelineLayout layout; // push 16B(FullscreenPush) + vk::Sampler sampler; + vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{}; + + // 各 pass 自己持有 per-slot set;分配统一走这里 + vk::DescriptorSet AllocateSet(); + + // 输入图片绑定(RG 重编译后重绑;物理镜像按槽位各一份) + void UpdateSetInput(vk::DescriptorSet set, u32 binding, + RG::RenderGraph& rg, RG::TextureHandle input, + u32 slot); + + // 全屏 pass 公共录制:动态状态/draw 全一致,仅附件 view、 + // 输入 set、push 值不同 + void RecordFullscreen(RG::RenderGraphContext& ctx, vk::ImageView view, + vk::ShaderEXT frag, const vk::DescriptorSet& set, + const FullscreenPush& push); + + private: + Device& m_Device; + DescriptorManager& m_Descriptors; + }; + + } // namespace Passes +} // namespace Engine + +#endif //HELLOVULKAN_FULLSCREENGPU_H diff --git a/Engine/include/Engine/Renderer/Passes/ImGuiPass.h b/Engine/include/Engine/Renderer/Passes/ImGuiPass.h new file mode 100644 index 0000000..f27e2d1 --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/ImGuiPass.h @@ -0,0 +1,38 @@ +// +// Created by atdunbg on 2026/9/23. +// +// ImGuiPass:叠加层(loadOp=Load,不清屏)。 +// 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类在 +// OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。 +// 未启用叠加层时空转(begin/end 一次空 rendering region)。 +// + +#ifndef HELLOVULKAN_IMGUIPASS_H +#define HELLOVULKAN_IMGUIPASS_H + +#include + +namespace Engine { + + class ImGuiLayer; + + namespace Passes { + + class ImGuiPass final : public RenderPass { + public: + // 二级指针:Renderer::SetImGuiLayer 在 Register 之后才可能被调 + //(EnableImGui 时机由派生类决定),执行期解引用取最新值 + ImGuiPass(ImGuiLayer** layer, RG::TextureHandle backbuffer) + : m_Layer(layer), m_Backbuffer(backbuffer) {} + + void Register(RG::RenderGraph& rg) override; + + private: + ImGuiLayer** m_Layer; + RG::TextureHandle m_Backbuffer; + }; + + } // namespace Passes +} // namespace Engine + +#endif //HELLOVULKAN_IMGUIPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/PresentPass.h b/Engine/include/Engine/Renderer/Passes/PresentPass.h new file mode 100644 index 0000000..889fdc2 --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/PresentPass.h @@ -0,0 +1,32 @@ +// +// Created by atdunbg on 2026/9/23. +// +// PresentPass:transition 到 ePresentSrcKHR。 +// layout 转换由 RG 的 barrier 完成,实际 vkQueuePresentKHR 由 Renderer 驱动。 +// + +#ifndef HELLOVULKAN_PRESENTPASS_H +#define HELLOVULKAN_PRESENTPASS_H + +#include + +namespace Engine::Passes { + + class PresentPass final : public RenderPass { + public: + explicit PresentPass(RG::TextureHandle backbuffer) + : m_Backbuffer(backbuffer) {} + + void Register(RG::RenderGraph& rg) override + { + rg.AddPass("Present", [](RG::RenderGraphContext&) {}) + .Read(m_Backbuffer, RG::ResourceUse::Present); + } + + private: + RG::TextureHandle m_Backbuffer; + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_PRESENTPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/RenderPass.h b/Engine/include/Engine/Renderer/Passes/RenderPass.h new file mode 100644 index 0000000..539cdef --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/RenderPass.h @@ -0,0 +1,34 @@ +// +// Created by atdunbg on 2026/9/23. +// +// RenderPass:渲染 pass 对象基类(Unity ScriptableRenderPass 思路)。 +// +// pass 自己负责两件事,框架(Renderer)不再手写"超级函数": +// Register —— 向 RenderGraph 声明资源使用 + 录制 lambda; +// 执行序由 RG 拓扑排序决定(writer 必先于 reader), +// 与注册顺序无关 +// OnTargetsRebuilt —— RG 重编译(resize / vsync 切换)后重绑描述符 +// (物理镜像按槽位可能全部换新) +// +// pass 之间不互相依赖:数据流只经 RG 资源句柄。新增效果 = 新增一个 +// RenderPass 子类 + Renderer::BuildGraph 里注册一行,其它代码不动。 +// + +#ifndef HELLOVULKAN_RENDERPASS_H +#define HELLOVULKAN_RENDERPASS_H + +#include + +namespace Engine::Passes { + + class RenderPass { + public: + virtual ~RenderPass() = default; + + virtual void Register(RG::RenderGraph& rg) = 0; + virtual void OnTargetsRebuilt(RG::RenderGraph& rg) { (void)rg; } + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_RENDERPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/SceneGpu.h b/Engine/include/Engine/Renderer/Passes/SceneGpu.h new file mode 100644 index 0000000..f2a816e --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/SceneGpu.h @@ -0,0 +1,67 @@ +// +// Created by atdunbg on 2026/9/23. +// +// SceneGpu:场景级 GPU 共享装配(Shadow/Forward 及未来几何类 pass 共用)。 +// +// 从 Renderer 拆出的装配体:set 布局 / pipeline 布局 / shader object / +// 采样器 / per-slot 描述符集合。新增几何 pass(延迟、调试线框等)直接 +// 复用本装配,不必碰 Renderer。 +// + +#ifndef HELLOVULKAN_SCENEGPU_H +#define HELLOVULKAN_SCENEGPU_H + +#include +#include +#include +#include + +namespace Engine { + + class Device; + class DescriptorManager; + class ShaderManager; + class UniformBuffer; + + namespace Passes { + + class SceneGpu { + public: + SceneGpu(Device& device, DescriptorManager& descriptors, + ShaderManager& shaders, + UniformBuffer& ubo, UniformBuffer& objectBuffer); + ~SceneGpu(); + + SceneGpu(const SceneGpu&) = delete; + SceneGpu& operator=(const SceneGpu&) = delete; + + // ---- set 布局(shader 契约,改绑定必须同步 shaders/)---- + vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer} + vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo} + vk::DescriptorSetLayout shadowSetLayout; // Set2 = {shadow map} + vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO) + vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B + + // per-slot 描述符:Set0(UBO+实例 SSBO)与 Set2(阴影图采样) + std::vector uboSets; + std::vector shadowSets; + + // 阴影采样器:clamp-to-border 白色 = 光视锥外自动无阴影 + //(省 shader 出界分支) + vk::Sampler shadowSampler; + + vk::ShaderEXT forwardVert{}, forwardFrag{}; + vk::ShaderEXT shadowVert{}, shadowFrag{}; + + // RG 重编译后重绑阴影图(物理镜像按槽位各一份) + void BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap); + + private: + Device& m_Device; + DescriptorManager& m_Descriptors; + }; + + } // namespace Passes +} // namespace Engine + +#endif //HELLOVULKAN_SCENEGPU_H diff --git a/Engine/include/Engine/Renderer/Passes/ShadowPass.h b/Engine/include/Engine/Renderer/Passes/ShadowPass.h new file mode 100644 index 0000000..3bdf60f --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/ShadowPass.h @@ -0,0 +1,33 @@ +// +// Created by atdunbg on 2026/9/23. +// +// ShadowPass:光源视角写深度。GPUDriven batch 消费光源视锥剔除结果 +// (FrameSceneData::shadowView——相机外实体仍投影子)。 +// + +#ifndef HELLOVULKAN_SHADOWPASS_H +#define HELLOVULKAN_SHADOWPASS_H + +#include +#include +#include + +namespace Engine::Passes { + + class ShadowPass final : public RenderPass { + public: + ShadowPass(SceneGpu& gpu, FrameSceneData& frame, RG::TextureHandle shadowMap) + : m_Gpu(gpu), m_Frame(frame), m_ShadowMap(shadowMap) {} + + void Register(RG::RenderGraph& rg) override; + void OnTargetsRebuilt(RG::RenderGraph& rg) override; + + private: + SceneGpu& m_Gpu; + FrameSceneData& m_Frame; + RG::TextureHandle m_ShadowMap; + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_SHADOWPASS_H diff --git a/Engine/include/Engine/Renderer/Passes/TonemapPass.h b/Engine/include/Engine/Renderer/Passes/TonemapPass.h new file mode 100644 index 0000000..d6380c2 --- /dev/null +++ b/Engine/include/Engine/Renderer/Passes/TonemapPass.h @@ -0,0 +1,41 @@ +// +// Created by atdunbg on 2026/9/23. +// +// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 Backbuffer。 +// 显示变换在此显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。 +// Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。 +// + +#ifndef HELLOVULKAN_TONEMAPPASS_H +#define HELLOVULKAN_TONEMAPPASS_H + +#include +#include +#include +#include + +namespace Engine::Passes { + + class TonemapPass final : public RenderPass { + public: + TonemapPass(FullscreenGpu& gpu, PostProcessParams& params, + RG::TextureHandle hdrColor, RG::TextureHandle bloomA, + RG::TextureHandle backbuffer); + ~TonemapPass() override = default; + + void Register(RG::RenderGraph& rg) override; + void OnTargetsRebuilt(RG::RenderGraph& rg) override; + + private: + FullscreenGpu& m_Gpu; + PostProcessParams& m_Params; + RG::TextureHandle m_HDR; + RG::TextureHandle m_A; + RG::TextureHandle m_Backbuffer; + + std::vector m_Sets; // per-slot + }; + +} // namespace Engine::Passes + +#endif //HELLOVULKAN_TONEMAPPASS_H diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index e3b7f0a..08073d8 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -1,6 +1,19 @@ // // Created by atdunbg on 2026/9/20. // +// Renderer:渲染门面 + 渲染侧组合根。 +// +// 分工(对照 UE/Unity 的渲染架构): +// Renderer —— 装配模块、声明 RG 资源、注册 pass 对象、 +// 每帧编排(翻译场景 → 写 buffer → 驱动 RG → 提交呈现)。 +// 不理解场景细节,不含 pass 实现。 +// SceneRenderer —— 场景 → FrameSceneData 的翻译层(相机/灯光/剔除, +// 对应 UE FSceneRenderer / Unity CullingResults)。 +// Passes::* —— 各 pass 的声明与录制(对应 Unity ScriptableRenderPass)。 +// 新增效果 = 新增 pass 类 + BuildGraph 注册一行。 +// +// 对外只暴露两个入口:Render(Scene&, Assets&) 和给 App 层调参的访问器。 +// #ifndef HELLOVULKAN_RENDERER_H #define HELLOVULKAN_RENDERER_H @@ -9,13 +22,11 @@ #include #include +#include #include #include -#include #include -#include - namespace Engine { class Window; @@ -27,26 +38,14 @@ namespace Engine { class SyncManager; class CommandManager; class UniformBuffer; - class Model; + class SceneRenderer; + class VulkanAssetUploader; + class ImGuiLayer; namespace ECS { class Scene; } - namespace Assets { class AssetManager; } - class ImGuiLayer; - class Mesh; + namespace Assets { class AssetManager; class IAssetUploader; } + namespace Passes { class RenderPass; class SceneGpu; class FullscreenGpu; class PostProcessParams; } - // GPUDriven:按 mesh 聚合的绘制批 - struct InstanceBatch { - Mesh* mesh; - u32 first; // 在实例矩阵数组中的起始下标 - u32 count; // 实例数 - }; - - // Renderer 职责(收敛后): - // 1. 装配底层模块(Device/Swapchain/Sync/Command/Descriptor/Shader) - // 2. 声明并驱动 RenderGraph(pass 结构在这里组装) - // 3. Render(Scene&, AssetManager&):从 ECS 场景读取相机/灯光/渲染体, - // 经资产层解析句柄后绘制 - // 不拥有场景,不加载资产(资产归 AssetManager,磁盘 IO 与这里无关)。 class Renderer { public: Renderer(Window& window); @@ -61,69 +60,63 @@ namespace Engine { Device& GetDevice() const { return *m_Device; } MemoryAllocator& GetAllocator() const { return *m_Allocator; } DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; } - vk::DescriptorSetLayout GetTextureSetLayout() const { return m_TextureSetLayout; } - vk::Format GetSwapchainFormat() const; // Swapchain 不完整,实现在 cpp + vk::DescriptorSetLayout GetTextureSetLayout() const; // 实现在 cpp(SceneGpu 成员) + vk::Format GetSwapchainFormat() const; u32 GetSwapchainImageCount() const; + // 资产上传服务(渲染层对资产层提供的 IAssetUploader 实现) + Assets::IAssetUploader& GetAssetUploader() const; // 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。 // 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。 void SetImGuiLayer(ImGuiLayer* layer) { m_ImGuiLayer = layer; } + // ---- 后处理参数(ImGui 可调,转发到 pass 共享的 PostProcessParams)---- + f32 GetExposure() const; + void SetExposure(f32 e); + bool GetUseACES() const; + void SetUseACES(bool b); + f32 GetBloomThreshold() const; + void SetBloomThreshold(f32 v); + f32 GetBloomStrength() const; + void SetBloomStrength(f32 v); + + // pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应) + const std::vector& GetPassNames() const { return m_PassNames; } + const std::vector& GetPassGpuMs() const { return m_PassGpuMs; } + + // 双视图剔除后的实例数(遥测) + u32 GetMainInstanceCount() const; + u32 GetShadowInstanceCount() const; + + // 垂直同步开关(present mode 切换会重建 swapchain), + // 只应在帧 UI 构建段调用(Render 之前) + void SetVSync(bool vsync); + private: - void BuildGraph(); // 声明 pass 结构 + 首次 Compile + void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象 void HandleResize(); - void UpdateShadowDescriptors(); - void UpdateFullscreenDescriptors(); void ReadGpuTimes(u32 frameSlot); Window& m_Window; - // ---- 底层模块(析构序:后声明者先析构,RG 在 Device/Allocator 之前)---- + // ---- 底层模块(析构序:后声明者先析构;引用他人者先析构)---- Scope m_Device; Scope m_Allocator; Scope m_DescriptorManager; Scope m_ShaderManager; + Scope m_UniformBuffer; // per-slot UBO ring + Scope m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO) + Scope m_SceneGpu; // 场景类 pass 共享装配 + Scope m_FullscreenGpu; // 全屏 pass 共享装配 Scope m_Swapchain; Scope m_SyncManager; Scope m_CommandManager; - Scope m_UniformBuffer; - Scope m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO) + Scope m_SceneRenderer; // 场景翻译层(先于 pass 构造) Scope m_RenderGraph; + Scope m_AssetUploader; - // ---- 管线装配(shader object 方案,无 vk::Pipeline)---- - // Forward layout:3 个 set(UBO/贴图/阴影图)+ 128B push(与 Shadow pass - // 共用上限;forward 的 shader block 只有 64B,block ⊂ range 合法) - vk::PipelineLayout m_PipelineLayout; - vk::DescriptorSetLayout m_UBOSetLayout; - vk::DescriptorSetLayout m_TextureSetLayout; - vk::DescriptorSetLayout m_ShadowSetLayout; - std::vector m_UBODescriptorSets; // 每帧槽位一个 - std::vector m_ShadowDescriptorSets; // 每帧槽位一个 - vk::Sampler m_ShadowSampler; - vk::ShaderEXT m_VertShader{}; - vk::ShaderEXT m_FragShader{}; - - // Shadow pass 独立装配:无 set,push constant = {lightVP, model} 128B - vk::PipelineLayout m_ShadowPipelineLayout; - vk::ShaderEXT m_ShadowVertShader{}; - vk::ShaderEXT m_ShadowFragShader{}; - - // Tonemap pass 装配:Set0 = {HDR 颜色图, Bloom 图},push = 16B - //(全屏 pass 通用:exposure/aces/bloomStrength/pad) - vk::PipelineLayout m_TonemapPipelineLayout; - vk::DescriptorSetLayout m_TonemapSetLayout; - std::vector m_TonemapDescriptorSets; // 每帧槽位一个 - vk::Sampler m_TonemapSampler; - vk::ShaderEXT m_TonemapVertShader{}; - vk::ShaderEXT m_TonemapFragShader{}; - - // Bloom:Extract(HDR→A) → BlurH(A→B) → BlurV(B→A),半分辨率 - // 与 Tonemap 共用 set layout/pipeline layout/vertex shader - std::vector m_BloomExtractSets; // 每帧槽位 - std::vector m_BloomBlurHSets; - std::vector m_BloomBlurVSets; - vk::ShaderEXT m_BloomExtractFrag{}; - vk::ShaderEXT m_BloomBlurFrag{}; + // pass 对象(注册序;执行序由 RG 拓扑排序决定) + std::vector> m_Passes; // ---- RenderGraph 资源句柄 ---- RG::TextureHandle m_Backbuffer{}; @@ -133,34 +126,12 @@ namespace Engine { RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果 RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果 - // ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)---- - ECS::Scene* m_BoundScene = nullptr; - Assets::AssetManager* m_BoundAssets = nullptr; - glm::mat4 m_LightSpace{1.0f}; // 阴影光源 VP(每帧由 Render 算好) - - // GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费) - // 双视图剔除:相机视锥与光源视锥各出一份可见列表—— - // 相机外的实体仍可能投影子进视野,所以 Shadow 用光源视锥单独剔除 - struct CullResult { - std::vector batches; - u32 instanceCount = 0; - }; - CullResult m_MainView; // 相机视锥 - CullResult m_ShadowView; // 光源视锥 - std::unordered_map m_MainBatchLookup; - std::unordered_map m_ShadowBatchLookup; - std::vector m_InstanceModels; // [主视图段 | 阴影视图段] - static constexpr u32 kMaxInstances = 16384; + // pass 共享的可调参数(Renderer 持有,App 层访问器转发) + Scope m_PostParams; // 可选叠加层(不拥有;Application 持有) ImGuiLayer* m_ImGuiLayer = nullptr; - // ---- Tonemap/Bloom 参数(ImGui 可调)---- - f32 m_Exposure = 1.0f; - bool m_UseACES = true; - f32 m_BloomThreshold = 1.0f; // HDR 域亮度阈值 - f32 m_BloomStrength = 0.8f; // 叠加强度 - // GPU timestamp(pass 级耗时,ImGui 可读) vk::QueryPool m_QueryPool; u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2 @@ -168,31 +139,6 @@ namespace Engine { std::vector m_PassNames; // 执行序 pass 名 std::vector m_PassGpuMs; // 最近一帧各 pass 耗时 bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取 - f32 m_CollectMs = 0.0f; // 实例收集 CPU 耗时(遥测) - - public: - f32 GetExposure() const { return m_Exposure; } - void SetExposure(f32 e) { m_Exposure = e; } - bool GetUseACES() const { return m_UseACES; } - void SetUseACES(bool b) { m_UseACES = b; } - f32 GetBloomThreshold() const { return m_BloomThreshold; } - void SetBloomThreshold(f32 v) { m_BloomThreshold = v; } - f32 GetBloomStrength() const { return m_BloomStrength; } - void SetBloomStrength(f32 v) { m_BloomStrength = v; } - - // pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应) - const std::vector& GetPassNames() const { return m_PassNames; } - const std::vector& GetPassGpuMs() const { return m_PassGpuMs; } - - // 双视图剔除后的实例数(遥测) - u32 GetMainInstanceCount() const { return m_MainView.instanceCount; } - u32 GetShadowInstanceCount() const { return m_ShadowView.instanceCount; } - - // 垂直同步开关(Mailbox ↔ Immediate):切换会重建 swapchain, - // 只应在帧 UI 构建段调用(Render 之前) - void SetVSync(bool vsync); - - private: vk::Extent2D m_Extent{}; u32 m_CurrentFrame = 0; diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h new file mode 100644 index 0000000..2957600 --- /dev/null +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -0,0 +1,61 @@ +// +// Created by atdunbg on 2026/9/23. +// +// SceneRenderer:场景 → 渲染数据的每帧翻译层(UE FSceneRenderer / +// Unity CullingResults 的对应物)。 +// +// 职责(全部是"读场景、产数据",不碰 GPU、不碰 Vulkan): +// 1. 相机/灯光/环境色 → UniformBufferObject 语义组装(含阴影光空间矩阵) +// 2. 单趟实例收集 + 双视图剔除(相机视锥/光源视锥各自独立可见列表—— +// 相机外实体仍可能投影子进视野,shadow popping 的教训) +// Renderer 拿 FrameSceneData 写 buffer、驱动 pass;场景理解全部收口在此。 +// +// 扩展性: +// - 多光源:lights 数组已就位(kMaxLights),每灯各自 lightSpace 即 CSM 前置 +// - 级联阴影:BuildFrame 内按级联多出几份 lightSpace + shadowView, +// pass 侧按索引消费,本类结构不变 +// - 遮挡剔除/GPU 剔除:只改本类收集逻辑,下游无感知 +// + +#ifndef HELLOVULKAN_SCENERENDERER_H +#define HELLOVULKAN_SCENERENDERER_H + +#include +#include +#include + +namespace Engine { + + namespace ECS { class Scene; } + namespace Assets { class AssetManager; } + + class SceneRenderer { + public: + // 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。 + // aspect 由调用方传入(跟随 swapchain,本类不感知窗口)。 + bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, f32 aspect); + + FrameSceneData& Frame() { return m_Frame; } + + // ---- 遥测 ---- + u32 GetMainInstanceCount() const { return m_Frame.mainView.instanceCount; } + u32 GetShadowInstanceCount() const { return m_Frame.shadowView.instanceCount; } + f32 GetCollectMs() const { return m_Frame.collectMs; } + + private: + // 阴影光空间矩阵:光源正交投影(覆盖场景,[0,1] 深度重映射)。 + // 写入(Shadow pass push)与采样(Forward UBO)共用同一矩阵。 + void ComputeLightSpace(const glm::vec3& shadowDir); + + FrameSceneData m_Frame; + + // batch 聚合查找表(mesh → batch 下标),每帧 clear 复用桶 + std::unordered_map m_MainLookup; + std::unordered_map m_ShadowLookup; + + bool m_TruncationWarned = false; // 实例缓冲打满只告警一次 + }; + +} // namespace Engine + +#endif //HELLOVULKAN_SCENERENDERER_H diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h new file mode 100644 index 0000000..a7b5e01 --- /dev/null +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -0,0 +1,69 @@ +// +// Created by atdunbg on 2026/9/23. +// +// SceneTypes:场景 → GPU 的数据契约(CPU 结构镜像 shader 布局)。 +// +// SceneRenderer 生产、Renderer/各 pass 消费;改字段必须同步 shaders/。 +// 常量也集中在这里:SSBO 容量、灯数、阴影图尺寸都是"渲染预算", +// 改任何一个都要同步评估 buffer/池容量。 +// + +#ifndef HELLOVULKAN_SCENETYPES_H +#define HELLOVULKAN_SCENETYPES_H + +#include +#include +#include + +namespace Engine { + + class Mesh; + + // GPUDriven:按 mesh 聚合的绘制批 + struct InstanceBatch { + Mesh* mesh; + u32 first; // 在实例矩阵数组中的起始下标 + u32 count; // 实例数 + }; + + // 一个渲染视图的剔除结果(相机视图 / 光源视图各一份) + struct CullResult { + std::vector batches; + u32 instanceCount = 0; + }; + + constexpr u32 kMaxLights = 4; + constexpr u32 kMaxInstances = 16384; // 实例 SSBO 容量(矩阵数) + constexpr u32 kShadowMapSize = 2048; // 阴影图边长 + + // 镜像 shader 侧 UBO 布局的灯光项 + struct LightData { + glm::vec4 direction; // xyz = 方向(已归一),w 未用 + glm::vec4 color; // rgb = 颜色,a = 强度 + }; + + // 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐) + struct UniformBufferObject { + glm::mat4 view; + glm::mat4 proj; + glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵) + glm::vec4 ambientColor; + LightData lights[kMaxLights]; + }; + + // 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。 + // 复用成员容量:clear() 不缩容,稳态零每帧堆分配。 + struct FrameSceneData { + UniformBufferObject ubo{}; + + // 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO) + std::vector instanceModels; + CullResult mainView; // 相机视锥 → Forward pass + CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子) + + f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测) + }; + +} // namespace Engine + +#endif //HELLOVULKAN_SCENETYPES_H diff --git a/Engine/include/Engine/Renderer/VulkanAssetUploader.h b/Engine/include/Engine/Renderer/VulkanAssetUploader.h new file mode 100644 index 0000000..6760ab1 --- /dev/null +++ b/Engine/include/Engine/Renderer/VulkanAssetUploader.h @@ -0,0 +1,43 @@ +// +// Created by atdunbg on 2026/9/23. +// +// VulkanAssetUploader:IAssetUploader 的渲染层实现。 +// +// 属于渲染层对资产层提供的服务:持有 GPU 上下文,负责把解析后的 +// 资产数据创建为 Vulkan 资源。资产层(AssetManager)只看到接口, +// 不知道 Device/Allocator 的存在——依赖方向:Assets → IAssetUploader +// ← VulkanAssetUploader(渲染层),永远不出现 Assets → Vulkan 具体。 +// + +#ifndef HELLOVULKAN_VULKANASSETUPLOADER_H +#define HELLOVULKAN_VULKANASSETUPLOADER_H + +#include + +namespace Engine { + + class Device; + class MemoryAllocator; + class DescriptorManager; + + class VulkanAssetUploader final : public Assets::IAssetUploader { + public: + VulkanAssetUploader(Device& device, MemoryAllocator& allocator, + DescriptorManager& descriptors, + vk::DescriptorSetLayout textureLayout) + : m_Device(device), m_Allocator(allocator), + m_Descriptors(descriptors), m_TextureLayout(textureLayout) {} + + Scope LoadModel(const std::string& path) override; + Scope CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) override; + + private: + Device& m_Device; + MemoryAllocator& m_Allocator; + DescriptorManager& m_Descriptors; + vk::DescriptorSetLayout m_TextureLayout; + }; + +} // namespace Engine + +#endif //HELLOVULKAN_VULKANASSETUPLOADER_H diff --git a/Engine/src/Assets/AssetManager.cpp b/Engine/src/Assets/AssetManager.cpp index 08c8ea2..9ad6caf 100644 --- a/Engine/src/Assets/AssetManager.cpp +++ b/Engine/src/Assets/AssetManager.cpp @@ -3,41 +3,31 @@ // #include -#include -#include -#include #include namespace Engine::Assets { - AssetManager::AssetManager(Device& device, MemoryAllocator& allocator, - DescriptorManager& descriptors, - vk::DescriptorSetLayout textureLayout) - : m_Device(device), m_Allocator(allocator), - m_Descriptors(descriptors), m_TextureLayout(textureLayout) + AssetManager::AssetManager(IAssetUploader& uploader) + : m_Uploader(uploader) { ENGINE_INFO("AssetManager created"); } - ModelHandle AssetManager::LoadModel(const std::string& path) + ModelRef AssetManager::LoadModel(const std::string& path) { - return m_Models.Load(path, [this](const std::string& p) -> Scope { - // GPU 上传在此发生:资产层调用渲染层接口完成 buffer/image 创建 - return CreateScope( - m_Device, m_Allocator, m_Descriptors, m_TextureLayout, p); - }); + // GPU 上传经接口发生在渲染层实现里,本层只管缓存与句柄 + return m_Models.Load(path, + [this](const std::string& p) { return m_Uploader.LoadModel(p); }); } - ModelHandle AssetManager::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) + ModelRef AssetManager::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) { // 程序化资产复用同一缓存:键 = 伪路径 + 参数 std::string key = "procedural://plane/" + std::to_string(halfSize) + "/" + std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b); - return m_Models.Load(key, [&](const std::string&) -> Scope { - return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors, - m_TextureLayout, halfSize, r, g, b); - }); + return m_Models.Load(key, + [&](const std::string&) { return m_Uploader.CreatePlane(halfSize, r, g, b); }); } } // namespace Engine::Assets diff --git a/Engine/src/Core/Application.cpp b/Engine/src/Core/Application.cpp index 44a7481..397a8fd 100644 --- a/Engine/src/Core/Application.cpp +++ b/Engine/src/Core/Application.cpp @@ -30,13 +30,10 @@ namespace Engine { m_Scene = CreateScope(); m_Renderer = CreateScope(*m_Window); - // 资产层:复用渲染器的 GPU 上下文; + // 资产层:上传服务由渲染层提供(IAssetUploader 实现), + // 资产层不感知 Vulkan 具体类。 // 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块) - m_Assets = CreateScope( - m_Renderer->GetDevice(), - m_Renderer->GetAllocator(), - m_Renderer->GetDescriptorManager(), - m_Renderer->GetTextureSetLayout()); + m_Assets = CreateScope(m_Renderer->GetAssetUploader()); } Application::~Application() diff --git a/Engine/src/Renderer/Passes/BloomPass.cpp b/Engine/src/Renderer/Passes/BloomPass.cpp new file mode 100644 index 0000000..93532fa --- /dev/null +++ b/Engine/src/Renderer/Passes/BloomPass.cpp @@ -0,0 +1,75 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include + +namespace Engine::Passes +{ + BloomPass::BloomPass(FullscreenGpu& gpu, PostProcessParams& params, + RG::TextureHandle hdrColor, + RG::TextureHandle bloomA, RG::TextureHandle bloomB) + : m_Gpu(gpu), m_Params(params), + m_HDR(hdrColor), m_A(bloomA), m_B(bloomB) + { + m_ExtractSets.resize(kMaxFramesInFlight); + m_BlurHSets.resize(kMaxFramesInFlight); + m_BlurVSets.resize(kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_ExtractSets[slot] = m_Gpu.AllocateSet(); + m_BlurHSets[slot] = m_Gpu.AllocateSet(); + m_BlurVSets[slot] = m_Gpu.AllocateSet(); + } + } + + void BloomPass::Register(RG::RenderGraph& rg) + { + // Extract:HDR → 半分辨率 A(软阈值由 frag 的 param0 传入) + rg.AddPass("BloomExtract", + [this](RG::RenderGraphContext& ctx) { + m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.extractFrag, + m_ExtractSets[ctx.frame.frameSlot], + FullscreenPush{0.0f, m_Params.bloomThreshold, 0.0f, 0.0f}); + }) + .Read(m_HDR, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Write(m_A, RG::ResourceUse::ColorAttachment); + + // BlurH:A → B(水平步长经 push 传入) + rg.AddPass("BloomBlurH", + [this](RG::RenderGraphContext& ctx) { + m_Gpu.RecordFullscreen(ctx, ctx.View(m_B), m_Gpu.blurFrag, + m_BlurHSets[ctx.frame.frameSlot], + FullscreenPush{0.0f, + 1.0f / ctx.extent.width, 0.0f, + 0.0f}); + }) + .Read(m_A, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Write(m_B, RG::ResourceUse::ColorAttachment); + + // BlurV:B → A(垂直步长) + rg.AddPass("BloomBlurV", + [this](RG::RenderGraphContext& ctx) { + m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.blurFrag, + m_BlurVSets[ctx.frame.frameSlot], + FullscreenPush{0.0f, 0.0f, + 1.0f / ctx.extent.height, + 0.0f}); + }) + .Read(m_B, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Write(m_A, RG::ResourceUse::ColorAttachment); + } + + void BloomPass::OnTargetsRebuilt(RG::RenderGraph& rg) + { + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_Gpu.UpdateSetInput(m_ExtractSets[slot], 0, rg, m_HDR, slot); + m_Gpu.UpdateSetInput(m_BlurHSets[slot], 0, rg, m_A, slot); + m_Gpu.UpdateSetInput(m_BlurVSets[slot], 0, rg, m_B, slot); + } + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/ForwardPass.cpp b/Engine/src/Renderer/Passes/ForwardPass.cpp new file mode 100644 index 0000000..beddd28 --- /dev/null +++ b/Engine/src/Renderer/Passes/ForwardPass.cpp @@ -0,0 +1,135 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include +#include +#include + +#include + +namespace Engine::Passes +{ + void ForwardPass::Register(RG::RenderGraph& rg) + { + rg.AddPass("Forward", [this](RG::RenderGraphContext& ctx) { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::ClearValue clearValue; + clearValue.color = vk::ClearColorValue( + std::array{0.1f, 0.1f, 0.1f, 0.0f}); + + vk::RenderingAttachmentInfo colorAttachment; + colorAttachment.setImageView(ctx.View(m_HDRColor)) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(clearValue); + + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_Depth)) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eDontCare) + .setClearValue(vk::ClearValue( + vk::ClearDepthStencilValue(1.0f, 0))); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, ctx.extent}) + .setLayerCount(1) + .setColorAttachments(colorAttachment) + .setPDepthAttachment(&depthAttachment); + + cmd.beginRendering(renderingInfo); + + // 绑定 shader object + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = {m_Gpu.forwardVert, m_Gpu.forwardFrag}; + cmd.bindShadersEXT(stages, shaders); + + // Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图 + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_Gpu.forwardLayout, 0, + m_Gpu.uboSets[ctx.frame.frameSlot], {}); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_Gpu.forwardLayout, 2, + m_Gpu.shadowSets[ctx.frame.frameSlot], {}); + + // ---- 动态状态 ---- + cmd.setRasterizerDiscardEnable(VK_FALSE); + + cmd.setDepthTestEnable(VK_TRUE); + cmd.setDepthWriteEnable(VK_TRUE); + cmd.setDepthCompareOp(vk::CompareOp::eLess); + cmd.setDepthBoundsTestEnable(VK_FALSE); + + cmd.setStencilTestEnable(VK_FALSE); + cmd.setDepthBiasEnable(VK_FALSE); + cmd.setPolygonModeEXT(vk::PolygonMode::eFill); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(vk::FrontFace::eCounterClockwise); + cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); + cmd.setPrimitiveRestartEnable(VK_FALSE); + + auto binding = Vertex::GetBindingDescription(); + auto attributes = Vertex::GetAttributeDescriptions(); + cmd.setVertexInputEXT(binding, attributes); + + cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); + vk::SampleMask sampleMask = 0xFFFFFFFF; + cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); + cmd.setAlphaToCoverageEnableEXT(VK_FALSE); + cmd.setAlphaToOneEnableEXT(VK_FALSE); + + vk::Viewport viewport(0.0f, 0.0f, + static_cast(ctx.extent.width), + static_cast(ctx.extent.height), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); + + vk::Rect2D scissor({0, 0}, ctx.extent); + cmd.setScissorWithCount(scissor); + + vk::Bool32 blendEnable = VK_FALSE; + cmd.setColorBlendEnableEXT(0, blendEnable); + + vk::ColorBlendEquationEXT blendEquation; + blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) + .setDstColorBlendFactor(vk::BlendFactor::eZero) + .setColorBlendOp(vk::BlendOp::eAdd) + .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) + .setDstAlphaBlendFactor(vk::BlendFactor::eZero) + .setAlphaBlendOp(vk::BlendOp::eAdd); + cmd.setColorBlendEquationEXT(0, blendEquation); + + vk::ColorComponentFlags colorWriteMask = + vk::ColorComponentFlagBits::eR | + vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | + vk::ColorComponentFlagBits::eA; + cmd.setColorWriteMaskEXT(0, colorWriteMask); + + // ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)---- + // 实例矩阵已由 Renderer 写入 ObjectBuffer(per-slot SSBO); + // 同 mesh 的 N 个可见实体合并为一次 drawIndexed(instanceCount=N), + // draw 数 = 可见唯一 mesh 数,与实体数量解耦 + for (const auto& batch : m_Frame.mainView.batches) { + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_Gpu.forwardLayout, 1, + batch.mesh->GetMaterial()->GetDescriptorSet(), {}); + batch.mesh->Draw(cmd, batch.count, batch.first); + } + + cmd.endRendering(); + }) + .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) + .Write(m_Depth, RG::ResourceUse::DepthAttachment) + .Read(m_ShadowMap, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment); + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/FullscreenGpu.cpp b/Engine/src/Renderer/Passes/FullscreenGpu.cpp new file mode 100644 index 0000000..4bae26f --- /dev/null +++ b/Engine/src/Renderer/Passes/FullscreenGpu.cpp @@ -0,0 +1,174 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include +#include +#include +#include + +#include + +namespace Engine::Passes +{ + FullscreenGpu::FullscreenGpu(Device& device, DescriptorManager& descriptors, + ShaderManager& shaders) + : m_Device(device), m_Descriptors(descriptors) + { + // Set0 = {HDR 颜色图, Bloom 图},push = 16B 通用结构 + std::vector bindings = { + {0, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}, + {1, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}, + }; + setLayout = descriptors.CreateLayout(bindings); + + layout = descriptors.CreatePipelineLayout( + std::vector{setLayout}, + static_cast(sizeof(FullscreenPush))); + + sampler = device.GetDevice().createSampler( + vk::SamplerCreateInfo{} + .setMagFilter(vk::Filter::eLinear) + .setMinFilter(vk::Filter::eLinear) + .setMipmapMode(vk::SamplerMipmapMode::eNearest) + .setAddressModeU(vk::SamplerAddressMode::eClampToEdge) + .setAddressModeV(vk::SamplerAddressMode::eClampToEdge) + .setAddressModeW(vk::SamplerAddressMode::eClampToEdge)); + + const std::string shaderDir = "shaders/"; + auto vertShader = shaders.CreateFromFile( + shaderDir + "tonemap.vert.spv", + vk::ShaderStageFlagBits::eVertex, + std::vector{setLayout}, + static_cast(sizeof(FullscreenPush))); + auto tonemapF = shaders.CreateFromFile( + shaderDir + "tonemap.frag.spv", + vk::ShaderStageFlagBits::eFragment, + std::vector{setLayout}, + static_cast(sizeof(FullscreenPush))); + auto extractF = shaders.CreateFromFile( + shaderDir + "bloom_extract.frag.spv", + vk::ShaderStageFlagBits::eFragment, + std::vector{setLayout}, + static_cast(sizeof(FullscreenPush))); + auto blurF = shaders.CreateFromFile( + shaderDir + "bloom_blur.frag.spv", + vk::ShaderStageFlagBits::eFragment, + std::vector{setLayout}, + static_cast(sizeof(FullscreenPush))); + + vert = vertShader.Handle; + tonemapFrag = tonemapF.Handle; + extractFrag = extractF.Handle; + blurFrag = blurF.Handle; + + if (!vert || !tonemapFrag || !extractFrag || !blurFrag) { + ENGINE_FATAL("FullscreenGpu: failed to create shader objects"); + } + } + + FullscreenGpu::~FullscreenGpu() + { + const auto& vkDevice = m_Device.GetDevice(); + + if (vert) vkDevice.destroyShaderEXT(vert); + if (tonemapFrag) vkDevice.destroyShaderEXT(tonemapFrag); + if (extractFrag) vkDevice.destroyShaderEXT(extractFrag); + if (blurFrag) vkDevice.destroyShaderEXT(blurFrag); + + vkDevice.destroySampler(sampler); + vkDevice.destroyPipelineLayout(layout); + vkDevice.destroyDescriptorSetLayout(setLayout); + } + + vk::DescriptorSet FullscreenGpu::AllocateSet() + { + return m_Descriptors.AllocateDescriptorSet(setLayout); + } + + void FullscreenGpu::UpdateSetInput(vk::DescriptorSet set, u32 binding, + RG::RenderGraph& rg, RG::TextureHandle input, + u32 slot) + { + // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view + m_Descriptors.UpdateTexture(set, binding, sampler, + rg.GetPhysicalView(input, slot)); + } + + void FullscreenGpu::RecordFullscreen(RG::RenderGraphContext& ctx, + vk::ImageView view, + vk::ShaderEXT frag, + const vk::DescriptorSet& set, + const FullscreenPush& push) + { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::ClearValue clearValue; + clearValue.color = vk::ClearColorValue( + std::array{0.0f, 0.0f, 0.0f, 1.0f}); + + vk::RenderingAttachmentInfo colorAttachment; + colorAttachment.setImageView(view) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(clearValue); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, ctx.extent}) + .setLayerCount(1) + .setColorAttachments(colorAttachment); + + cmd.beginRendering(renderingInfo); + + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = {vert, frag}; + cmd.bindShadersEXT(stages, shaders); + + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + layout, 0, set, {}); + + // ---- 动态状态(无深度附件、无顶点输入)---- + cmd.setRasterizerDiscardEnable(VK_FALSE); + cmd.setDepthTestEnable(VK_FALSE); + cmd.setDepthWriteEnable(VK_FALSE); + cmd.setDepthBoundsTestEnable(VK_FALSE); + cmd.setStencilTestEnable(VK_FALSE); + cmd.setDepthBiasEnable(VK_FALSE); + cmd.setPolygonModeEXT(vk::PolygonMode::eFill); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(vk::FrontFace::eCounterClockwise); + cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); + cmd.setPrimitiveRestartEnable(VK_FALSE); + cmd.setVertexInputEXT( + std::array{}, + std::array{}); + cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); + vk::SampleMask sampleMask = 0xFFFFFFFF; + cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); + cmd.setAlphaToCoverageEnableEXT(VK_FALSE); + cmd.setAlphaToOneEnableEXT(VK_FALSE); + + vk::Viewport viewport(0.0f, 0.0f, + static_cast(ctx.extent.width), + static_cast(ctx.extent.height), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); + cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent)); + + cmd.pushConstants(layout, + vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment, + 0, sizeof(FullscreenPush), &push); + + cmd.draw(3, 1, 0, 0); + cmd.endRendering(); + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/ImGuiPass.cpp b/Engine/src/Renderer/Passes/ImGuiPass.cpp new file mode 100644 index 0000000..a65912e --- /dev/null +++ b/Engine/src/Renderer/Passes/ImGuiPass.cpp @@ -0,0 +1,36 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include + +namespace Engine::Passes +{ + void ImGuiPass::Register(RG::RenderGraph& rg) + { + rg.AddPass("ImGui", [this](RG::RenderGraphContext& ctx) { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::RenderingAttachmentInfo colorAttachment; + colorAttachment.setImageView(ctx.View(m_Backbuffer)) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eLoad) // 关键:叠加 + .setStoreOp(vk::AttachmentStoreOp::eStore); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, ctx.extent}) + .setLayerCount(1) + .setColorAttachments(colorAttachment); + + cmd.beginRendering(renderingInfo); + + if (*m_Layer) { + (*m_Layer)->RenderDrawData(cmd); + } + + cmd.endRendering(); + }).Write(m_Backbuffer, RG::ResourceUse::ColorAttachment); + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp new file mode 100644 index 0000000..f78c770 --- /dev/null +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -0,0 +1,139 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include +#include +#include +#include +#include + +#include + +namespace Engine::Passes +{ + SceneGpu::SceneGpu(Device& device, DescriptorManager& descriptors, + ShaderManager& shaders, + UniformBuffer& ubo, UniformBuffer& objectBuffer) + : m_Device(device), m_Descriptors(descriptors) + { + // Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取) + std::vector uboBindings = { + {0, vk::DescriptorType::eUniformBuffer, + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, + {1, vk::DescriptorType::eStorageBuffer, + vk::ShaderStageFlagBits::eVertex}, + }; + uboSetLayout = descriptors.CreateLayout(uboBindings); + + // Set 1: texture + std::vector texBindings = { + {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, + }; + textureSetLayout = descriptors.CreateLayout(texBindings); + + // Set 2: shadow map(fragment 采样) + std::vector shadowBindings = { + {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, + }; + shadowSetLayout = descriptors.CreateLayout(shadowBindings); + + // Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO) + forwardLayout = descriptors.CreatePipelineLayout( + {uboSetLayout, textureSetLayout, shadowSetLayout}, 0); + + // Shadow pipeline layout:Set0 = UBO/ObjectBuffer(实例矩阵), + // push = lightVP 64B + shadowLayout = descriptors.CreatePipelineLayout( + std::vector{uboSetLayout}, + sizeof(glm::mat4)); + + const std::string shaderDir = "shaders/"; + // forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878) + auto vert = shaders.CreateFromFile( + shaderDir + "simple_shader.vert.spv", + vk::ShaderStageFlagBits::eVertex, + {uboSetLayout, textureSetLayout, shadowSetLayout}, 0); + auto frag = shaders.CreateFromFile( + shaderDir + "simple_shader.frag.spv", + vk::ShaderStageFlagBits::eFragment, + {uboSetLayout, textureSetLayout, shadowSetLayout}, 0); + + // shadow shader:depth-only,push = lightVP + auto shadowVertShader = shaders.CreateFromFile( + shaderDir + "shadow.vert.spv", + vk::ShaderStageFlagBits::eVertex, + std::vector{uboSetLayout}, + sizeof(glm::mat4)); + auto shadowFragShader = shaders.CreateFromFile( + shaderDir + "shadow.frag.spv", + vk::ShaderStageFlagBits::eFragment, + std::vector{uboSetLayout}, + sizeof(glm::mat4)); + + forwardVert = vert.Handle; + forwardFrag = frag.Handle; + shadowVert = shadowVertShader.Handle; + shadowFrag = shadowFragShader.Handle; + + if (!forwardVert || !forwardFrag || !shadowVert || !shadowFrag) { + ENGINE_FATAL("SceneGpu: failed to create shader objects"); + } + + // 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影 + vk::SamplerCreateInfo shadowSamplerInfo; + shadowSamplerInfo.setMagFilter(vk::Filter::eLinear) + .setMinFilter(vk::Filter::eLinear) + .setMipmapMode(vk::SamplerMipmapMode::eNearest) + .setAddressModeU(vk::SamplerAddressMode::eClampToBorder) + .setAddressModeV(vk::SamplerAddressMode::eClampToBorder) + .setAddressModeW(vk::SamplerAddressMode::eClampToBorder) + .setBorderColor(vk::BorderColor::eFloatOpaqueWhite) + .setUnnormalizedCoordinates(VK_FALSE) + .setCompareEnable(VK_FALSE); + shadowSampler = device.GetDevice().createSampler(shadowSamplerInfo); + + // per-slot 描述符分配 + Set0 绑定(buffer 句柄全程稳定,不用重绑) + uboSets.resize(kMaxFramesInFlight); + shadowSets.resize(kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + uboSets[slot] = descriptors.AllocateDescriptorSet(uboSetLayout); + descriptors.UpdateUniformBuffer( + uboSets[slot], 0, ubo.Get(slot), ubo.GetSize()); + descriptors.UpdateStorageBuffer( + uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize()); + + shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout); + } + // shadowSets 的图片绑定在 Compile 后由 BindShadowMap 完成 + } + + SceneGpu::~SceneGpu() + { + const auto& vkDevice = m_Device.GetDevice(); + + if (forwardVert) vkDevice.destroyShaderEXT(forwardVert); + if (forwardFrag) vkDevice.destroyShaderEXT(forwardFrag); + if (shadowVert) vkDevice.destroyShaderEXT(shadowVert); + if (shadowFrag) vkDevice.destroyShaderEXT(shadowFrag); + + vkDevice.destroySampler(shadowSampler); + vkDevice.destroyPipelineLayout(forwardLayout); + vkDevice.destroyPipelineLayout(shadowLayout); + vkDevice.destroyDescriptorSetLayout(uboSetLayout); + vkDevice.destroyDescriptorSetLayout(textureSetLayout); + vkDevice.destroyDescriptorSetLayout(shadowSetLayout); + } + + void SceneGpu::BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap) + { + // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_Descriptors.UpdateTexture( + shadowSets[slot], 0, shadowSampler, + rg.GetPhysicalView(shadowMap, slot)); + } + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/ShadowPass.cpp b/Engine/src/Renderer/Passes/ShadowPass.cpp new file mode 100644 index 0000000..7c73f17 --- /dev/null +++ b/Engine/src/Renderer/Passes/ShadowPass.cpp @@ -0,0 +1,104 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include +#include + +#include + +namespace Engine::Passes +{ + void ShadowPass::Register(RG::RenderGraph& rg) + { + // 所有渲染体都写入,不做实体级开关——castShadow 应由材质/实体 + // 数据驱动剔除,而非 pass 逻辑特判 + rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_ShadowMap)) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(vk::ClearValue( + vk::ClearDepthStencilValue(1.0f, 0))); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) + .setLayerCount(1) + .setPDepthAttachment(&depthAttachment); + + cmd.beginRendering(renderingInfo); + + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = {m_Gpu.shadowVert, m_Gpu.shadowFrag}; + cmd.bindShadersEXT(stages, shaders); + + // ---- 动态状态(depth-only)---- + cmd.setRasterizerDiscardEnable(VK_FALSE); + + cmd.setDepthTestEnable(VK_TRUE); + cmd.setDepthWriteEnable(VK_TRUE); + cmd.setDepthCompareOp(vk::CompareOp::eLess); + cmd.setDepthBoundsTestEnable(VK_FALSE); + + cmd.setStencilTestEnable(VK_FALSE); + // 深度 bias 压平 shadow acne(NDC 量级) + cmd.setDepthBiasEnable(VK_TRUE); + cmd.setDepthBias(0.0025f, 0.0f, 0.003f); + cmd.setPolygonModeEXT(vk::PolygonMode::eFill); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(vk::FrontFace::eCounterClockwise); + cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); + cmd.setPrimitiveRestartEnable(VK_FALSE); + + auto binding = Vertex::GetBindingDescription(); + auto attributes = Vertex::GetAttributeDescriptions(); + cmd.setVertexInputEXT(binding, attributes); + + cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); + vk::SampleMask sampleMask = 0xFFFFFFFF; + cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); + cmd.setAlphaToCoverageEnableEXT(VK_FALSE); + cmd.setAlphaToOneEnableEXT(VK_FALSE); + + vk::Viewport viewport(0.0f, 0.0f, + static_cast(kShadowMapSize), + static_cast(kShadowMapSize), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); + + vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); + cmd.setScissorWithCount(scissor); + + // ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用 + // 光源视锥剔除后的 batch(相机外实体仍投影子)---- + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_Gpu.shadowLayout, 0, + m_Gpu.uboSets[ctx.frame.frameSlot], {}); + + // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) + cmd.pushConstants(m_Gpu.shadowLayout, + vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment, + 0, sizeof(glm::mat4), &m_Frame.ubo.lightSpace); + + for (const auto& batch : m_Frame.shadowView.batches) { + batch.mesh->Draw(cmd, batch.count, batch.first); + } + + cmd.endRendering(); + }).Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); + } + + void ShadowPass::OnTargetsRebuilt(RG::RenderGraph& rg) + { + m_Gpu.BindShadowMap(rg, m_ShadowMap); + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Passes/TonemapPass.cpp b/Engine/src/Renderer/Passes/TonemapPass.cpp new file mode 100644 index 0000000..dcccf43 --- /dev/null +++ b/Engine/src/Renderer/Passes/TonemapPass.cpp @@ -0,0 +1,48 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include + +namespace Engine::Passes +{ + TonemapPass::TonemapPass(FullscreenGpu& gpu, PostProcessParams& params, + RG::TextureHandle hdrColor, RG::TextureHandle bloomA, + RG::TextureHandle backbuffer) + : m_Gpu(gpu), m_Params(params), + m_HDR(hdrColor), m_A(bloomA), m_Backbuffer(backbuffer) + { + m_Sets.resize(kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_Sets[slot] = m_Gpu.AllocateSet(); + } + } + + void TonemapPass::Register(RG::RenderGraph& rg) + { + rg.AddPass("Tonemap", + [this](RG::RenderGraphContext& ctx) { + m_Gpu.RecordFullscreen(ctx, ctx.View(m_Backbuffer), + m_Gpu.tonemapFrag, + m_Sets[ctx.frame.frameSlot], + FullscreenPush{m_Params.exposure, + m_Params.useACES ? 1.0f : 0.0f, + m_Params.bloomStrength, 0.0f}); + }) + .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) + .Read(m_HDR, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_A, RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment); + } + + void TonemapPass::OnTargetsRebuilt(RG::RenderGraph& rg) + { + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot); + m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_A, slot); + } + } + +} // namespace Engine::Passes diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 688695c..f005b87 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -1,14 +1,23 @@ // // Created by atdunbg on 2026/9/20. // +// Renderer:编排者。pass 实现在 Passes::*,场景理解在 SceneRenderer, +// 本文件只做装配、资源声明与每帧编排——新增渲染效果不需要改这里 +// (除非要加新的 RG 资源或注册新 pass)。 +// #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -19,215 +28,27 @@ #include #include #include -#include -#include +#include +#include #include -#include #include #include namespace Engine { - using namespace ECS; // TransformComponent / Entity 等直接使用 - - namespace - { - struct Light { - glm::vec4 direction; // xyz = 方向,w 未用 - glm::vec4 color; // rgb = 颜色,a = 强度 - }; - - struct UniformBufferObject { - glm::mat4 view; - glm::mat4 proj; - glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵) - glm::vec4 ambientColor; - Light lights[4]; - }; - - constexpr u32 kUboBinding = 0; - constexpr u32 kObjectBinding = 1; - constexpr u32 kUBOSetIndex = 0; - constexpr u32 kTextureSetIndex = 1; - constexpr u32 kShadowSetIndex = 2; - constexpr u32 kMaxLights = 4; - constexpr u32 kShadowMapSize = 2048; - - // 全屏 pass 通用 push(16B):所有全屏 pass(Tonemap/Bloom 系) - // 统一声明与推送,规避 VUID-08878 的"range 定义必须一致" - struct FullscreenPush { - f32 exposure; - f32 param0; // 各 pass 自定语义(tonemap: aces 开关) - f32 param1; - f32 param2; - }; - constexpr u32 kFullscreenPushSize = static_cast(sizeof(FullscreenPush)); - - // 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应) - constexpr f32 kLightOrthoExtent = 15.0f; - constexpr f32 kLightDistance = 30.0f; - constexpr f32 kLightNear = 1.0f; - constexpr f32 kLightFar = 80.0f; - } - Renderer::Renderer(Window& window) : m_Window(window) { - // 1. Device + // 1. Device / Allocator / Descriptor / Shader m_Device = CreateScope(window); - - // 2. MemoryAllocator m_Allocator = CreateScope(*m_Device); - - // 3. DescriptorManager m_DescriptorManager = CreateScope(*m_Device); - - // 4. Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取) - std::vector uboBindings = { - {kUboBinding, vk::DescriptorType::eUniformBuffer, - vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, - {kObjectBinding, vk::DescriptorType::eStorageBuffer, - vk::ShaderStageFlagBits::eVertex}, - }; - m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings); - - // 5. Set 1: texture - std::vector texBindings = { - {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, - }; - m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings); - - // 5.5 Set 2: shadow map(fragment 采样) - std::vector shadowBindings = { - {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, - }; - m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings); - - // 6. PipelineLayout:三个 set;forward 无 push(GPUDriven 经 SSBO) - m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout( - {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); - - // Shadow pass layout:Set0 = UBO/ObjectBuffer(实例矩阵),push = lightVP - m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout( - std::vector{m_UBOSetLayout}, - sizeof(glm::mat4)); - - // 7. ShaderManager(VK_EXT_shader_object) m_ShaderManager = CreateScope(*m_Device); - std::string shaderDir = "shaders/"; - // forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878) - auto vert = m_ShaderManager->CreateFromFile( - shaderDir + "simple_shader.vert.spv", - vk::ShaderStageFlagBits::eVertex, - {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); - auto frag = m_ShaderManager->CreateFromFile( - shaderDir + "simple_shader.frag.spv", - vk::ShaderStageFlagBits::eFragment, - {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); - - m_VertShader = vert.Handle; - m_FragShader = frag.Handle; - - // shadow shader:depth-only,无 set - auto shadowVert = m_ShaderManager->CreateFromFile( - shaderDir + "shadow.vert.spv", - vk::ShaderStageFlagBits::eVertex, - std::vector{m_UBOSetLayout}, - sizeof(glm::mat4)); - auto shadowFrag = m_ShaderManager->CreateFromFile( - shaderDir + "shadow.frag.spv", - vk::ShaderStageFlagBits::eFragment, - std::vector{m_UBOSetLayout}, - sizeof(glm::mat4)); - - m_ShadowVertShader = shadowVert.Handle; - m_ShadowFragShader = shadowFrag.Handle; - - if (!m_VertShader || !m_FragShader || !m_ShadowVertShader || !m_ShadowFragShader) { - ENGINE_FATAL("Failed to create shader objects"); - } - - // 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影(省 shader 出界分支) - vk::SamplerCreateInfo shadowSamplerInfo; - shadowSamplerInfo.setMagFilter(vk::Filter::eLinear) - .setMinFilter(vk::Filter::eLinear) - .setMipmapMode(vk::SamplerMipmapMode::eNearest) - .setAddressModeU(vk::SamplerAddressMode::eClampToBorder) - .setAddressModeV(vk::SamplerAddressMode::eClampToBorder) - .setAddressModeW(vk::SamplerAddressMode::eClampToBorder) - .setBorderColor(vk::BorderColor::eFloatOpaqueWhite) - .setUnnormalizedCoordinates(VK_FALSE) - .setCompareEnable(VK_FALSE); - m_ShadowSampler = m_Device->GetDevice().createSampler(shadowSamplerInfo); - - // 7.5 全屏后处理装配(Tonemap + Bloom 共用一套 layout/sampler): - // Set0 = {HDR 颜色图, Bloom 图},push = 16B 通用结构 - std::vector fullscreenBindings = { - {0, vk::DescriptorType::eCombinedImageSampler, - vk::ShaderStageFlagBits::eFragment}, - {1, vk::DescriptorType::eCombinedImageSampler, - vk::ShaderStageFlagBits::eFragment}, - }; - m_TonemapSetLayout = m_DescriptorManager->CreateLayout(fullscreenBindings); - - m_TonemapPipelineLayout = m_DescriptorManager->CreatePipelineLayout( - std::vector{m_TonemapSetLayout}, - kFullscreenPushSize); - - m_TonemapSampler = m_Device->GetDevice().createSampler( - vk::SamplerCreateInfo{} - .setMagFilter(vk::Filter::eLinear) - .setMinFilter(vk::Filter::eLinear) - .setMipmapMode(vk::SamplerMipmapMode::eNearest) - .setAddressModeU(vk::SamplerAddressMode::eClampToEdge) - .setAddressModeV(vk::SamplerAddressMode::eClampToEdge) - .setAddressModeW(vk::SamplerAddressMode::eClampToEdge)); - - auto tonemapVert = m_ShaderManager->CreateFromFile( - shaderDir + "tonemap.vert.spv", - vk::ShaderStageFlagBits::eVertex, - std::vector{m_TonemapSetLayout}, - kFullscreenPushSize); - auto tonemapFrag = m_ShaderManager->CreateFromFile( - shaderDir + "tonemap.frag.spv", - vk::ShaderStageFlagBits::eFragment, - std::vector{m_TonemapSetLayout}, - kFullscreenPushSize); - auto bloomExtractFrag = m_ShaderManager->CreateFromFile( - shaderDir + "bloom_extract.frag.spv", - vk::ShaderStageFlagBits::eFragment, - std::vector{m_TonemapSetLayout}, - kFullscreenPushSize); - auto bloomBlurFrag = m_ShaderManager->CreateFromFile( - shaderDir + "bloom_blur.frag.spv", - vk::ShaderStageFlagBits::eFragment, - std::vector{m_TonemapSetLayout}, - kFullscreenPushSize); - - m_TonemapVertShader = tonemapVert.Handle; - m_TonemapFragShader = tonemapFrag.Handle; - m_BloomExtractFrag = bloomExtractFrag.Handle; - m_BloomBlurFrag = bloomBlurFrag.Handle; - - if (!m_TonemapVertShader || !m_TonemapFragShader || - !m_BloomExtractFrag || !m_BloomBlurFrag) { - ENGINE_FATAL("Failed to create fullscreen shader objects"); - } - - // 8. Swapchain(只管呈现;深度图由 RenderGraph 创建) - m_Swapchain = CreateScope(*m_Device, *m_Allocator, m_Window); - m_Extent = m_Swapchain->GetExtent(); - - // 9. Sync / Command(per-frame) - m_SyncManager = CreateScope( - *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); - m_CommandManager = CreateScope(*m_Device, kMaxFramesInFlight); - - // 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring - // (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致) + // 2. Per-frame ring:UBO + GPUDriven 实例 SSBO(同一 ring 类, + // SSBO 只是 usage 不同;正确性保证一致) m_UniformBuffer = CreateScope( *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); m_ObjectBuffer = CreateScope( @@ -235,44 +56,40 @@ namespace Engine sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight, vk::BufferUsageFlagBits::eStorageBuffer); - m_UBODescriptorSets.resize(kMaxFramesInFlight); - m_ShadowDescriptorSets.resize(kMaxFramesInFlight); - m_TonemapDescriptorSets.resize(kMaxFramesInFlight); - m_BloomExtractSets.resize(kMaxFramesInFlight); - m_BloomBlurHSets.resize(kMaxFramesInFlight); - m_BloomBlurVSets.resize(kMaxFramesInFlight); - for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { - m_UBODescriptorSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout); - m_DescriptorManager->UpdateUniformBuffer( - m_UBODescriptorSets[slot], kUboBinding, - m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize()); - m_DescriptorManager->UpdateStorageBuffer( - m_UBODescriptorSets[slot], kObjectBinding, - m_ObjectBuffer->Get(slot), m_ObjectBuffer->GetSize()); + // 3. pass 装配体:布局/shader/采样器/per-slot 描述符 + m_SceneGpu = CreateScope( + *m_Device, *m_DescriptorManager, *m_ShaderManager, + *m_UniformBuffer, *m_ObjectBuffer); + m_FullscreenGpu = CreateScope( + *m_Device, *m_DescriptorManager, *m_ShaderManager); - m_ShadowDescriptorSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout); + // 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph) + m_Swapchain = CreateScope(*m_Device, *m_Allocator, m_Window); + m_Extent = m_Swapchain->GetExtent(); - m_TonemapDescriptorSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); + // 5. Sync / Command(per-frame) + m_SyncManager = CreateScope( + *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); + m_CommandManager = CreateScope(*m_Device, kMaxFramesInFlight); - m_BloomExtractSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); - m_BloomBlurHSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); - m_BloomBlurVSets[slot] = - m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); - } + // 6. 场景翻译层(pass 经引用读取 FrameSceneData,必须先于 BuildGraph) + m_SceneRenderer = CreateScope(); - // 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定) + // 7. 资产上传服务(渲染层对外提供的 IAssetUploader 实现) + m_AssetUploader = CreateScope( + *m_Device, *m_Allocator, *m_DescriptorManager, + m_SceneGpu->textureSetLayout); + + // 8. RenderGraph:资源声明 + pass 注册 + 首次编译 + m_PostParams = CreateScope(); BuildGraph(); - // Compile 已分配物理镜像:把各槽位的阴影图/HDR view 绑进描述符 - UpdateShadowDescriptors(); - UpdateFullscreenDescriptors(); + // 首次编译后物理镜像就位:各 pass 绑定自己的输入描述符 + for (auto& pass : m_Passes) { + pass->OnTargetsRebuilt(*m_RenderGraph); + } - // 12. GPU timestamp:每 pass 两枚(前后),按帧槽位分区 + // 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区 m_QueriesPerFrame = static_cast(m_RenderGraph->GetPassCount()) * 2; m_PassNames = m_RenderGraph->GetPassNames(); m_PassGpuMs.assign(m_RenderGraph->GetPassCount(), 0.0f); @@ -290,47 +107,17 @@ namespace Engine Renderer::~Renderer() { + // 必须等 GPU 空闲再析构成员(pass/RG/装配体都会销毁 GPU 对象) m_Device->GetDevice().waitIdle(); - const auto& vkDevice = m_Device->GetDevice(); - - if (m_VertShader) { - vkDevice.destroyShaderEXT(m_VertShader); - } - if (m_FragShader) { - vkDevice.destroyShaderEXT(m_FragShader); - } - if (m_ShadowVertShader) { - vkDevice.destroyShaderEXT(m_ShadowVertShader); - } - if (m_ShadowFragShader) { - vkDevice.destroyShaderEXT(m_ShadowFragShader); - } - if (m_TonemapVertShader) { - vkDevice.destroyShaderEXT(m_TonemapVertShader); - } - if (m_TonemapFragShader) { - vkDevice.destroyShaderEXT(m_TonemapFragShader); - } - if (m_BloomExtractFrag) { - vkDevice.destroyShaderEXT(m_BloomExtractFrag); - } - if (m_BloomBlurFrag) { - vkDevice.destroyShaderEXT(m_BloomBlurFrag); - } - - vkDevice.destroySampler(m_ShadowSampler); - vkDevice.destroySampler(m_TonemapSampler); if (m_QueryPool) { - vkDevice.destroyQueryPool(m_QueryPool); + m_Device->GetDevice().destroyQueryPool(m_QueryPool); } - vkDevice.destroyPipelineLayout(m_PipelineLayout); - vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout); - vkDevice.destroyPipelineLayout(m_TonemapPipelineLayout); - vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout); - vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout); - vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout); - vkDevice.destroyDescriptorSetLayout(m_TonemapSetLayout); + } + + vk::DescriptorSetLayout Renderer::GetTextureSetLayout() const + { + return m_SceneGpu->textureSetLayout; } vk::Format Renderer::GetSwapchainFormat() const @@ -343,11 +130,37 @@ namespace Engine return m_Swapchain->GetImageCount(); } + Assets::IAssetUploader& Renderer::GetAssetUploader() const + { + return *m_AssetUploader; + } + + f32 Renderer::GetExposure() const { return m_PostParams->exposure; } + void Renderer::SetExposure(f32 e) { m_PostParams->exposure = e; } + bool Renderer::GetUseACES() const { return m_PostParams->useACES; } + void Renderer::SetUseACES(bool b) { m_PostParams->useACES = b; } + f32 Renderer::GetBloomThreshold() const { return m_PostParams->bloomThreshold; } + void Renderer::SetBloomThreshold(f32 v) { m_PostParams->bloomThreshold = v; } + f32 Renderer::GetBloomStrength() const { return m_PostParams->bloomStrength; } + void Renderer::SetBloomStrength(f32 v) { m_PostParams->bloomStrength = v; } + + u32 Renderer::GetMainInstanceCount() const + { + return m_SceneRenderer->GetMainInstanceCount(); + } + + u32 Renderer::GetShadowInstanceCount() const + { + return m_SceneRenderer->GetShadowInstanceCount(); + } + void Renderer::BuildGraph() { m_RenderGraph = CreateScope( *m_Device, *m_Allocator, kMaxFramesInFlight); + // ---- 资源声明(命名资源;pass 经句柄引用,数据流由 uses 决定)---- + // 导入 swapchain 颜色附件 m_Backbuffer = m_RenderGraph->ImportSwapchain( "Backbuffer", m_Swapchain->GetFormat()); @@ -368,8 +181,7 @@ namespace Engine RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); // HDR 颜色图:Forward 的输出(线性光照值),Tonemap 显式完成 - // 显示变换后写入 Backbuffer。对齐 UE/Unity 管线: - // SceneColor(HDR) → tonemapper → UI(UI 在 tonemap 之后叠加) + // 显示变换后写入 Backbuffer(对齐 UE/Unity 管线) m_HDRColor = m_RenderGraph->CreateTexture( "HDRColor", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat}); @@ -382,374 +194,24 @@ namespace Engine "BloomB", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f}); - // ---- Shadow pass:光源视角写深度(所有渲染体都写入,不做实体级开关—— - // castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)---- - m_RenderGraph->AddPass("Shadow", - [this](RG::RenderGraphContext& ctx) { - const vk::CommandBuffer cmd = ctx.cmd; + // ---- pass 注册:执行序由拓扑排序决定,与注册顺序无关 ---- + // 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。 + FrameSceneData& frame = m_SceneRenderer->Frame(); + m_Passes.push_back(CreateScope( + *m_SceneGpu, frame, m_ShadowMap)); + m_Passes.push_back(CreateScope( + *m_SceneGpu, frame, m_HDRColor, m_Depth, m_ShadowMap)); + m_Passes.push_back(CreateScope( + *m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA, m_BloomB)); + m_Passes.push_back(CreateScope( + *m_FullscreenGpu, *m_PostParams, m_HDRColor, m_BloomA, m_Backbuffer)); + m_Passes.push_back(CreateScope( + &m_ImGuiLayer, m_Backbuffer)); + m_Passes.push_back(CreateScope(m_Backbuffer)); - vk::RenderingAttachmentInfo depthAttachment; - depthAttachment.setImageView(ctx.View(m_ShadowMap)) - .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(vk::ClearValue( - vk::ClearDepthStencilValue(1.0f, 0))); - - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) - .setLayerCount(1) - .setPDepthAttachment(&depthAttachment); - - cmd.beginRendering(renderingInfo); - - std::array stages = { - vk::ShaderStageFlagBits::eVertex, - vk::ShaderStageFlagBits::eFragment - }; - std::array shaders = {m_ShadowVertShader, m_ShadowFragShader}; - cmd.bindShadersEXT(stages, shaders); - - // ---- 动态状态(depth-only)---- - cmd.setRasterizerDiscardEnable(VK_FALSE); - - cmd.setDepthTestEnable(VK_TRUE); - cmd.setDepthWriteEnable(VK_TRUE); - cmd.setDepthCompareOp(vk::CompareOp::eLess); - cmd.setDepthBoundsTestEnable(VK_FALSE); - - cmd.setStencilTestEnable(VK_FALSE); - // 深度 bias 压平 shadow acne(NDC 量级) - cmd.setDepthBiasEnable(VK_TRUE); - cmd.setDepthBias(0.0025f, 0.0f, 0.003f); - cmd.setPolygonModeEXT(vk::PolygonMode::eFill); - cmd.setCullMode(vk::CullModeFlagBits::eNone); - cmd.setFrontFace(vk::FrontFace::eCounterClockwise); - cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); - cmd.setPrimitiveRestartEnable(VK_FALSE); - - auto binding = Vertex::GetBindingDescription(); - auto attributes = Vertex::GetAttributeDescriptions(); - cmd.setVertexInputEXT(binding, attributes); - - cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); - vk::SampleMask sampleMask = 0xFFFFFFFF; - cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); - cmd.setAlphaToCoverageEnableEXT(VK_FALSE); - cmd.setAlphaToOneEnableEXT(VK_FALSE); - - vk::Viewport viewport(0.0f, 0.0f, - static_cast(kShadowMapSize), - static_cast(kShadowMapSize), - 0.0f, 1.0f); - cmd.setViewportWithCount(viewport); - - vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); - cmd.setScissorWithCount(scissor); - - // ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用 - // 光源视锥剔除后的 batch(相机外实体仍投影子)---- - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_ShadowPipelineLayout, 0, - m_UBODescriptorSets[ctx.frame.frameSlot], {}); - - // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) - cmd.pushConstants(m_ShadowPipelineLayout, - vk::ShaderStageFlagBits::eVertex | - vk::ShaderStageFlagBits::eFragment, - 0, sizeof(glm::mat4), &m_LightSpace); - - for (const auto& batch : m_ShadowView.batches) { - batch.mesh->Draw(cmd, batch.count, batch.first); - } - - cmd.endRendering(); - }) - .Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); - - // ---- Forward pass:直接渲染到 swapchain ---- - // 场景数据每帧经 m_BoundScene 从 ECS 读取 - m_RenderGraph->AddPass("Forward", - [this](RG::RenderGraphContext& ctx) { - const vk::CommandBuffer cmd = ctx.cmd; - - vk::ClearValue clearValue; - clearValue.color = vk::ClearColorValue( - std::array{0.1f, 0.1f, 0.1f, 0.0f}); - - vk::RenderingAttachmentInfo colorAttachment; - colorAttachment.setImageView(ctx.View(m_HDRColor)) - .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(clearValue); - - vk::RenderingAttachmentInfo depthAttachment; - depthAttachment.setImageView(ctx.View(m_Depth)) - .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eDontCare) - .setClearValue(vk::ClearValue( - vk::ClearDepthStencilValue(1.0f, 0))); - - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, ctx.extent}) - .setLayerCount(1) - .setColorAttachments(colorAttachment) - .setPDepthAttachment(&depthAttachment); - - cmd.beginRendering(renderingInfo); - - // 绑定 shader object - std::array stages = { - vk::ShaderStageFlagBits::eVertex, - vk::ShaderStageFlagBits::eFragment - }; - std::array shaders = {m_VertShader, m_FragShader}; - cmd.bindShadersEXT(stages, shaders); - - // Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图 - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, kUBOSetIndex, - m_UBODescriptorSets[ctx.frame.frameSlot], {}); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, kShadowSetIndex, - m_ShadowDescriptorSets[ctx.frame.frameSlot], {}); - - // ---- 动态状态 ---- - cmd.setRasterizerDiscardEnable(VK_FALSE); - - cmd.setDepthTestEnable(VK_TRUE); - cmd.setDepthWriteEnable(VK_TRUE); - cmd.setDepthCompareOp(vk::CompareOp::eLess); - cmd.setDepthBoundsTestEnable(VK_FALSE); - - cmd.setStencilTestEnable(VK_FALSE); - cmd.setDepthBiasEnable(VK_FALSE); - cmd.setPolygonModeEXT(vk::PolygonMode::eFill); - cmd.setCullMode(vk::CullModeFlagBits::eNone); - cmd.setFrontFace(vk::FrontFace::eCounterClockwise); - cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); - cmd.setPrimitiveRestartEnable(VK_FALSE); - - auto binding = Vertex::GetBindingDescription(); - auto attributes = Vertex::GetAttributeDescriptions(); - cmd.setVertexInputEXT(binding, attributes); - - cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); - vk::SampleMask sampleMask = 0xFFFFFFFF; - cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); - cmd.setAlphaToCoverageEnableEXT(VK_FALSE); - cmd.setAlphaToOneEnableEXT(VK_FALSE); - - vk::Viewport viewport(0.0f, 0.0f, - static_cast(ctx.extent.width), - static_cast(ctx.extent.height), - 0.0f, 1.0f); - cmd.setViewportWithCount(viewport); - - vk::Rect2D scissor({0, 0}, ctx.extent); - cmd.setScissorWithCount(scissor); - - vk::Bool32 blendEnable = VK_FALSE; - cmd.setColorBlendEnableEXT(0, blendEnable); - - vk::ColorBlendEquationEXT blendEquation; - blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) - .setDstColorBlendFactor(vk::BlendFactor::eZero) - .setColorBlendOp(vk::BlendOp::eAdd) - .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) - .setDstAlphaBlendFactor(vk::BlendFactor::eZero) - .setAlphaBlendOp(vk::BlendOp::eAdd); - cmd.setColorBlendEquationEXT(0, blendEquation); - - vk::ColorComponentFlags colorWriteMask = - vk::ColorComponentFlagBits::eR | - vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | - vk::ColorComponentFlagBits::eA; - cmd.setColorWriteMaskEXT(0, colorWriteMask); - - // ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)---- - // 实例矩阵已在 Render() 里写入 ObjectBuffer(per-slot SSBO); - // 同 mesh 的 N 个可见实体合并为一次 drawIndexed(instanceCount=N), - // draw 数 = 可见唯一 mesh 数,与实体数量解耦 - for (const auto& batch : m_MainView.batches) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, kTextureSetIndex, - batch.mesh->GetMaterial()->GetDescriptorSet(), {}); - batch.mesh->Draw(cmd, batch.count, batch.first); - } - - cmd.endRendering(); - }) - .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) - .Write(m_Depth, RG::ResourceUse::DepthAttachment) - .Read(m_ShadowMap, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment); - - // ---- 全屏 pass 公共录制:动态状态/draw 全一致,仅附件 view、 - // 输入 set、push 值不同(Extract/BlurH/BlurV/Tonemap 复用)---- - auto recordFullscreen = - [this](RG::RenderGraphContext& ctx, vk::ImageView view, - vk::ShaderEXT frag, const vk::DescriptorSet& set, - const FullscreenPush& push) { - const vk::CommandBuffer cmd = ctx.cmd; - - vk::ClearValue clearValue; - clearValue.color = vk::ClearColorValue( - std::array{0.0f, 0.0f, 0.0f, 1.0f}); - - vk::RenderingAttachmentInfo colorAttachment; - colorAttachment.setImageView(view) - .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(clearValue); - - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, ctx.extent}) - .setLayerCount(1) - .setColorAttachments(colorAttachment); - - cmd.beginRendering(renderingInfo); - - std::array stages = { - vk::ShaderStageFlagBits::eVertex, - vk::ShaderStageFlagBits::eFragment - }; - std::array shaders = { - m_TonemapVertShader, frag}; - cmd.bindShadersEXT(stages, shaders); - - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_TonemapPipelineLayout, 0, set, {}); - - // ---- 动态状态(无深度附件、无顶点输入)---- - cmd.setRasterizerDiscardEnable(VK_FALSE); - cmd.setDepthTestEnable(VK_FALSE); - cmd.setDepthWriteEnable(VK_FALSE); - cmd.setDepthBoundsTestEnable(VK_FALSE); - cmd.setStencilTestEnable(VK_FALSE); - cmd.setDepthBiasEnable(VK_FALSE); - cmd.setPolygonModeEXT(vk::PolygonMode::eFill); - cmd.setCullMode(vk::CullModeFlagBits::eNone); - cmd.setFrontFace(vk::FrontFace::eCounterClockwise); - cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); - cmd.setPrimitiveRestartEnable(VK_FALSE); - cmd.setVertexInputEXT( - std::array{}, - std::array{}); - cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); - vk::SampleMask sampleMask = 0xFFFFFFFF; - cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); - cmd.setAlphaToCoverageEnableEXT(VK_FALSE); - cmd.setAlphaToOneEnableEXT(VK_FALSE); - - vk::Viewport viewport(0.0f, 0.0f, - static_cast(ctx.extent.width), - static_cast(ctx.extent.height), - 0.0f, 1.0f); - cmd.setViewportWithCount(viewport); - cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent)); - - cmd.pushConstants(m_TonemapPipelineLayout, - vk::ShaderStageFlagBits::eVertex | - vk::ShaderStageFlagBits::eFragment, - 0, kFullscreenPushSize, &push); - - cmd.draw(3, 1, 0, 0); - cmd.endRendering(); - }; - - // ---- Bloom:Extract(HDR 软阈值)→ BlurH → BlurV,全部半分辨率 ---- - m_RenderGraph->AddPass("BloomExtract", - [this, recordFullscreen](RG::RenderGraphContext& ctx) { - recordFullscreen(ctx, ctx.View(m_BloomA), m_BloomExtractFrag, - m_BloomExtractSets[ctx.frame.frameSlot], - FullscreenPush{0.0f, m_BloomThreshold, 0.0f, 0.0f}); - }) - .Read(m_HDRColor, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Write(m_BloomA, RG::ResourceUse::ColorAttachment); - - m_RenderGraph->AddPass("BloomBlurH", - [this, recordFullscreen](RG::RenderGraphContext& ctx) { - recordFullscreen(ctx, ctx.View(m_BloomB), m_BloomBlurFrag, - m_BloomBlurHSets[ctx.frame.frameSlot], - FullscreenPush{0.0f, - 1.0f / ctx.extent.width, 0.0f, - 0.0f}); - }) - .Read(m_BloomA, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Write(m_BloomB, RG::ResourceUse::ColorAttachment); - - m_RenderGraph->AddPass("BloomBlurV", - [this, recordFullscreen](RG::RenderGraphContext& ctx) { - recordFullscreen(ctx, ctx.View(m_BloomA), m_BloomBlurFrag, - m_BloomBlurVSets[ctx.frame.frameSlot], - FullscreenPush{0.0f, 0.0f, - 1.0f / ctx.extent.height, - 0.0f}); - }) - .Read(m_BloomB, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Write(m_BloomA, RG::ResourceUse::ColorAttachment); - - // ---- Tonemap pass:HDR(+bloom)→ 显示空间,全屏三角无顶点缓冲 ---- - // 显示变换在此显式建模(此前隐式依赖 Srgb 附件,是设计欠账)。 - // Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。 - m_RenderGraph->AddPass("Tonemap", - [this, recordFullscreen](RG::RenderGraphContext& ctx) { - recordFullscreen(ctx, ctx.View(m_Backbuffer), m_TonemapFragShader, - m_TonemapDescriptorSets[ctx.frame.frameSlot], - FullscreenPush{m_Exposure, - m_UseACES ? 1.0f : 0.0f, - m_BloomStrength, 0.0f}); - }) - .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) - .Read(m_HDRColor, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment) - .Read(m_BloomA, RG::ResourceUse::ShaderRead, - vk::ShaderStageFlagBits::eFragment); - - // ---- ImGui pass:叠加层(loadOp=Load,不清屏)---- - // 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类 - // 在 OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。 - // 未启用叠加层时空转(begin/end 一次空 rendering region)。 - m_RenderGraph->AddPass("ImGui", - [this](RG::RenderGraphContext& ctx) { - const vk::CommandBuffer cmd = ctx.cmd; - - vk::RenderingAttachmentInfo colorAttachment; - colorAttachment.setImageView(ctx.View(m_Backbuffer)) - .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eLoad) // 关键:叠加 - .setStoreOp(vk::AttachmentStoreOp::eStore); - - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, ctx.extent}) - .setLayerCount(1) - .setColorAttachments(colorAttachment); - - cmd.beginRendering(renderingInfo); - - if (m_ImGuiLayer) { - m_ImGuiLayer->RenderDrawData(cmd); - } - - cmd.endRendering(); - }) - .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment); - - // ---- Present pass:transition 到 ePresentSrcKHR ---- - m_RenderGraph->AddPass("Present", - [](RG::RenderGraphContext&) { - // present 的 layout 转换由 RG 的 barrier 完成, - // 实际 vkQueuePresentKHR 由 Renderer 驱动。 - }) - .Read(m_Backbuffer, RG::ResourceUse::Present); + for (auto& pass : m_Passes) { + pass->Register(*m_RenderGraph); + } m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile } @@ -772,51 +234,10 @@ namespace Engine m_SyncManager = CreateScope( *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); - // RG 重编译:物理镜像按新尺寸轮换 + // RG 重编译:物理镜像按新尺寸轮换,各 pass 重绑输入描述符 m_RenderGraph->SetTargetExtent(m_Extent); - - // 重编译后物理镜像可能变化,描述符重新绑定 - UpdateShadowDescriptors(); - UpdateFullscreenDescriptors(); - } - - void Renderer::UpdateShadowDescriptors() - { - // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view - for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { - m_DescriptorManager->UpdateTexture( - m_ShadowDescriptorSets[slot], 0, - m_ShadowSampler, - m_RenderGraph->GetPhysicalView(m_ShadowMap, slot)); - } - } - - void Renderer::UpdateFullscreenDescriptors() - { - // 全屏 pass 的输入绑定(per-slot 物理镜像): - // Tonemap = {0: HDRColor, 1: BloomA} - // Extract = {0: HDRColor} (binding1 不用,可不写) - // BlurH = {0: BloomA} - // BlurV = {0: BloomB} - for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { - const vk::ImageView hdr = - m_RenderGraph->GetPhysicalView(m_HDRColor, slot); - const vk::ImageView bloomA = - m_RenderGraph->GetPhysicalView(m_BloomA, slot); - const vk::ImageView bloomB = - m_RenderGraph->GetPhysicalView(m_BloomB, slot); - - m_DescriptorManager->UpdateTexture( - m_TonemapDescriptorSets[slot], 0, m_TonemapSampler, hdr); - m_DescriptorManager->UpdateTexture( - m_TonemapDescriptorSets[slot], 1, m_TonemapSampler, bloomA); - - m_DescriptorManager->UpdateTexture( - m_BloomExtractSets[slot], 0, m_TonemapSampler, hdr); - m_DescriptorManager->UpdateTexture( - m_BloomBlurHSets[slot], 0, m_TonemapSampler, bloomA); - m_DescriptorManager->UpdateTexture( - m_BloomBlurVSets[slot], 0, m_TonemapSampler, bloomB); + for (auto& pass : m_Passes) { + pass->OnTargetsRebuilt(*m_RenderGraph); } } @@ -849,27 +270,10 @@ namespace Engine ? static_cast(end - start) * m_TimestampPeriod / 1.0e6f : 0.0f; } - - /* - // 控制台遥测:每 120 次读取(≈60 帧 @2 slot)输出一行 - static u32 telemetryCount = 0; - if (++telemetryCount % 120 == 1) { - std::string line = "GPU times:"; - for (u32 i = 0; i < passCount; ++i) { - line += " " + m_PassNames[i] + "=" + - std::to_string(m_PassGpuMs[i]).substr(0, 5) + "ms"; - } - line += " collect=" + std::to_string(m_CollectMs).substr(0, 5) + "ms"; - ENGINE_INFO("{}", line); - } - */ } void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets) { - m_BoundScene = &scene; - m_BoundAssets = &assets; - if (m_Window.WasResized()) { m_Window.ResetResizedFlag(); HandleResize(); @@ -878,153 +282,26 @@ namespace Engine const u32 frameSlot = m_CurrentFrame; const auto& vkDevice = m_Device->GetDevice(); - // 1. UBO:从 ECS 场景组装(primary 相机 + 灯光 + 环境色) - auto* cameraComp = scene.GetPrimaryCamera(); - if (!cameraComp) { - // 无相机:跳过本帧(正常场景必有相机,这里只是安全网) - return; + // 1. 场景 → 当帧渲染数据(相机/灯光/双视图剔除;纯 CPU,不碰 GPU) + const f32 aspect = static_cast(m_Extent.width) / + static_cast(m_Extent.height); + if (!m_SceneRenderer->BuildFrame(scene, assets, aspect)) { + return; // 无 primary 相机:跳帧 } - const Camera& camera = cameraComp->camera; + const FrameSceneData& frame = m_SceneRenderer->Frame(); - float aspect = static_cast(m_Extent.width) / - static_cast(m_Extent.height); - - UniformBufferObject ubo{}; - ubo.view = camera.GetView(); - ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f); - ubo.ambientColor = scene.GetSettings().ambientColor; - - u32 lightCount = 0; - glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射 - scene.Each([&](Entity, LightComponent& light) { - if (lightCount >= kMaxLights) return; - - glm::vec3 dir = glm::length(light.direction) > 0.0f - ? glm::normalize(light.direction) - : glm::vec3(0.0f); - ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f); - ubo.lights[lightCount].color = light.color; - - // 第一个灯作为阴影光源 - if (lightCount == 0) shadowDir = dir; - ++lightCount; - }); - for (u32 i = lightCount; i < kMaxLights; ++i) { - ubo.lights[i].direction = glm::vec4(0.0f); - ubo.lights[i].color = glm::vec4(0.0f); - } - - // ---- 阴影光空间矩阵:写入(Shadow pass)与采样(Forward UBO)共用, - // 从根上排除任何"旋转/镜像不一致"的可能 ---- - // 光源位置在光方向的反方向远处,看向场景中心。 - { - const glm::vec3 lightPos = -shadowDir * kLightDistance; - // up 与视线平行时叉积退化(NaN),切换 up - const glm::vec3 up = glm::abs(shadowDir.y) > 0.999f - ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f); - const glm::mat4 lightView = glm::lookAt( - lightPos, glm::vec3(0.0f), up); - - glm::mat4 lightProj = glm::ortho( - -kLightOrthoExtent, kLightOrthoExtent, - -kLightOrthoExtent, kLightOrthoExtent, - kLightNear, kLightFar); - // Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。 - // ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!) - lightProj[2][2] *= 0.5f; - lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; - - m_LightSpace = lightProj * lightView; - ubo.lightSpace = m_LightSpace; - } - - // 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮), - // 再写 UBO,写-读竞争关闭。 + // 2. 等本槽位 fence → 写 UBO/SSBO → reset(写-读竞争关闭) m_SyncManager->WaitFence(frameSlot); ReadGpuTimes(frameSlot); // fence 已等:上一轮该槽位的查询已就绪 - m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo)); + m_UniformBuffer->Update(frameSlot, &frame.ubo, sizeof(frame.ubo)); m_SyncManager->ResetFence(frameSlot); - // ---- GPUDriven:单趟收集 + 双视图剔除 ---- - // 每个实例对相机视锥、光源视锥分别做包围球测试: - // 进相机视锥 → 主视图 batch(Forward 画) - // 进光源视锥 → 阴影 batch(Shadow 画)——相机外实体仍可投影子 - // 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。 - // 哈希聚合保证各视图内 batch 区间连续。 - const auto collectStart = std::chrono::steady_clock::now(); - m_InstanceModels.clear(); - m_MainView.batches.clear(); - m_ShadowView.batches.clear(); - m_MainView.instanceCount = 0; - m_ShadowView.instanceCount = 0; - m_MainBatchLookup.clear(); - m_ShadowBatchLookup.clear(); - bool instanceTruncated = false; - - const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view); - // 注意:剔除平面提取按 [0,1] 深度约定——相机 proj 与 m_LightSpace - // 都已做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用 - const Frustum shadowFrustum = Frustum::FromMatrix(m_LightSpace); - - scene.Each( - [&](Entity, TransformComponent& t, MeshRendererComponent& mr) { - Model* model = assets.GetModel(mr.model); - if (!model) return; - - // 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放) - const f32 worldScale = glm::max( - glm::length(glm::vec3(t.world[0])), - glm::max(glm::length(glm::vec3(t.world[1])), - glm::length(glm::vec3(t.world[2])))); - - for (auto& mesh : model->GetMeshes()) { - const auto& bounds = mesh->GetBounds(); - const glm::vec3 worldCenter = - glm::vec3(t.world * glm::vec4(bounds.center, 1.0f)); - const f32 worldRadius = bounds.radius * worldScale; - - auto tryAppend = [&](CullResult& view, const Frustum& frustum, - std::unordered_map& lookup) { - if (!frustum.IntersectsSphere(worldCenter, worldRadius)) { - return; - } - if (m_InstanceModels.size() >= kMaxInstances) { - instanceTruncated = true; - return; - } - const auto [it, inserted] = lookup.emplace( - mesh.get(), static_cast(view.batches.size())); - if (inserted) { - view.batches.push_back({mesh.get(), - static_cast(m_InstanceModels.size()), 0}); - } - view.batches[it->second].count++; - view.instanceCount++; - m_InstanceModels.push_back(t.world); - }; - - tryAppend(m_MainView, mainFrustum, m_MainBatchLookup); - tryAppend(m_ShadowView, shadowFrustum, m_ShadowBatchLookup); - } - }); - - if (instanceTruncated) { - // 只告警一次(每帧告警会刷屏) - static bool warned = false; - if (!warned) { - warned = true; - ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated", - kMaxInstances); - } + if (!frame.instanceModels.empty()) { + m_ObjectBuffer->Update(frameSlot, frame.instanceModels.data(), + frame.instanceModels.size() * sizeof(glm::mat4)); } - if (!m_InstanceModels.empty()) { - m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(), - m_InstanceModels.size() * sizeof(glm::mat4)); - } - m_CollectMs = std::chrono::duration( - std::chrono::steady_clock::now() - collectStart).count(); - // 2. acquire swapchain image + // 3. acquire swapchain image u32 imageIndex = 0; try { const auto result = vkDevice.acquireNextImageKHR( @@ -1043,22 +320,22 @@ namespace Engine return; } - // 3. 录制:FrameContext + RenderGraph - FrameContext frame; - frame.frameSlot = frameSlot; - frame.imageIndex = imageIndex; - frame.cmd = m_CommandManager->BeginFrame(frameSlot); + // 4. 录制:FrameContext + RenderGraph + FrameContext frameCtx; + frameCtx.frameSlot = frameSlot; + frameCtx.imageIndex = imageIndex; + frameCtx.cmd = m_CommandManager->BeginFrame(frameSlot); m_RenderGraph->SetImportedImage( m_Backbuffer, m_Swapchain->GetImages()[imageIndex], m_Swapchain->GetImageViews()[imageIndex]); - m_RenderGraph->Execute(frame); + m_RenderGraph->Execute(frameCtx); m_CommandManager->EndFrame(frameSlot); - // 4. 提交 + // 5. 提交 std::array waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput }; @@ -1070,7 +347,7 @@ namespace Engine }; vk::SubmitInfo submitInfo; - submitInfo.setCommandBuffers(frame.cmd) + submitInfo.setCommandBuffers(frameCtx.cmd) .setWaitSemaphores(waitSemaphores) .setWaitDstStageMask(waitStages) .setSignalSemaphores(signalSemaphores); @@ -1078,7 +355,7 @@ namespace Engine m_Device->GetGraphicsQueue().submit( submitInfo, m_SyncManager->GetFrameFence(frameSlot)); - // 5. 呈现 + // 6. 呈现 std::array swapchains = {m_Swapchain->Get()}; std::array presentWait = { m_SyncManager->GetRenderFinished(imageIndex) diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp new file mode 100644 index 0000000..60b5aa0 --- /dev/null +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -0,0 +1,168 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace Engine +{ + using namespace ECS; // TransformComponent / Entity 等直接使用 + + namespace + { + // 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应) + constexpr f32 kLightOrthoExtent = 15.0f; + constexpr f32 kLightDistance = 30.0f; + constexpr f32 kLightNear = 1.0f; + constexpr f32 kLightFar = 80.0f; + } + + void SceneRenderer::ComputeLightSpace(const glm::vec3& shadowDir) + { + // 光源位置在光方向的反方向远处,看向场景中心 + const glm::vec3 lightPos = -shadowDir * kLightDistance; + // up 与视线平行时叉积退化(NaN),切换 up + const glm::vec3 up = glm::abs(shadowDir.y) > 0.999f + ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f); + const glm::mat4 lightView = glm::lookAt( + lightPos, glm::vec3(0.0f), up); + + glm::mat4 lightProj = glm::ortho( + -kLightOrthoExtent, kLightOrthoExtent, + -kLightOrthoExtent, kLightOrthoExtent, + kLightNear, kLightFar); + // Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。 + // ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!) + lightProj[2][2] *= 0.5f; + lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; + + m_Frame.ubo.lightSpace = lightProj * lightView; + } + + bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, + f32 aspect) + { + const auto collectStart = std::chrono::steady_clock::now(); + + // ---- 1. 相机 ---- + auto* cameraComp = scene.GetPrimaryCamera(); + if (!cameraComp) { + // 无相机:本帧跳过(正常场景必有相机,安全网) + return false; + } + const Camera& camera = cameraComp->camera; + + UniformBufferObject& ubo = m_Frame.ubo; + ubo.view = camera.GetView(); + ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f); + ubo.ambientColor = scene.GetSettings().ambientColor; + + // ---- 2. 灯光(第一个灯作为阴影光源)---- + u32 lightCount = 0; + glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射 + scene.Each([&](Entity, LightComponent& light) { + if (lightCount >= kMaxLights) return; + + glm::vec3 dir = glm::length(light.direction) > 0.0f + ? glm::normalize(light.direction) + : glm::vec3(0.0f); + ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f); + ubo.lights[lightCount].color = light.color; + + if (lightCount == 0) shadowDir = dir; + ++lightCount; + }); + for (u32 i = lightCount; i < kMaxLights; ++i) { + ubo.lights[i].direction = glm::vec4(0.0f); + ubo.lights[i].color = glm::vec4(0.0f); + } + ComputeLightSpace(shadowDir); + + // ---- 3. GPUDriven:单趟收集 + 双视图剔除 ---- + // 每个实例对相机视锥、光源视锥分别做包围球测试: + // 进相机视锥 → 主视图 batch(Forward 画) + // 进光源视锥 → 阴影 batch(Shadow 画)——相机外实体仍可投影子 + // 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。 + // 哈希聚合保证各视图内 batch 区间连续。 + m_Frame.instanceModels.clear(); + m_Frame.mainView.batches.clear(); + m_Frame.shadowView.batches.clear(); + m_Frame.mainView.instanceCount = 0; + m_Frame.shadowView.instanceCount = 0; + m_MainLookup.clear(); + m_ShadowLookup.clear(); + bool instanceTruncated = false; + + // 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已 + // 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用 + const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view); + const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace); + + scene.Each( + [&](Entity, TransformComponent& t, MeshRendererComponent& mr) { + Model* model = assets.GetModel(mr.model); + if (!model) return; + + // 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放) + const f32 worldScale = glm::max( + glm::length(glm::vec3(t.world[0])), + glm::max(glm::length(glm::vec3(t.world[1])), + glm::length(glm::vec3(t.world[2])))); + + for (auto& mesh : model->GetMeshes()) { + const auto& bounds = mesh->GetBounds(); + const glm::vec3 worldCenter = + glm::vec3(t.world * glm::vec4(bounds.center, 1.0f)); + const f32 worldRadius = bounds.radius * worldScale; + + auto tryAppend = [&](CullResult& view, const Frustum& frustum, + std::unordered_map& lookup) { + if (!frustum.IntersectsSphere(worldCenter, worldRadius)) { + return; + } + if (m_Frame.instanceModels.size() >= kMaxInstances) { + instanceTruncated = true; + return; + } + const auto [it, inserted] = lookup.emplace( + mesh.get(), static_cast(view.batches.size())); + if (inserted) { + view.batches.push_back({mesh.get(), + static_cast(m_Frame.instanceModels.size()), 0}); + } + view.batches[it->second].count++; + view.instanceCount++; + m_Frame.instanceModels.push_back(t.world); + }; + + tryAppend(m_Frame.mainView, mainFrustum, m_MainLookup); + tryAppend(m_Frame.shadowView, shadowFrustum, m_ShadowLookup); + } + }); + + if (instanceTruncated && !m_TruncationWarned) { + // 只告警一次(每帧告警会刷屏) + m_TruncationWarned = true; + ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated", + kMaxInstances); + } + + m_Frame.collectMs = std::chrono::duration( + std::chrono::steady_clock::now() - collectStart).count(); + return true; + } + +} // namespace Engine diff --git a/Engine/src/Renderer/VulkanAssetUploader.cpp b/Engine/src/Renderer/VulkanAssetUploader.cpp new file mode 100644 index 0000000..edaee8d --- /dev/null +++ b/Engine/src/Renderer/VulkanAssetUploader.cpp @@ -0,0 +1,23 @@ +// +// Created by atdunbg on 2026/9/23. +// + +#include +#include + +namespace Engine { + + Scope VulkanAssetUploader::LoadModel(const std::string& path) + { + // GPU 上传在此发生:解析(assimp)+ buffer/image 创建一步完成 + return CreateScope( + m_Device, m_Allocator, m_Descriptors, m_TextureLayout, path); + } + + Scope VulkanAssetUploader::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) + { + return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors, + m_TextureLayout, halfSize, r, g, b); + } + +} // namespace Engine