refactor: 解耦重构——还清四处架构债(对齐 UE/Unity 分层)
- SceneRenderer:场景→渲染数据翻译层(UE FSceneRenderer / Unity CullingResults
模式),收口相机/灯光/光空间矩阵/双视图剔除,Renderer 只编排
- pass 对象化(Unity ScriptableRenderPass 模式):8 个 pass lambda 拆为
Passes::{Shadow,Forward,Bloom,Tonemap,ImGui,Present},BuildGraph 超级函数消失;
SceneGpu/FullscreenGpu 承接共享装配
- 中性 AssetRef(Core 层):ECS 组件不再依赖 Assets 模块(Unity InstanceID 思路)
- IAssetUploader 接口:AssetManager 不感知 Vulkan 具体类,上传服务由渲染层
VulkanAssetUploader 提供
- Renderer 公共 API 不变(App 层零改动),编译零错误 validation 零警告
This commit is contained in:
@@ -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 <Engine/Core/Types.h>
|
|
||||||
|
|
||||||
// Model 前向声明(句柄不依赖完整类型)
|
|
||||||
namespace Engine { class Model; }
|
|
||||||
|
|
||||||
namespace Engine::Assets {
|
|
||||||
|
|
||||||
inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
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<Engine::Model>;
|
|
||||||
|
|
||||||
} // namespace Engine::Assets
|
|
||||||
|
|
||||||
#endif //HELLOVULKAN_ASSETHANDLE_H
|
|
||||||
@@ -3,14 +3,9 @@
|
|||||||
//
|
//
|
||||||
// AssetManager:引擎资产层。
|
// AssetManager:引擎资产层。
|
||||||
//
|
//
|
||||||
// 职责划分(对照 Renderer):
|
// 职责:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理。
|
||||||
// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理
|
// GPU 创建不在这里:经 IAssetUploader 接口(渲染层提供实现)完成,
|
||||||
// - 渲染层:拿已解析资产创建/绑定 GPU 资源
|
// 本类不感知 Vulkan——依赖方向 Assets → 接口,实现由组合根注入。
|
||||||
//
|
|
||||||
// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的
|
|
||||||
// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。
|
|
||||||
// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的
|
|
||||||
// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。
|
|
||||||
//
|
//
|
||||||
// 组件存句柄不存指针的理由:
|
// 组件存句柄不存指针的理由:
|
||||||
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
|
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
|
||||||
@@ -23,7 +18,8 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
#include <Engine/Assets/AssetHandle.h>
|
#include <Engine/Core/AssetRef.h>
|
||||||
|
#include <Engine/Assets/IAssetUploader.h>
|
||||||
#include <Engine/Renderer/Model.h>
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -32,11 +28,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Device;
|
|
||||||
class MemoryAllocator;
|
|
||||||
class DescriptorManager;
|
|
||||||
|
|
||||||
namespace Assets {
|
namespace Assets {
|
||||||
|
|
||||||
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
|
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
|
||||||
@@ -48,7 +39,7 @@ namespace Engine {
|
|||||||
using LoaderFn = std::function<Scope<T>(const std::string&)>;
|
using LoaderFn = std::function<Scope<T>(const std::string&)>;
|
||||||
|
|
||||||
// 加载(带路径去重)。失败返回无效句柄。
|
// 加载(带路径去重)。失败返回无效句柄。
|
||||||
AssetHandle<T> Load(const std::string& path, const LoaderFn& loader)
|
AssetRef<T> Load(const std::string& path, const LoaderFn& loader)
|
||||||
{
|
{
|
||||||
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
|
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
@@ -60,7 +51,7 @@ namespace Engine {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetHandle<T> h;
|
AssetRef<T> h;
|
||||||
if (!m_Free.empty()) {
|
if (!m_Free.empty()) {
|
||||||
h.index = m_Free.back();
|
h.index = m_Free.back();
|
||||||
m_Free.pop_back();
|
m_Free.pop_back();
|
||||||
@@ -77,7 +68,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
|
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
|
||||||
T* Get(AssetHandle<T> h) const
|
T* Get(AssetRef<T> h) const
|
||||||
{
|
{
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) {
|
if (!h.Valid() || h.index >= m_Entries.size()) {
|
||||||
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
|
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
|
||||||
@@ -94,7 +85,7 @@ namespace Engine {
|
|||||||
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
|
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
|
||||||
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
|
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
|
||||||
// 本类只管所有权,不管 GPU 生命周期。
|
// 本类只管所有权,不管 GPU 生命周期。
|
||||||
void Unload(AssetHandle<T> h)
|
void Unload(AssetRef<T> h)
|
||||||
{
|
{
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) return;
|
if (!h.Valid() || h.index >= m_Entries.size()) return;
|
||||||
|
|
||||||
@@ -109,7 +100,7 @@ namespace Engine {
|
|||||||
m_Free.push_back(h.index);
|
m_Free.push_back(h.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& Path(AssetHandle<T> h) const
|
const std::string& Path(AssetRef<T> h) const
|
||||||
{
|
{
|
||||||
static const std::string kEmpty;
|
static const std::string kEmpty;
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
|
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
|
||||||
@@ -132,29 +123,27 @@ namespace Engine {
|
|||||||
|
|
||||||
std::vector<Entry> m_Entries;
|
std::vector<Entry> m_Entries;
|
||||||
std::vector<u32> m_Free;
|
std::vector<u32> m_Free;
|
||||||
std::unordered_map<std::string, AssetHandle<T>> m_ByPath;
|
std::unordered_map<std::string, AssetRef<T>> m_ByPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- AssetManager:全引擎资产入口 ----------
|
// ---------- AssetManager:全引擎资产入口 ----------
|
||||||
// (ModelHandle 别名来自 AssetHandle.h)
|
|
||||||
|
|
||||||
class AssetManager {
|
class AssetManager {
|
||||||
public:
|
public:
|
||||||
// GPU 上下文由组合根(Application)在 Renderer 装配完成后注入
|
// 上传服务由组合根(Application)注入:渲染层实现接口,
|
||||||
AssetManager(Device& device, MemoryAllocator& allocator,
|
// 资产层不感知 Vulkan 具体类
|
||||||
DescriptorManager& descriptors,
|
explicit AssetManager(IAssetUploader& uploader);
|
||||||
vk::DescriptorSetLayout textureLayout);
|
|
||||||
~AssetManager() = default;
|
~AssetManager() = default;
|
||||||
|
|
||||||
AssetManager(const AssetManager&) = delete;
|
AssetManager(const AssetManager&) = delete;
|
||||||
AssetManager& operator=(const AssetManager&) = delete;
|
AssetManager& operator=(const AssetManager&) = delete;
|
||||||
|
|
||||||
ModelHandle LoadModel(const std::string& path);
|
ModelRef LoadModel(const std::string& path);
|
||||||
Model* GetModel(ModelHandle h) const { return m_Models.Get(h); }
|
Model* GetModel(ModelRef h) const { return m_Models.Get(h); }
|
||||||
void UnloadModel(ModelHandle h) { m_Models.Unload(h); }
|
void UnloadModel(ModelRef h) { m_Models.Unload(h); }
|
||||||
|
|
||||||
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
|
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
|
||||||
ModelHandle CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
|
ModelRef CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
|
||||||
|
|
||||||
AssetCache<Model>& Models() { return m_Models; }
|
AssetCache<Model>& Models() { return m_Models; }
|
||||||
|
|
||||||
@@ -162,10 +151,7 @@ namespace Engine {
|
|||||||
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
|
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
IAssetUploader& m_Uploader;
|
||||||
MemoryAllocator& m_Allocator;
|
|
||||||
DescriptorManager& m_Descriptors;
|
|
||||||
vk::DescriptorSetLayout m_TextureLayout;
|
|
||||||
|
|
||||||
AssetCache<Model> m_Models;
|
AssetCache<Model> m_Models;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
namespace Engine::Assets {
|
||||||
|
|
||||||
|
class IAssetUploader {
|
||||||
|
public:
|
||||||
|
virtual ~IAssetUploader() = default;
|
||||||
|
|
||||||
|
// 磁盘模型 → 解析 + GPU 上传(失败返回 nullptr)
|
||||||
|
virtual Scope<Model> LoadModel(const std::string& path) = 0;
|
||||||
|
|
||||||
|
// 程序化纯色平面(size 为半边长)
|
||||||
|
virtual Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Assets
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_IASSETUPLOADER_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 <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
// 资产类型前向声明(必须放全局层:namespace 内前向声明会创建
|
||||||
|
// Engine::Assets::Engine::Model 这类错误嵌套命名空间)
|
||||||
|
namespace Engine {
|
||||||
|
class Model;
|
||||||
|
class Texture;
|
||||||
|
class Material;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
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<Model>;
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_ASSETREF_H
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/UUID.h>
|
#include <Engine/Core/UUID.h>
|
||||||
|
#include <Engine/Core/AssetRef.h>
|
||||||
#include <Engine/Renderer/Camera.h>
|
#include <Engine/Renderer/Camera.h>
|
||||||
#include <Engine/Assets/AssetHandle.h>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
@@ -62,9 +62,10 @@ namespace Engine::ECS {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 网格渲染体:引用共享的 Model 资产。
|
// 网格渲染体:引用共享的 Model 资产。
|
||||||
// 存句柄不存指针/Ref:所有权归 AssetManager,悬空由句柄 generation 拦截。
|
// 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)——
|
||||||
|
// ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。
|
||||||
struct MeshRendererComponent {
|
struct MeshRendererComponent {
|
||||||
Assets::ModelHandle model;
|
ModelRef model;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||||||
|
|||||||
@@ -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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<vk::DescriptorSet> m_ExtractSets;
|
||||||
|
std::vector<vk::DescriptorSet> m_BlurHSets;
|
||||||
|
std::vector<vk::DescriptorSet> m_BlurVSets;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_BLOOMPASS_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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/Core/Types.h>
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<vk::DescriptorSet> uboSets;
|
||||||
|
std::vector<vk::DescriptorSet> 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
|
||||||
@@ -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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TONEMAPPASS_H
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/20.
|
// 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
|
#ifndef HELLOVULKAN_RENDERER_H
|
||||||
#define HELLOVULKAN_RENDERER_H
|
#define HELLOVULKAN_RENDERER_H
|
||||||
@@ -9,13 +22,11 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/FrameContext.h>
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
#include <Engine/RenderGraph/RenderGraph.h>
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Window;
|
class Window;
|
||||||
@@ -27,26 +38,14 @@ namespace Engine {
|
|||||||
class SyncManager;
|
class SyncManager;
|
||||||
class CommandManager;
|
class CommandManager;
|
||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
class Model;
|
class SceneRenderer;
|
||||||
|
class VulkanAssetUploader;
|
||||||
|
class ImGuiLayer;
|
||||||
|
|
||||||
namespace ECS { class Scene; }
|
namespace ECS { class Scene; }
|
||||||
namespace Assets { class AssetManager; }
|
namespace Assets { class AssetManager; class IAssetUploader; }
|
||||||
class ImGuiLayer;
|
namespace Passes { class RenderPass; class SceneGpu; class FullscreenGpu; class PostProcessParams; }
|
||||||
class Mesh;
|
|
||||||
|
|
||||||
// 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 {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer(Window& window);
|
Renderer(Window& window);
|
||||||
@@ -61,69 +60,63 @@ namespace Engine {
|
|||||||
Device& GetDevice() const { return *m_Device; }
|
Device& GetDevice() const { return *m_Device; }
|
||||||
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
||||||
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
||||||
vk::DescriptorSetLayout GetTextureSetLayout() const { return m_TextureSetLayout; }
|
vk::DescriptorSetLayout GetTextureSetLayout() const; // 实现在 cpp(SceneGpu 成员)
|
||||||
vk::Format GetSwapchainFormat() const; // Swapchain 不完整,实现在 cpp
|
vk::Format GetSwapchainFormat() const;
|
||||||
u32 GetSwapchainImageCount() const;
|
u32 GetSwapchainImageCount() const;
|
||||||
|
// 资产上传服务(渲染层对资产层提供的 IAssetUploader 实现)
|
||||||
|
Assets::IAssetUploader& GetAssetUploader() const;
|
||||||
|
|
||||||
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
||||||
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
||||||
void SetImGuiLayer(ImGuiLayer* layer) { m_ImGuiLayer = layer; }
|
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<std::string>& GetPassNames() const { return m_PassNames; }
|
||||||
|
const std::vector<f32>& GetPassGpuMs() const { return m_PassGpuMs; }
|
||||||
|
|
||||||
|
// 双视图剔除后的实例数(遥测)
|
||||||
|
u32 GetMainInstanceCount() const;
|
||||||
|
u32 GetShadowInstanceCount() const;
|
||||||
|
|
||||||
|
// 垂直同步开关(present mode 切换会重建 swapchain),
|
||||||
|
// 只应在帧 UI 构建段调用(Render 之前)
|
||||||
|
void SetVSync(bool vsync);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
|
void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象
|
||||||
void HandleResize();
|
void HandleResize();
|
||||||
void UpdateShadowDescriptors();
|
|
||||||
void UpdateFullscreenDescriptors();
|
|
||||||
void ReadGpuTimes(u32 frameSlot);
|
void ReadGpuTimes(u32 frameSlot);
|
||||||
|
|
||||||
Window& m_Window;
|
Window& m_Window;
|
||||||
|
|
||||||
// ---- 底层模块(析构序:后声明者先析构,RG 在 Device/Allocator 之前)----
|
// ---- 底层模块(析构序:后声明者先析构;引用他人者先析构)----
|
||||||
Scope<Device> m_Device;
|
Scope<Device> m_Device;
|
||||||
Scope<MemoryAllocator> m_Allocator;
|
Scope<MemoryAllocator> m_Allocator;
|
||||||
Scope<DescriptorManager> m_DescriptorManager;
|
Scope<DescriptorManager> m_DescriptorManager;
|
||||||
Scope<ShaderManager> m_ShaderManager;
|
Scope<ShaderManager> m_ShaderManager;
|
||||||
|
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
|
||||||
|
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
|
||||||
|
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
||||||
|
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
||||||
Scope<Swapchain> m_Swapchain;
|
Scope<Swapchain> m_Swapchain;
|
||||||
Scope<SyncManager> m_SyncManager;
|
Scope<SyncManager> m_SyncManager;
|
||||||
Scope<CommandManager> m_CommandManager;
|
Scope<CommandManager> m_CommandManager;
|
||||||
Scope<UniformBuffer> m_UniformBuffer;
|
Scope<SceneRenderer> m_SceneRenderer; // 场景翻译层(先于 pass 构造)
|
||||||
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
|
|
||||||
Scope<RG::RenderGraph> m_RenderGraph;
|
Scope<RG::RenderGraph> m_RenderGraph;
|
||||||
|
Scope<VulkanAssetUploader> m_AssetUploader;
|
||||||
|
|
||||||
// ---- 管线装配(shader object 方案,无 vk::Pipeline)----
|
// pass 对象(注册序;执行序由 RG 拓扑排序决定)
|
||||||
// Forward layout:3 个 set(UBO/贴图/阴影图)+ 128B push(与 Shadow pass
|
std::vector<Scope<Passes::RenderPass>> m_Passes;
|
||||||
// 共用上限;forward 的 shader block 只有 64B,block ⊂ range 合法)
|
|
||||||
vk::PipelineLayout m_PipelineLayout;
|
|
||||||
vk::DescriptorSetLayout m_UBOSetLayout;
|
|
||||||
vk::DescriptorSetLayout m_TextureSetLayout;
|
|
||||||
vk::DescriptorSetLayout m_ShadowSetLayout;
|
|
||||||
std::vector<vk::DescriptorSet> m_UBODescriptorSets; // 每帧槽位一个
|
|
||||||
std::vector<vk::DescriptorSet> 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<vk::DescriptorSet> 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<vk::DescriptorSet> m_BloomExtractSets; // 每帧槽位
|
|
||||||
std::vector<vk::DescriptorSet> m_BloomBlurHSets;
|
|
||||||
std::vector<vk::DescriptorSet> m_BloomBlurVSets;
|
|
||||||
vk::ShaderEXT m_BloomExtractFrag{};
|
|
||||||
vk::ShaderEXT m_BloomBlurFrag{};
|
|
||||||
|
|
||||||
// ---- RenderGraph 资源句柄 ----
|
// ---- RenderGraph 资源句柄 ----
|
||||||
RG::TextureHandle m_Backbuffer{};
|
RG::TextureHandle m_Backbuffer{};
|
||||||
@@ -133,34 +126,12 @@ namespace Engine {
|
|||||||
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
||||||
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
||||||
|
|
||||||
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
|
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
|
||||||
ECS::Scene* m_BoundScene = nullptr;
|
Scope<Passes::PostProcessParams> m_PostParams;
|
||||||
Assets::AssetManager* m_BoundAssets = nullptr;
|
|
||||||
glm::mat4 m_LightSpace{1.0f}; // 阴影光源 VP(每帧由 Render 算好)
|
|
||||||
|
|
||||||
// GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费)
|
|
||||||
// 双视图剔除:相机视锥与光源视锥各出一份可见列表——
|
|
||||||
// 相机外的实体仍可能投影子进视野,所以 Shadow 用光源视锥单独剔除
|
|
||||||
struct CullResult {
|
|
||||||
std::vector<InstanceBatch> batches;
|
|
||||||
u32 instanceCount = 0;
|
|
||||||
};
|
|
||||||
CullResult m_MainView; // 相机视锥
|
|
||||||
CullResult m_ShadowView; // 光源视锥
|
|
||||||
std::unordered_map<Mesh*, u32> m_MainBatchLookup;
|
|
||||||
std::unordered_map<Mesh*, u32> m_ShadowBatchLookup;
|
|
||||||
std::vector<glm::mat4> m_InstanceModels; // [主视图段 | 阴影视图段]
|
|
||||||
static constexpr u32 kMaxInstances = 16384;
|
|
||||||
|
|
||||||
// 可选叠加层(不拥有;Application 持有)
|
// 可选叠加层(不拥有;Application 持有)
|
||||||
ImGuiLayer* m_ImGuiLayer = nullptr;
|
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 可读)
|
// GPU timestamp(pass 级耗时,ImGui 可读)
|
||||||
vk::QueryPool m_QueryPool;
|
vk::QueryPool m_QueryPool;
|
||||||
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
|
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
|
||||||
@@ -168,31 +139,6 @@ namespace Engine {
|
|||||||
std::vector<std::string> m_PassNames; // 执行序 pass 名
|
std::vector<std::string> m_PassNames; // 执行序 pass 名
|
||||||
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
||||||
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
|
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<std::string>& GetPassNames() const { return m_PassNames; }
|
|
||||||
const std::vector<f32>& 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{};
|
vk::Extent2D m_Extent{};
|
||||||
u32 m_CurrentFrame = 0;
|
u32 m_CurrentFrame = 0;
|
||||||
|
|||||||
@@ -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 <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
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<Mesh*, u32> m_MainLookup;
|
||||||
|
std::unordered_map<Mesh*, u32> m_ShadowLookup;
|
||||||
|
|
||||||
|
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENERENDERER_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 <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Mesh;
|
||||||
|
|
||||||
|
// GPUDriven:按 mesh 聚合的绘制批
|
||||||
|
struct InstanceBatch {
|
||||||
|
Mesh* mesh;
|
||||||
|
u32 first; // 在实例矩阵数组中的起始下标
|
||||||
|
u32 count; // 实例数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 一个渲染视图的剔除结果(相机视图 / 光源视图各一份)
|
||||||
|
struct CullResult {
|
||||||
|
std::vector<InstanceBatch> 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<glm::mat4> instanceModels;
|
||||||
|
CullResult mainView; // 相机视锥 → Forward pass
|
||||||
|
CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子)
|
||||||
|
|
||||||
|
f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENETYPES_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 <Engine/Assets/IAssetUploader.h>
|
||||||
|
|
||||||
|
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<Model> LoadModel(const std::string& path) override;
|
||||||
|
Scope<Model> 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
|
||||||
@@ -3,41 +3,31 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
|
||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
namespace Engine::Assets {
|
namespace Engine::Assets {
|
||||||
|
|
||||||
AssetManager::AssetManager(Device& device, MemoryAllocator& allocator,
|
AssetManager::AssetManager(IAssetUploader& uploader)
|
||||||
DescriptorManager& descriptors,
|
: m_Uploader(uploader)
|
||||||
vk::DescriptorSetLayout textureLayout)
|
|
||||||
: m_Device(device), m_Allocator(allocator),
|
|
||||||
m_Descriptors(descriptors), m_TextureLayout(textureLayout)
|
|
||||||
{
|
{
|
||||||
ENGINE_INFO("AssetManager created");
|
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<Model> {
|
// GPU 上传经接口发生在渲染层实现里,本层只管缓存与句柄
|
||||||
// GPU 上传在此发生:资产层调用渲染层接口完成 buffer/image 创建
|
return m_Models.Load(path,
|
||||||
return CreateScope<Model>(
|
[this](const std::string& p) { return m_Uploader.LoadModel(p); });
|
||||||
m_Device, m_Allocator, m_Descriptors, m_TextureLayout, 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::string key = "procedural://plane/" + std::to_string(halfSize) +
|
||||||
"/" + std::to_string(r) + "," + std::to_string(g) +
|
"/" + std::to_string(r) + "," + std::to_string(g) +
|
||||||
"," + std::to_string(b);
|
"," + std::to_string(b);
|
||||||
return m_Models.Load(key, [&](const std::string&) -> Scope<Model> {
|
return m_Models.Load(key,
|
||||||
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
|
[&](const std::string&) { return m_Uploader.CreatePlane(halfSize, r, g, b); });
|
||||||
m_TextureLayout, halfSize, r, g, b);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine::Assets
|
} // namespace Engine::Assets
|
||||||
|
|||||||
@@ -30,13 +30,10 @@ namespace Engine {
|
|||||||
m_Scene = CreateScope<ECS::Scene>();
|
m_Scene = CreateScope<ECS::Scene>();
|
||||||
m_Renderer = CreateScope<Renderer>(*m_Window);
|
m_Renderer = CreateScope<Renderer>(*m_Window);
|
||||||
|
|
||||||
// 资产层:复用渲染器的 GPU 上下文;
|
// 资产层:上传服务由渲染层提供(IAssetUploader 实现),
|
||||||
|
// 资产层不感知 Vulkan 具体类。
|
||||||
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
|
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
|
||||||
m_Assets = CreateScope<Assets::AssetManager>(
|
m_Assets = CreateScope<Assets::AssetManager>(m_Renderer->GetAssetUploader());
|
||||||
m_Renderer->GetDevice(),
|
|
||||||
m_Renderer->GetAllocator(),
|
|
||||||
m_Renderer->GetDescriptorManager(),
|
|
||||||
m_Renderer->GetTextureSetLayout());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application()
|
Application::~Application()
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||||
|
#include <Engine/Renderer/Vertex.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
#include <Engine/Renderer/Material.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
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<float, 4>{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<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
std::array<vk::ShaderEXT, 2> 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<float>(ctx.extent.width),
|
||||||
|
static_cast<float>(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
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
FullscreenGpu::FullscreenGpu(Device& device, DescriptorManager& descriptors,
|
||||||
|
ShaderManager& shaders)
|
||||||
|
: m_Device(device), m_Descriptors(descriptors)
|
||||||
|
{
|
||||||
|
// Set0 = {HDR 颜色图, Bloom 图},push = 16B 通用结构
|
||||||
|
std::vector<DescriptorBinding> bindings = {
|
||||||
|
{0, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{1, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
};
|
||||||
|
setLayout = descriptors.CreateLayout(bindings);
|
||||||
|
|
||||||
|
layout = descriptors.CreatePipelineLayout(
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(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<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto tonemapF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "tonemap.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto extractF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "bloom_extract.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto blurF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "bloom_blur.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(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<float, 4>{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<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
std::array<vk::ShaderEXT, 2> 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<vk::VertexInputBindingDescription2EXT, 0>{},
|
||||||
|
std::array<vk::VertexInputAttributeDescription2EXT, 0>{});
|
||||||
|
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<float>(ctx.extent.width),
|
||||||
|
static_cast<float>(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
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ImGuiPass.h>
|
||||||
|
#include <Engine/ImGui/ImGuiLayer.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Vulkan/UniformBuffer.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
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<DescriptorBinding> 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<DescriptorBinding> texBindings = {
|
||||||
|
{0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment},
|
||||||
|
};
|
||||||
|
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||||
|
|
||||||
|
// Set 2: shadow map(fragment 采样)
|
||||||
|
std::vector<DescriptorBinding> 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<vk::DescriptorSetLayout>{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<vk::DescriptorSetLayout>{uboSetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
auto shadowFragShader = shaders.CreateFromFile(
|
||||||
|
shaderDir + "shadow.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{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
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ShadowPass.h>
|
||||||
|
#include <Engine/Renderer/Vertex.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
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<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
std::array<vk::ShaderEXT, 2> 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<float>(kShadowMapSize),
|
||||||
|
static_cast<float>(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
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
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
|
||||||
+122
-845
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/SceneRenderer.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/Renderer/Camera.h>
|
||||||
|
#include <Engine/Renderer/Frustum.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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<LightComponent>([&](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<TransformComponent, MeshRendererComponent>(
|
||||||
|
[&](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<Mesh*, u32>& 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<u32>(view.batches.size()));
|
||||||
|
if (inserted) {
|
||||||
|
view.batches.push_back({mesh.get(),
|
||||||
|
static_cast<u32>(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<f32, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - collectStart).count();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/VulkanAssetUploader.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
Scope<Model> VulkanAssetUploader::LoadModel(const std::string& path)
|
||||||
|
{
|
||||||
|
// GPU 上传在此发生:解析(assimp)+ buffer/image 创建一步完成
|
||||||
|
return CreateScope<Model>(
|
||||||
|
m_Device, m_Allocator, m_Descriptors, m_TextureLayout, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope<Model> 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
|
||||||
Reference in New Issue
Block a user