feat: ImGui docking Editor + 显示链/帧同步/输入语义重构
- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期 - 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报 - 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链 - 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行) - 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧 - FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点) - Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
This commit is contained in:
@@ -88,7 +88,7 @@ namespace Engine {
|
||||
|
||||
// 主渲染之后、当帧提交之前的叠加层钩子(ImGui/调试视图/编辑器 UI)。
|
||||
// 主渲染由框架固定执行;这里只负责"额外的"渲染,不覆写则无叠加层。
|
||||
virtual void OnRenderOverlay() {}
|
||||
virtual void OnImGuiRender() {}
|
||||
|
||||
// 主循环结束后、析构前(保存、清理场景等)
|
||||
virtual void OnShutdown() {}
|
||||
|
||||
@@ -63,8 +63,15 @@ namespace Engine {
|
||||
// 输入事件系统
|
||||
|
||||
bool IsKeyPressed(int key) const;
|
||||
// UI 捕获鼠标(ImGui 悬停)时返回 false——面板上的按键归 ImGui
|
||||
bool IsMouseButtonPressed(int button) const;
|
||||
void GetMouseDelta(float& dx, float& dy) const;
|
||||
|
||||
// 光标捕获(自由视角):DISABLED 隐藏光标出无界增量,NORMAL 恢复。
|
||||
// 重复设置同值无操作;切换后鼠标增量参考点重置(防跳变)
|
||||
void SetCursorCaptured(bool captured);
|
||||
bool IsCursorCaptured() const { return m_CursorCaptured; }
|
||||
|
||||
|
||||
|
||||
private:
|
||||
@@ -80,6 +87,12 @@ namespace Engine {
|
||||
CaptureFn m_KeyboardCapture;
|
||||
CaptureFn m_MouseCapture;
|
||||
|
||||
// 光标捕获状态与鼠标增量参考点(GetMouseDelta 为 const,用 mutable)
|
||||
bool m_CursorCaptured = false;
|
||||
mutable bool m_FirstMouse = true;
|
||||
mutable double m_LastMouseX = 0.0;
|
||||
mutable double m_LastMouseY = 0.0;
|
||||
|
||||
// GLFW 静态回调(通过 window user pointer 找回 this)
|
||||
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
|
||||
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Engine::ECS {
|
||||
// ECS 内部遍历不用它(用 index+generation 句柄,性能)。
|
||||
struct IdComponent {
|
||||
UUID uuid;
|
||||
|
||||
operator UUID() const { return uuid; }
|
||||
};
|
||||
|
||||
// 实体名(调试/查询用;大规模场景建议演进为哈希 id)
|
||||
@@ -83,6 +85,37 @@ namespace Engine::ECS {
|
||||
ModelRef model;
|
||||
};
|
||||
|
||||
// 骨骼动画播放状态(纯数据 + 便捷控制)。
|
||||
// 采样/局部层混合逻辑在 SceneRenderer::BuildFrame(渲染侧唯一消费者);
|
||||
// ImGui 面板经 Play()/字段直接驱动。无此组件 = 按绑定姿势渲染。
|
||||
struct AnimationComponent {
|
||||
int clipIndex = 0; // 当前 clip(Model::GetClips 下标)
|
||||
bool playing = true;
|
||||
bool loop = true;
|
||||
f32 speed = 1.0f;
|
||||
f32 time = 0.0f; // 当前 clip 播放头(秒,wrap 由消费端处理)
|
||||
|
||||
// ---- crossfade(clip 切换平滑过渡)----
|
||||
// 来源 clip 淡出期间与目标 clip 并行采样,局部 TRS 层混合
|
||||
//(palette 数量不变,只是求值端多采一组姿态)
|
||||
int fadeFrom = -1; // 来源 clip 下标(-1 = 无过渡)
|
||||
f32 fadeFromTime = 0.0f; // 来源 clip 播放头
|
||||
f32 fadeElapsed = 0.0f; // 已过渡时长
|
||||
f32 fadeDuration = 0.3f; // 过渡总时长(秒)
|
||||
|
||||
// 切换 clip(带 crossfade)。越界/空 clip 保护在消费端。
|
||||
void Play(int index, f32 fadeSeconds = 0.3f)
|
||||
{
|
||||
if (index == clipIndex) return;
|
||||
fadeFrom = clipIndex;
|
||||
fadeFromTime = time;
|
||||
fadeElapsed = 0.0f;
|
||||
fadeDuration = fadeSeconds;
|
||||
clipIndex = index;
|
||||
time = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||||
struct CameraComponent {
|
||||
Camera camera;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Engine::ECS {
|
||||
// ---- 实体 ----
|
||||
|
||||
// 返回句柄表皮:e.AddComponent<T>() / e.GetComponent<T>() / e.Destroy()
|
||||
EntityHandle CreateEntity(std::string name = {});
|
||||
EntityHandle CreateEntity(std::string name = "new entity");
|
||||
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
||||
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Core/FrameContext.h> // kMaxFramesInFlight
|
||||
#include <imgui.h>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
namespace Engine {
|
||||
@@ -35,6 +37,17 @@ namespace Engine {
|
||||
ImGuiLayer(const ImGuiLayer&) = delete;
|
||||
ImGuiLayer& operator=(const ImGuiLayer&) = delete;
|
||||
|
||||
// ---- 场景显示纹理(RG PresentTarget → ImGui::Image 采样)----
|
||||
// RG 物理镜像按帧槽位各一份,每槽位各注册一个描述符;
|
||||
// 重编译(首次/resize)后 view 变化,Renderer 调 Update 重注册。
|
||||
// 返回值直接传给 ImGui::Image(无场景纹理时为 ImTextureID_Invalid)
|
||||
ImTextureID GetSceneTexture(u32 slot) const
|
||||
{
|
||||
return slot < kMaxFramesInFlight
|
||||
? m_SceneTextures[slot] : ImTextureID_Invalid;
|
||||
}
|
||||
void UpdateSceneTexture(u32 slot, vk::ImageView view);
|
||||
|
||||
// ---- 每帧三段式(由 Application::Run 驱动)----
|
||||
|
||||
// 开始 UI 构建(ImGui::NewFrame)。此后到 BuildDrawData 之间,
|
||||
@@ -57,6 +70,7 @@ namespace Engine {
|
||||
Device& m_Device;
|
||||
vk::DescriptorPool m_Pool;
|
||||
bool m_Enabled = true;
|
||||
ImTextureID m_SceneTextures[kMaxFramesInFlight]{};
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -98,6 +98,8 @@ namespace Engine::RG {
|
||||
// 按句柄取当前帧槽位的物理资源(image/view)
|
||||
vk::ImageView View(TextureHandle h) const;
|
||||
vk::Image Image(TextureHandle h) const;
|
||||
// 物理尺寸(导入资源 = SetImportedExtent 设定值;创建资源 = 物理镜像)
|
||||
vk::Extent2D Extent(TextureHandle h) const;
|
||||
|
||||
// 帧历史资源:上一帧槽位的物理镜像((slot + framesInFlight - 1) % F)。
|
||||
// 采样描述符经 GetPhysicalView(h, 历史槽位) 预绑(同阴影图模式)。
|
||||
@@ -129,6 +131,7 @@ namespace Engine::RG {
|
||||
// 导入资源每帧绑定
|
||||
vk::Image importedImage{};
|
||||
vk::ImageView importedView{};
|
||||
vk::Extent2D importedExtent{}; // 物理尺寸(swapchain 重建后更新)
|
||||
|
||||
// 帧槽位状态:物理镜像是否已被使用过(决定 entry barrier 变体)
|
||||
struct SlotState {
|
||||
@@ -207,6 +210,11 @@ namespace Engine::RG {
|
||||
// 每帧 acquire 后调用,把当帧 swapchain image 绑到导入资源
|
||||
void SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view);
|
||||
|
||||
// 导入资源的物理尺寸(swapchain 重建后同步更新)。pass 渲染区域
|
||||
// 跟实际附件尺寸走:backbuffer = 窗口尺寸,与目标尺寸(场景渲染
|
||||
// 分辨率)解耦——Editor 视口模式下二者不同
|
||||
void SetImportedExtent(TextureHandle h, vk::Extent2D extent);
|
||||
|
||||
// 指定帧槽位的物理镜像 view(Compile 后有效)。
|
||||
// 用途:把 RG 管理的资源绑定到外部描述符(如阴影图采样)。
|
||||
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Engine {
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
// 输入驱动:WASD/Space/Shift 移动 + 鼠标视角(需 SetMouseLookEnabled)。
|
||||
// 输入驱动:WASD/Space/Shift 移动 + 按住右键鼠标视角。
|
||||
// 全部写回传入的 transform,有变更才 Compose。
|
||||
void Update(Window& window, ECS::TransformComponent& transform, f32 deltaTime);
|
||||
|
||||
@@ -35,14 +35,15 @@ namespace Engine {
|
||||
void SetMoveSpeed(f32 speed) { m_MoveSpeed = speed; }
|
||||
void SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; }
|
||||
|
||||
// 鼠标视角默认关闭(需要先做 cursor capture 再打开)
|
||||
// 鼠标视角总开关(默认开)。交互 = 按住右键自由视角(光标捕获),
|
||||
// 松开释放光标;右键落在 ImGui 面板上时归 UI 不触发相机
|
||||
void SetMouseLookEnabled(bool enabled) { m_MouseLookEnabled = enabled; }
|
||||
bool IsMouseLookEnabled() const { return m_MouseLookEnabled; }
|
||||
|
||||
private:
|
||||
f32 m_MoveSpeed = 2.5f;
|
||||
f32 m_MouseSensitivity = 0.1f;
|
||||
bool m_MouseLookEnabled = false;
|
||||
bool m_MouseLookEnabled = true;
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -7,13 +7,19 @@
|
||||
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
||||
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
|
||||
// 3 emissive(自发光,sRGB) 4 ao(环境光遮蔽,线性)
|
||||
// 5 材质参数 UBO(fragment,kParamsBinding)
|
||||
// 5 roughness(独立粗糙度图,R 通道——FBX/OBJ specular 工作流,
|
||||
// glTF 合图工作流不用此槽)
|
||||
// 6 材质参数 UBO(fragment,kParamsBinding)
|
||||
//
|
||||
// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。
|
||||
// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上:
|
||||
// glTF(MR 合图 B=金属度语义)→ 因子取 glTF 声明(默认 1/1)
|
||||
// OBJ(specular 工作流,独立 roughness 图或无图)→ metallic=0,
|
||||
// roughness 因子按贴图有无/Ns 换算——金属度强制 0
|
||||
// 因子(metallicFactor/roughnessFactor)乘在贴图上:
|
||||
// glTF(MR 合图 B=金属度语义)→ 因子取 glTF 声明(默认 1/1),
|
||||
// factors.z=0 → shader 直接读 MR.g 当粗糙度
|
||||
// FBX(metalness 独立图 + roughness 独立图)→ metal 图落 MR 槽
|
||||
// (灰度 R=G=B,.b 读金属度恰好正确),roughness 图落 Roughness
|
||||
// 槽,factors.z=1 → shader 用 Roughness.r 替换 MR.g
|
||||
// OBJ(specular 工作流)→ metallic=0,独立 roughness 图同 FBX;
|
||||
// 无图时 roughness 因子按 Ns 换算
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_MATERIAL_H
|
||||
@@ -33,9 +39,11 @@ namespace Engine {
|
||||
class Texture;
|
||||
class UniformBuffer;
|
||||
|
||||
// 材质因子(乘 MR 贴图;无贴图时即最终值)
|
||||
// 材质因子(乘贴图;无贴图时即最终值)
|
||||
// x = metallic 因子 y = roughness 因子
|
||||
// z = 独立粗糙度图开关(1 = 用 Roughness 槽 R 通道替换 MR.g)
|
||||
struct MaterialParams {
|
||||
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f}; // x=metallic y=roughness
|
||||
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f};
|
||||
};
|
||||
|
||||
class Material {
|
||||
@@ -47,8 +55,9 @@ namespace Engine {
|
||||
MetallicRoughness = 2,
|
||||
Emissive = 3,
|
||||
Ao = 4,
|
||||
kSlotCount = 5, // 纹理槽数
|
||||
kParamsBinding = 5, // 参数 UBO binding
|
||||
Roughness = 5, // 独立粗糙度图(R 通道)
|
||||
kSlotCount = 6, // 纹理槽数
|
||||
kParamsBinding = 6, // 参数 UBO binding
|
||||
};
|
||||
|
||||
Material(Device& device, MemoryAllocator& allocator,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Renderer/Vertex.h>
|
||||
#include <Engine/Vulkan/Buffer.h>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <vector>
|
||||
|
||||
@@ -50,6 +51,15 @@ namespace Engine
|
||||
// 全局连续槽位(SceneRenderer 数组化 batch 查找用,避免逐实例 hash)
|
||||
u32 GetSlot() const { return m_Slot; }
|
||||
|
||||
// ---- MDI 兼容取数(per-mesh buffer:偏移恒 0,firstIndex 即全局)----
|
||||
vk::Buffer GetVertexBuffer() const { return m_VertexBuffer->Get(); }
|
||||
vk::Buffer GetIndexBuffer() const { return m_IndexBuffer->Get(); }
|
||||
u32 GetVertexOffset() const { return 0; }
|
||||
u32 GetGlobalFirstIndex(u32 lod) const
|
||||
{
|
||||
return m_Lods[lod < kMeshLodCount ? lod : kMeshLodCount - 1].firstIndex;
|
||||
}
|
||||
|
||||
// 局部空间包围球(构造时从顶点算出),视锥剔除用
|
||||
struct Bounds {
|
||||
glm::vec3 center{0.0f};
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Renderer/Material.h>
|
||||
#include <Engine/Renderer/Mesh.h>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <assimp/matrix4x4.h> // 默认参数需完整类型(烘焙节点变换用)
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
@@ -41,10 +43,52 @@ namespace Engine
|
||||
MaterialParams params;
|
||||
bool transparent = false; // glTF alphaMode=BLEND(透明绘制队列)
|
||||
};
|
||||
|
||||
// ---- 骨骼动画数据(assimp aiNode/aiAnimation/aiBone 的引擎侧镜像)----
|
||||
// 关键帧(time 单位 = clip 原生 tick,运行时按 tps 换算秒)
|
||||
struct PosKey { f32 time; glm::vec3 value; };
|
||||
struct RotKey { f32 time; glm::quat value; };
|
||||
struct SclKey { f32 time; glm::vec3 value; };
|
||||
|
||||
// 一条节点动画轨道:作用于 node 表中下标为 nodeIdx 的节点;
|
||||
// 无轨道的分量运行时用节点静止姿态(localTransform)
|
||||
struct AnimChannel {
|
||||
u32 nodeIdx = 0;
|
||||
std::vector<PosKey> positions;
|
||||
std::vector<RotKey> rotations;
|
||||
std::vector<SclKey> scales;
|
||||
};
|
||||
|
||||
struct AnimationClip {
|
||||
std::string name;
|
||||
f32 duration = 0.0f; // 秒(已按 tps 换算)
|
||||
f32 tps = 1.0f; // ticks per second(0 按 1,assimp 惯例)
|
||||
std::vector<AnimChannel> channels;
|
||||
};
|
||||
|
||||
// aiNode 树拍平表:骨骼 global 变换的累乘骨架(解析期 ProcessNode
|
||||
// 原本丢弃 mTransformation,骨骼层级必须保留)
|
||||
struct ModelNode {
|
||||
std::string name;
|
||||
i32 parent = -1; // 根节点 = -1
|
||||
glm::mat4 localTransform{1.0f}; // 静止姿态 local TRS(assimp 行主→glm 列主已转置)
|
||||
};
|
||||
|
||||
// 骨骼 = palette 槽位 ↔ 节点 ↔ 逆绑定矩阵
|
||||
// palette[i] = global(bone.nodeIdx) × invBind
|
||||
struct ModelBone {
|
||||
std::string name;
|
||||
u32 nodeIdx = 0;
|
||||
glm::mat4 invBind{1.0f}; // aiBone mOffsetMatrix(绑定姿势全局逆)
|
||||
};
|
||||
|
||||
struct ParsedModel {
|
||||
std::string path;
|
||||
bool isGltf = false;
|
||||
std::vector<ParsedMesh> meshes;
|
||||
std::vector<ModelNode> nodes;
|
||||
std::vector<ModelBone> bones;
|
||||
std::vector<AnimationClip> clips;
|
||||
bool Valid() const { return !meshes.empty(); }
|
||||
};
|
||||
|
||||
@@ -92,6 +136,13 @@ namespace Engine
|
||||
// 实体级粗剔除用——整个模型在视锥外直接跳过全部 mesh 循环
|
||||
const Mesh::Bounds& GetBounds() const { return m_Bounds; }
|
||||
|
||||
// ---- 骨骼动画运行时数据(无动画模型全部为空,零开销)----
|
||||
bool HasAnimations() const { return !m_Clips.empty(); }
|
||||
u32 GetBoneCount() const { return static_cast<u32>(m_Bones.size()); }
|
||||
const std::vector<AnimationClip>& GetClips() const { return m_Clips; }
|
||||
const std::vector<ModelNode>& GetNodes() const { return m_Nodes; }
|
||||
const std::vector<ModelBone>& GetBones() const { return m_Bones; }
|
||||
|
||||
private:
|
||||
Model(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
@@ -105,13 +156,33 @@ namespace Engine
|
||||
vk::DescriptorSetLayout textureLayout);
|
||||
|
||||
void LoadModel(const std::string& path);
|
||||
// aiNode 树拍平成 node 表(DFS 序,parent 下标回填)
|
||||
static u32 FlattenNodes(const aiNode* node, i32 parent,
|
||||
std::vector<ModelNode>& out);
|
||||
// 每帧动画的 channel 轨道提取(nodeName → node 表下标)
|
||||
static void ParseAnimations(const aiScene* scene,
|
||||
const std::vector<ModelNode>& nodes,
|
||||
std::vector<AnimationClip>& out);
|
||||
static void ProcessNode(const aiNode* node, const aiScene* scene,
|
||||
std::vector<ParsedMesh>& out,
|
||||
const std::filesystem::path& modelDir,
|
||||
bool isGltf);
|
||||
bool isGltf,
|
||||
std::vector<ModelBone>& bones,
|
||||
std::unordered_map<std::string, u32>& boneSlots,
|
||||
const aiMatrix4x4& parentGlobal
|
||||
= aiMatrix4x4());
|
||||
static ParsedMesh ProcessMesh(const aiMesh* mesh, const aiScene* scene,
|
||||
const std::filesystem::path& modelDir,
|
||||
bool isGltf);
|
||||
bool isGltf,
|
||||
std::vector<ModelBone>& bones,
|
||||
std::unordered_map<std::string, u32>& boneSlots,
|
||||
const aiMatrix4x4& globalTransform
|
||||
= aiMatrix4x4());
|
||||
// mesh 的 aiBone 权重灌进顶点 + 模型级骨骼表注册去重
|
||||
static void CollectMeshBones(const aiMesh* mesh,
|
||||
std::vector<Vertex>& vertices,
|
||||
std::vector<ModelBone>& bones,
|
||||
std::unordered_map<std::string, u32>& boneSlots);
|
||||
void InitDefaultTextures();
|
||||
|
||||
// 聚合各 mesh bounds 为全模型联合包围球(实体级粗剔除)
|
||||
@@ -121,10 +192,13 @@ namespace Engine
|
||||
|
||||
// 材质缓存入口:同贴图组合+同因子+同透明语义共享一份 Material,
|
||||
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
||||
// paths 下标 = Material::Slot;params = metallic/roughness 因子
|
||||
// paths 下标 = Material::Slot;params = metallic/roughness 因子;
|
||||
// slotOverrides = 程序化模型的槽位纹理直供(非空项优先于路径/默认)
|
||||
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
|
||||
const MaterialParams& params,
|
||||
bool transparent = false);
|
||||
bool transparent = false,
|
||||
const std::array<Texture*, Material::kSlotCount>&
|
||||
slotOverrides = {});
|
||||
|
||||
Device& m_Device;
|
||||
MemoryAllocator& m_Allocator;
|
||||
@@ -142,6 +216,11 @@ namespace Engine
|
||||
Mesh::Bounds m_Bounds; // mesh 建完后聚合(实体级粗剔除)
|
||||
std::string m_ModelPath;
|
||||
bool m_IsGltf = false; // glTF 家族:MR 合图语义 + 不翻转 UV
|
||||
|
||||
// ---- 骨骼动画运行时数据(FromParsed/LoadModel 从解析产物 move)----
|
||||
std::vector<ModelNode> m_Nodes;
|
||||
std::vector<ModelBone> m_Bones;
|
||||
std::vector<AnimationClip> m_Clips;
|
||||
};
|
||||
} // namespace Engine
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/29.
|
||||
//
|
||||
// BlitPass:显示模式适配器(kDisplayDirect 专用)。
|
||||
// Direct 模式:vkCmdBlitImage 把 PresentTarget(tonemap 输出)拷进
|
||||
// backbuffer,ImGui 随后 loadOp=Load 叠加 —— 场景铺满窗口的游戏形态。
|
||||
// Viewport(Editor)模式:空转,backbuffer 由 ImGui 清屏独占,场景
|
||||
// 经 ImGui::Image 呈现。
|
||||
//
|
||||
// 用 vkCmdBlitImage 而非着色器:同格式全屏拷贝零 shader 成本(声明
|
||||
// TransferSrc/TransferDst use,layout/barrier 由 RG 推导)。Blit 不在
|
||||
// rendering scope 内,直接录在 cmd 上,合法。
|
||||
//
|
||||
// 注:Blit 只在 Direct 模式运行,此时 PresentTarget 与 backbuffer 同为
|
||||
// 目标尺寸(1:1),src/dst offset 均取本 pass 渲染区域即可。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_BLITPASS_H
|
||||
#define HELLOVULKAN_BLITPASS_H
|
||||
|
||||
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||
#include <Engine/Renderer/SceneTypes.h>
|
||||
#include <array>
|
||||
|
||||
namespace Engine::Passes {
|
||||
|
||||
class BlitPass final : public RenderPass {
|
||||
public:
|
||||
BlitPass(FrameSceneData& frame, RG::TextureHandle src,
|
||||
RG::TextureHandle dst)
|
||||
: m_Frame(frame), m_Src(src), m_Dst(dst) {}
|
||||
|
||||
void Register(RG::RenderGraph& rg) override
|
||||
{
|
||||
rg.AddPass("Blit", [this](RG::RenderGraphContext& ctx) {
|
||||
if (m_Frame.displayMode != kDisplayDirect) return; // Editor 模式空转
|
||||
|
||||
vk::ImageBlit region;
|
||||
region.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
|
||||
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1});
|
||||
// src/dst 各按自己附件的物理尺寸取 offset(Direct 模式下
|
||||
// 二者同为窗口尺寸;尺寸解耦时此写法也各自正确)
|
||||
const vk::Extent2D srcExt = ctx.Extent(m_Src);
|
||||
const vk::Extent2D dstExt = ctx.Extent(m_Dst);
|
||||
const std::array<vk::Offset3D, 2> srcSpan = {
|
||||
vk::Offset3D{0, 0, 0},
|
||||
vk::Offset3D{static_cast<i32>(srcExt.width),
|
||||
static_cast<i32>(srcExt.height), 1}};
|
||||
const std::array<vk::Offset3D, 2> dstSpan = {
|
||||
vk::Offset3D{0, 0, 0},
|
||||
vk::Offset3D{static_cast<i32>(dstExt.width),
|
||||
static_cast<i32>(dstExt.height), 1}};
|
||||
region.setSrcOffsets(srcSpan).setDstOffsets(dstSpan);
|
||||
|
||||
ctx.cmd.blitImage(
|
||||
ctx.Image(m_Src), vk::ImageLayout::eTransferSrcOptimal,
|
||||
ctx.Image(m_Dst), vk::ImageLayout::eTransferDstOptimal,
|
||||
{region}, vk::Filter::eLinear);
|
||||
})
|
||||
.Read(m_Src, RG::ResourceUse::TransferSrc)
|
||||
.Write(m_Dst, RG::ResourceUse::TransferDst);
|
||||
}
|
||||
|
||||
private:
|
||||
FrameSceneData& m_Frame; // displayMode(帧契约)
|
||||
RG::TextureHandle m_Src; // PresentTarget(tonemap 输出)
|
||||
RG::TextureHandle m_Dst; // backbuffer
|
||||
};
|
||||
|
||||
} // namespace Engine::Passes
|
||||
|
||||
#endif //HELLOVULKAN_BLITPASS_H
|
||||
@@ -28,6 +28,12 @@ namespace Engine::Passes {
|
||||
void Register(RG::RenderGraph& rg) override;
|
||||
|
||||
private:
|
||||
// 两段渲染共享的命令录制(shader/描述符绑定 + 几何/光栅/视口状态)。
|
||||
// translucent=true 绑定 gl_FragDepth=1.0 的透明 frag 变体;
|
||||
// 深度写与混合状态由各渲染实例作为初始态设置,见 ForwardPass.cpp
|
||||
void RecordSharedState(const RG::RenderGraphContext& ctx,
|
||||
vk::CommandBuffer cmd, bool translucent) const;
|
||||
|
||||
SceneGpu& m_Gpu;
|
||||
FrameSceneData& m_Frame;
|
||||
RG::TextureHandle m_HDRColor;
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace Engine {
|
||||
kDebugIrradiance, // IBL 漫反射辐照度(32² 低频)
|
||||
kDebugPrefilter, // IBL pre-filter(纵向 = mip/roughness 分级)
|
||||
kDebugBrdfLut, // BRDF 积分 LUT(RG 双通道)
|
||||
kDebugTransparent, // 仅透明队列(Skybox/Forward 跳过绘制,看透明段内容)
|
||||
kDebugViewCount
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define HELLOVULKAN_IMGUIPASS_H
|
||||
|
||||
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||
#include <Engine/Renderer/SceneTypes.h> // FrameSceneData(displayMode 帧契约)
|
||||
|
||||
namespace Engine {
|
||||
|
||||
@@ -21,15 +22,22 @@ namespace Engine {
|
||||
class ImGuiPass final : public RenderPass {
|
||||
public:
|
||||
// 二级指针:Renderer::SetImGuiLayer 在 Register 之后才可能被调
|
||||
//(EnableImGui 时机由派生类决定),执行期解引用取最新值
|
||||
ImGuiPass(ImGuiLayer** layer, RG::TextureHandle backbuffer)
|
||||
: m_Layer(layer), m_Backbuffer(backbuffer) {}
|
||||
//(EnableImGui 时机由派生类决定),执行期解引用取最新值。
|
||||
// presentTarget:ImGui::Image 采样的场景纹理(tonemap 输出)。
|
||||
// loadOp 按 displayMode 分支:Direct = Load(blit 已铺场景),
|
||||
// Viewport = Clear(唯一写入者,清"桌面底色")
|
||||
ImGuiPass(ImGuiLayer** layer, FrameSceneData& frame,
|
||||
RG::TextureHandle backbuffer, RG::TextureHandle presentTarget)
|
||||
: m_Layer(layer), m_Frame(frame), m_Backbuffer(backbuffer),
|
||||
m_PresentTarget(presentTarget) {}
|
||||
|
||||
void Register(RG::RenderGraph& rg) override;
|
||||
|
||||
private:
|
||||
ImGuiLayer** m_Layer;
|
||||
FrameSceneData& m_Frame; // displayMode(帧契约)
|
||||
RG::TextureHandle m_Backbuffer;
|
||||
RG::TextureHandle m_PresentTarget;
|
||||
};
|
||||
|
||||
} // namespace Passes
|
||||
|
||||
@@ -25,22 +25,36 @@ namespace Engine {
|
||||
class ShaderManager;
|
||||
class UniformBuffer;
|
||||
class Cubemap;
|
||||
class Buffer;
|
||||
class MemoryAllocator;
|
||||
|
||||
namespace Passes {
|
||||
|
||||
class SceneGpu {
|
||||
public:
|
||||
SceneGpu(Device& device, DescriptorManager& descriptors,
|
||||
SceneGpu(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptors,
|
||||
ShaderManager& shaders,
|
||||
UniformBuffer& ubo, UniformBuffer& objectBuffer,
|
||||
UniformBuffer& prevObjectBuffer);
|
||||
UniformBuffer& prevObjectBuffer,
|
||||
UniformBuffer& skinOffsetBuffer,
|
||||
UniformBuffer& skinPaletteBuffer);
|
||||
~SceneGpu();
|
||||
|
||||
SceneGpu(const SceneGpu&) = delete;
|
||||
SceneGpu& operator=(const SceneGpu&) = delete;
|
||||
|
||||
// ---- MDI 间接绘制 ----
|
||||
// CPU 打包的命令/count 落进本槽位 ring(Renderer 在 fence
|
||||
// 等待后调用——与 UBO/SSBO 上传同一安全窗口)
|
||||
void UploadIndirect(u32 frameSlot, const FrameSceneData& frame);
|
||||
// 一次组绘制:bind 共享 megabuffer + drawIndexedIndirectCount
|
||||
void DrawIndirectGroup(vk::CommandBuffer cmd, u32 frameSlot,
|
||||
u32 viewIndex, const IndirectGroup& g);
|
||||
|
||||
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
||||
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
||||
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer,
|
||||
// SkinOffsets, SkinPalettes}
|
||||
vk::DescriptorSetLayout textureSetLayout; // Set1 = {材质 4 槽 + 参数 UBO}
|
||||
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图, irradiance cube}
|
||||
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
||||
@@ -54,7 +68,16 @@ namespace Engine {
|
||||
//(省 shader 出界分支)
|
||||
vk::Sampler shadowSampler;
|
||||
|
||||
// ---- MDI per-slot ring ----
|
||||
// 布局:[main | cascade0 | cascade1 | cascade2],每区
|
||||
// kMaxInstances 条命令预算(每实例至多 1 条命令,必不越界)。
|
||||
// count buffer 同分区(每组一个 u32,下标 = 组首命令下标)
|
||||
std::array<Scope<Buffer>, kMaxFramesInFlight> indirectCmdBuffers;
|
||||
std::array<Scope<Buffer>, kMaxFramesInFlight> indirectCountBuffers;
|
||||
|
||||
vk::ShaderEXT forwardVert{}, forwardFrag{};
|
||||
// 透明变体:同 simple_shader + gl_FragDepth=1.0(见 frag 注释)
|
||||
vk::ShaderEXT translucentFrag{};
|
||||
vk::ShaderEXT shadowVert{}, shadowFrag{};
|
||||
|
||||
// RG 重编译后重绑阴影图(物理镜像按槽位各一份)
|
||||
@@ -66,6 +89,7 @@ namespace Engine {
|
||||
|
||||
private:
|
||||
Device& m_Device;
|
||||
MemoryAllocator& m_Allocator;
|
||||
DescriptorManager& m_Descriptors;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/23.
|
||||
//
|
||||
// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 Backbuffer。
|
||||
// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 PresentTarget。
|
||||
// 显示变换在此显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。
|
||||
// Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。
|
||||
// PresentTarget 是 offscreen 可采样纹理:由 ImGui::Image 呈现(输出位置
|
||||
// 可自由摆放),backbuffer 不再接收场景画面。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_TONEMAPPASS_H
|
||||
@@ -28,7 +29,8 @@ namespace Engine {
|
||||
PostProcessParams& params,
|
||||
FrameSceneData& frame,
|
||||
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
||||
RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer,
|
||||
RG::TextureHandle temporalDiff,
|
||||
RG::TextureHandle presentTarget,
|
||||
RG::TextureHandle rawHdr, RG::TextureHandle velocity,
|
||||
RG::TextureHandle shadowMap0, RG::TextureHandle shadowMap1,
|
||||
RG::TextureHandle shadowMap2);
|
||||
@@ -46,7 +48,7 @@ namespace Engine {
|
||||
RG::TextureHandle m_HDR;
|
||||
RG::TextureHandle m_A;
|
||||
RG::TextureHandle m_TemporalDiff;
|
||||
RG::TextureHandle m_Backbuffer;
|
||||
RG::TextureHandle m_Target; // PresentTarget(offscreen,ImGui 采样呈现)
|
||||
// ---- 调试视图源(binding 3-7,Register 时声明读防止剔除)----
|
||||
// 注:TAAColor 视图直接用 binding0(主输入即 TAA 输出),无需重复绑
|
||||
RG::TextureHandle m_RawHDR; // Forward 直出(TAA 前)
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Engine {
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
|
||||
void Render(ECS::Scene& scene, Assets::AssetManager& assets);
|
||||
void Render(ECS::Scene& scene, Assets::AssetManager& assets, f32 dt);
|
||||
|
||||
// ---- 组合根(Application)装配其他模块时需要的访问器 ----
|
||||
Device& GetDevice() const { return *m_Device; }
|
||||
@@ -72,7 +72,18 @@ namespace Engine {
|
||||
|
||||
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
||||
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
||||
void SetImGuiLayer(ImGuiLayer* layer) { m_ImGuiLayer = layer; }
|
||||
void SetImGuiLayer(ImGuiLayer* layer)
|
||||
{
|
||||
m_ImGuiLayer = layer;
|
||||
if (layer) RefreshSceneTextures(); // 后端就绪:注册场景纹理
|
||||
}
|
||||
|
||||
// ---- 场景显示纹理(tonemap 输出,ImGui::Image 可采样显示)----
|
||||
// 按 m_CurrentFrame 取当帧槽位的描述符(UI 构建段与紧随的
|
||||
// Render 同槽位)。无 ImGui 层时返回 nullptr
|
||||
void* GetSceneTexture() const;
|
||||
// 渲染分辨率(PresentTarget 尺寸,等比适配用)
|
||||
vk::Extent2D GetRenderExtent() const { return m_Extent; }
|
||||
|
||||
// ---- 后处理参数(ImGui 可调,转发到 pass 共享的 PostProcessParams)----
|
||||
f32 GetExposure() const;
|
||||
@@ -115,13 +126,32 @@ namespace Engine {
|
||||
bool GetInstancesTruncated() const; // 实例预算溢出(遥测红字)
|
||||
f32 GetCollectMs() const; // CPU 收集+剔除段耗时
|
||||
|
||||
// ---- 显示模式(Editor 视口 ↔ 全屏直出;帧首同步生效)----
|
||||
int GetDisplayMode() const { return m_DesiredDisplayMode; }
|
||||
void SetDisplayMode(int mode); // cpp 内定义(SceneRenderer 前置声明)
|
||||
// Editor 视口的期望渲染尺寸(像素;UI 面板每帧上报,帧首统一生效)
|
||||
void SetViewportSize(u32 w, u32 h)
|
||||
{
|
||||
m_DesiredViewport = vk::Extent2D{w, h};
|
||||
}
|
||||
|
||||
// 垂直同步开关(present mode 切换会重建 swapchain),
|
||||
// 只应在帧 UI 构建段调用(Render 之前)
|
||||
void SetVSync(bool vsync);
|
||||
void SetVSync(bool vsync) { m_DesiredVSync = vsync; } // 帧首同步生效
|
||||
|
||||
// 帧首同步:窗口 resize / 视口尺寸 / 显示模式 / vsync 意图统一在
|
||||
// UI 构建之前落定(waitIdle → 重编译 → 重绑描述符)。必须先于
|
||||
// UI 构建段调用:本帧 draw data 引用的描述符与物理资源由此保证
|
||||
// 全程新鲜(resize 中途换描述符会绑出已销毁句柄)
|
||||
void BeginFrameSync();
|
||||
private:
|
||||
void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象
|
||||
void HandleResize();
|
||||
void HandleResize(bool recreateSwapchain);
|
||||
void ReadGpuTimes(u32 frameSlot);
|
||||
void RefreshSceneTextures(); // 物理 view → ImGui 描述符(重建后刷新)
|
||||
// 期望渲染分辨率:Editor 视口 = 面板尺寸(未上报时回退 swapchain);
|
||||
// Direct = swapchain 尺寸
|
||||
vk::Extent2D DesiredRenderExtent() const;
|
||||
|
||||
Window& m_Window;
|
||||
|
||||
@@ -133,6 +163,8 @@ namespace Engine {
|
||||
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
|
||||
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
|
||||
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
|
||||
Scope<UniformBuffer> m_SkinOffsetBuffer; // 实例→palette 基址 ring(SSBO,与实例序平行)
|
||||
Scope<UniformBuffer> m_SkinPaletteBuffer; // 骨骼蒙皮矩阵 palette ring(SSBO,帧首 bump)
|
||||
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
||||
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
||||
// 环境预积分生成端(compute 链),先于 m_Environment/m_Passes 构造
|
||||
@@ -152,6 +184,7 @@ namespace Engine {
|
||||
|
||||
// ---- RenderGraph 资源句柄 ----
|
||||
RG::TextureHandle m_Backbuffer{};
|
||||
RG::TextureHandle m_PresentTarget{}; // tonemap 输出(offscreen,ImGui 采样)
|
||||
RG::TextureHandle m_Depth{};
|
||||
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps{}; // CSM 级联
|
||||
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
|
||||
@@ -180,6 +213,11 @@ namespace Engine {
|
||||
|
||||
vk::Extent2D m_Extent{};
|
||||
u32 m_CurrentFrame = 0;
|
||||
|
||||
// ---- 帧首同步的意图缓冲(UI 段写入,下一帧帧首落定)----
|
||||
int m_DesiredDisplayMode = kDisplayDirect; // 默认直接渲染, 视口渲染需要手动指定一下
|
||||
vk::Extent2D m_DesiredViewport{}; // Editor 视口渲染分辨率(面板上报)
|
||||
bool m_DesiredVSync = true;
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Renderer/Mesh.h>
|
||||
#include <Engine/Renderer/SceneTypes.h>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
@@ -36,8 +37,9 @@ namespace Engine {
|
||||
public:
|
||||
// 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。
|
||||
// targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
|
||||
// dt 驱动骨骼动画采样推进(crossfade 计时)。
|
||||
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||
vk::Extent2D targetExtent);
|
||||
vk::Extent2D targetExtent, f32 dt);
|
||||
|
||||
FrameSceneData& Frame() { return m_Frame; }
|
||||
|
||||
@@ -59,6 +61,7 @@ namespace Engine {
|
||||
void SetBloomEnabled(bool b) { m_BloomEnabled = b; }
|
||||
void SetLodEnabled(bool b) { m_LodEnabled = b; }
|
||||
void SetDebugView(int v) { m_DebugView = v; }
|
||||
void SetDisplayMode(int m) { m_DisplayMode = m; } // kDisplayDirect / kDisplayViewport
|
||||
|
||||
private:
|
||||
// CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
|
||||
@@ -66,6 +69,12 @@ namespace Engine {
|
||||
//(Forward UBO)共用同一组矩阵。
|
||||
void ComputeCascades(const glm::vec3& shadowDir);
|
||||
|
||||
// MDI 打包:batch 按组键排序(megabuffer + 可选材质)后压成
|
||||
// IndirectDrawCmd 连续数组 + IndirectGroup 区间表。
|
||||
// byMaterial=true(Forward):每材质一次 bind + 一次 MDI;
|
||||
// byMaterial=false(Shadow):只按 megabuffer 分组(depth-only)
|
||||
static void PackIndirect(CullResult& view, bool byMaterial);
|
||||
|
||||
FrameSceneData m_Frame;
|
||||
|
||||
// ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)----
|
||||
@@ -74,6 +83,7 @@ namespace Engine {
|
||||
struct InstanceMatrices {
|
||||
glm::mat4 cur;
|
||||
glm::mat4 prev;
|
||||
u32 skinBase = 0; // palette 区段基址(0 = identity 槽)
|
||||
};
|
||||
CullResult result;
|
||||
// 每 batch 自己的实例列表(按 mesh 分组)。GPU 端 batch 语义 =
|
||||
@@ -108,6 +118,17 @@ namespace Engine {
|
||||
std::unordered_map<u32, glm::mat4> m_PrevWorlds;
|
||||
std::unordered_map<u32, glm::mat4> m_CurWorlds;
|
||||
|
||||
// ---- 骨骼动画 scratch(帧首重建/容量复用)----
|
||||
// entKey → palette 区段基址(第 2.5 节预采样填,收集段查)
|
||||
std::unordered_map<u32, u32> m_EntitySkinBase;
|
||||
// 采样层 A(当前 clip)/ B(fade 来源 clip)与 global 累乘
|
||||
std::vector<glm::vec3> m_PosePosA, m_PosePosB;
|
||||
std::vector<glm::quat> m_PoseRotA, m_PoseRotB;
|
||||
std::vector<glm::vec3> m_PoseSclA, m_PoseSclB;
|
||||
std::vector<u8> m_TouchedA, m_TouchedB;
|
||||
std::vector<glm::mat4> m_Globals;
|
||||
bool m_SkinTruncWarned = false; // palette 预算溢出只告警一次
|
||||
|
||||
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
|
||||
|
||||
// ---- 运行时开关/调试视图状态(本类是唯一写入者)----
|
||||
@@ -116,6 +137,7 @@ namespace Engine {
|
||||
bool m_BloomEnabled = true;
|
||||
bool m_LodEnabled = true; // 关 = 全部强制 LOD0(选级跳过)
|
||||
int m_DebugView = 0;
|
||||
int m_DisplayMode = kDisplayViewport; // Editor 视口为默认形态
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
namespace Engine {
|
||||
|
||||
class Mesh;
|
||||
class Material;
|
||||
|
||||
// GPUDriven:按 (mesh, LOD) 聚合的绘制批——同 mesh 不同 LOD 的实例
|
||||
// 索引区间不同,不能并成一次 draw
|
||||
@@ -28,10 +29,38 @@ namespace Engine {
|
||||
u32 lod = 0; // 绘制用索引区间(Mesh::Draw)
|
||||
};
|
||||
|
||||
// 一个渲染视图的剔除结果(相机视图 / 光源视图各一份)
|
||||
// MDI:一次 vkCmdDrawIndexedIndirectCount 消费的命令(CPU 侧镜像,
|
||||
// 内存布局 = VkDrawIndexedIndirectCommand,20B)
|
||||
struct IndirectDrawCmd {
|
||||
u32 indexCount;
|
||||
u32 instanceCount;
|
||||
u32 firstIndex; // megabuffer 里的全局索引下标
|
||||
u32 vertexOffset; // megabuffer 里的顶点元素偏移
|
||||
u32 firstInstance; // 实例矩阵数组下标
|
||||
};
|
||||
static_assert(sizeof(IndirectDrawCmd) == 20, "布局必须镜像 VkDrawIndexedIndirectCommand");
|
||||
|
||||
// MDI:共享同一 megabuffer(同 Model)的一组命令——
|
||||
// 一次 bindVertex/IndexBuffer +(Forward)一次材质 Set1 绑定 +
|
||||
// 一次 drawIndexedIndirectCount。组内命令在 indirect buffer 中连续
|
||||
struct IndirectGroup {
|
||||
const Mesh* mesh = nullptr; // 取共享 VB/IB 句柄(组内同一份)
|
||||
Material* material = nullptr; // shadow 组为 null(depth-only 无材质)
|
||||
u32 firstCommand = 0; // 命令区起始下标(count 同下标)
|
||||
u32 commandCount = 0;
|
||||
};
|
||||
|
||||
// indirect buffer 的视图分区:[main | cascade0 | cascade1 | cascade2],
|
||||
// 每区 kMaxInstances 条命令预算(每实例至多 1 条,必不越界)
|
||||
constexpr u32 kIndirectViewMain = 0;
|
||||
|
||||
// 一个渲染视图的剔除结果(相机视图 / 光源视图各一份)。
|
||||
// batches = 逐实例批次(透明队列仍逐批 draw);indirect = MDI 打包产物
|
||||
struct CullResult {
|
||||
std::vector<InstanceBatch> batches;
|
||||
u32 instanceCount = 0;
|
||||
std::vector<IndirectDrawCmd> indirectCommands;
|
||||
std::vector<IndirectGroup> indirectGroups;
|
||||
};
|
||||
|
||||
constexpr u32 kMaxLights = 4;
|
||||
@@ -43,6 +72,13 @@ namespace Engine {
|
||||
constexpr u32 kMaxCascades = 3; // CSM 级联数
|
||||
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
|
||||
|
||||
// 蒙皮矩阵预算(palette 总槽位数/帧):每动画实体一段(按实体分配,
|
||||
// 不按模型去重——同模型两实体可不同播放状态)。4096×64B = 256KB/槽位
|
||||
constexpr u32 kMaxSkinMatrices = 4096;
|
||||
// palette slot 0 固定 identity:无骨骼顶点(weights=(1,0,0,0), idx=0)
|
||||
// 直通它 → 静态模型蒙皮零影响
|
||||
constexpr u32 kIdentityPaletteSlot = 0;
|
||||
|
||||
// 镜像 shader 侧 UBO 布局的灯光项(std140,每灯 64B)。
|
||||
// 字段未用部分置零——shader 按类型分支读取
|
||||
struct LightData {
|
||||
@@ -70,6 +106,14 @@ namespace Engine {
|
||||
glm::vec4 cameraPos; // 相机世界位置(高光 V 向量)
|
||||
};
|
||||
|
||||
// 显示模式(帧契约 displayMode 字段;ImGui 经 Renderer 门面写入):
|
||||
// Direct = 全屏直出:Blit pass 把 PresentTarget 拷进 backbuffer,
|
||||
// ImGui 叠加其上(游戏形态)
|
||||
// Viewport = Editor 视口:backbuffer 只画 ImGui(清屏),场景经
|
||||
// ImGui::Image 呈现;渲染分辨率跟随 ImGui 面板尺寸
|
||||
inline constexpr int kDisplayDirect = 0;
|
||||
inline constexpr int kDisplayViewport = 1;
|
||||
|
||||
// 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。
|
||||
// 复用成员容量:clear() 不缩容,稳态零每帧堆分配。
|
||||
struct FrameSceneData {
|
||||
@@ -87,6 +131,15 @@ namespace Engine {
|
||||
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
||||
// 实例的上一帧姿态,TAA velocity 用);首帧/新实体 = 当前矩阵
|
||||
std::vector<glm::mat4> prevInstanceModels;
|
||||
|
||||
// ---- 蒙皮(与 instanceModels 平行的 per-instance 间接索引)----
|
||||
// skinOffsets[i] = 第 i 个实例的 palette 区段起点(kMaxSkinMatrices
|
||||
// 全局数组内下标);无动画实体指向 slot0 identity
|
||||
std::vector<u32> skinOffsets;
|
||||
// palette 区段池:帧首 bump allocator 按动画实体分配
|
||||
// [skinBase .. skinBase+boneCount) = 该实体全部骨骼的蒙皮矩阵
|
||||
std::vector<glm::mat4> skinPalettes;
|
||||
|
||||
CullResult mainView; // 相机视锥 → Forward pass
|
||||
// 每级联独立剔除结果(Shadow pass 按 region 各画各的——
|
||||
// 级联盒沿光方向加了 caster pad,影子不会被剔除裁掉)
|
||||
@@ -102,10 +155,13 @@ namespace Engine {
|
||||
bool bloomEnabled = true; // BloomPass 子 pass 空转 + tonemap 强度归零
|
||||
bool lodEnabled = true; // 关 = 实例收集恒选 LOD0(绘制原网格)
|
||||
int debugView = 0; // Passes::DebugView 枚举(tonemap 路由上屏)
|
||||
int displayMode = kDisplayViewport; // 显示模式(Blit 路由 + ImGui loadOp)
|
||||
|
||||
// 实例预算溢出(kMaxInstances):本轮收集有实例被静默丢弃——
|
||||
// 必须遥测可见,"只告警一次"的大场景截断等于没有告警
|
||||
bool instancesTruncated = false;
|
||||
// palette 预算溢出(kMaxSkinMatrices):动画实体 palette 被截断
|
||||
bool skinPaletteTruncated = false;
|
||||
|
||||
f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测)
|
||||
};
|
||||
|
||||
@@ -16,6 +16,14 @@ namespace Engine {
|
||||
glm::vec2 uv;
|
||||
glm::vec3 tangent; // 切线(法线贴图 TBN 用;assimp CalcTangentSpace)
|
||||
|
||||
// ---- 骨骼蒙皮属性 ----
|
||||
// 无骨骼资产保持默认值(weights=(1,0,0,0) + palette[0]=identity)
|
||||
// → skin 矩阵 = identity,蒙皮路径对静态模型零影响。
|
||||
// weights 必须带默认初始化器:聚合初始化 Vertex{pos,normal,uv,tangent}
|
||||
// 会把未列出的成员零初始化 → skin=mat4(0) → 顶点全部塌缩到原点
|
||||
glm::uvec4 boneIds{0, 0, 0, 0};
|
||||
glm::vec4 weights{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
static vk::VertexInputBindingDescription2EXT GetBindingDescription() {
|
||||
vk::VertexInputBindingDescription2EXT binding;
|
||||
binding.setBinding(0)
|
||||
@@ -25,8 +33,8 @@ namespace Engine {
|
||||
return binding;
|
||||
}
|
||||
|
||||
static std::array<vk::VertexInputAttributeDescription2EXT, 4> GetAttributeDescriptions() {
|
||||
std::array<vk::VertexInputAttributeDescription2EXT, 4> attrs;
|
||||
static std::array<vk::VertexInputAttributeDescription2EXT, 6> GetAttributeDescriptions() {
|
||||
std::array<vk::VertexInputAttributeDescription2EXT, 6> attrs;
|
||||
|
||||
attrs[0].setLocation(0)
|
||||
.setBinding(0)
|
||||
@@ -48,6 +56,16 @@ namespace Engine {
|
||||
.setFormat(vk::Format::eR32G32B32Sfloat)
|
||||
.setOffset(offsetof(Vertex, tangent));
|
||||
|
||||
attrs[4].setLocation(4)
|
||||
.setBinding(0)
|
||||
.setFormat(vk::Format::eR32G32B32A32Uint)
|
||||
.setOffset(offsetof(Vertex, boneIds));
|
||||
|
||||
attrs[5].setLocation(5)
|
||||
.setBinding(0)
|
||||
.setFormat(vk::Format::eR32G32B32A32Sfloat)
|
||||
.setOffset(offsetof(Vertex, weights));
|
||||
|
||||
return attrs;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,7 +26,9 @@ namespace Engine {
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
vk::Buffer Get() const { return m_Buffer; }
|
||||
vk::DeviceSize GetSize() const { return m_Size; }
|
||||
vk::DeviceSize Size() const { return m_Size; }
|
||||
// 持久映射指针(非 host visible 为 nullptr;诊断读回用)
|
||||
void* MappedPtr() const { return m_MappedPtr; }
|
||||
|
||||
// 只有 HOST_VISIBLE 的 Buffer 能调
|
||||
void Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset = 0) const;
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
bool IsDynamicRenderingSupported() const { return m_DynamicRenderingSupported; }
|
||||
bool IsShaderObjectSupported() const { return m_ShaderObjectSupported; }
|
||||
bool IsSynchronization2Supported() const { return m_Synchronization2Supported; }
|
||||
// MDI:drawCount>1 的 drawIndexedIndirect(Count) 必需(core 特性位)
|
||||
bool IsMultiDrawIndirectSupported() const { return m_MultiDrawIndirectSupported; }
|
||||
|
||||
private:
|
||||
void CreateInstance();
|
||||
@@ -72,6 +74,8 @@ private:
|
||||
bool m_DynamicRenderingSupported = false;
|
||||
bool m_ShaderObjectSupported = false;
|
||||
bool m_Synchronization2Supported = false;
|
||||
bool m_MultiDrawIndirectSupported = false;
|
||||
bool m_DrawIndirectCountSupported = false;
|
||||
|
||||
std::vector<const char*> m_ValidationLayers = { "VK_LAYER_KHRONOS_validation" };
|
||||
std::vector<const char*> m_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
||||
|
||||
Reference in New Issue
Block a user