Files
HelloVulkan/Engine/include/Engine/Renderer/SceneRenderer.h
T
atdunbg fa9681aa41 fix: GPUDriven batch 实例连续性——多实体共享 mesh 时实例数组错乱
实例数组按实体序收集([实体A的mesh][实体B的mesh]),而 GPU 端
batch 语义要求同一 mesh 的实例连续(firstInstance 基址 +
gl_InstanceIndex 直查 SSBO)。多实体共享 Model 后 mesh i 的各实例
分散在数组各处,batch.first+i 落到别的 mesh 的变换上——第二颗
背包的几何全画到第一颗的相邻 mesh 位置(碎裂/缺失/近看缺块)。

修:剔除期实例先归入各自 batch(ViewScratch 持有 per-batch 实例
列表),拼接时按 batch 扁平化为连续区间,batch.first = 扁平化基址。
单实体路径行为不变(每 mesh 单实例天然连续),容量跨帧复用。
2026-09-24 16:23:47 +08:00

111 lines
4.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/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 <vulkan/vulkan.hpp>
#include <unordered_map>
namespace Engine {
namespace ECS { class Scene; }
namespace Assets { class AssetManager; }
class SceneRenderer {
public:
// 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。
// targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
vk::Extent2D targetExtent);
FrameSceneData& Frame() { return m_Frame; }
// ---- 遥测 ----
u32 GetMainInstanceCount() const { return m_Frame.mainView.instanceCount; }
u32 GetShadowInstanceCount() const
{
u32 sum = 0;
for (const auto& c : m_Frame.shadowCascades) sum += c.instanceCount;
return sum;
}
f32 GetCollectMs() const { return m_Frame.collectMs; }
// ---- 运行时开关/调试视图(ImGui 经 Renderer 门面调用)。
// 状态唯一持有者在此,BuildFrame 每帧落入 FrameSceneData;
// TAA 开关同时控制投影抖动,同一持有者天然同步 ----
void SetShadowsEnabled(bool b) { m_ShadowsEnabled = b; }
void SetTAAEnabled(bool b) { m_TAAEnabled = b; }
void SetBloomEnabled(bool b) { m_BloomEnabled = b; }
void SetDebugView(int v) { m_DebugView = v; }
private:
// CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
//(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样
//(Forward UBO)共用同一组矩阵。
void ComputeCascades(const glm::vec3& shadowDir);
FrameSceneData m_Frame;
// ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)----
struct ViewScratch {
CullResult result;
// 每 batch 自己的实例列表(按 mesh 分组)。GPU 端 batch 语义 =
// firstInstance + 连续区间,实体序收集不满足(多实体共享 mesh
// 时实例分散各处),扁平化成连续数组推迟到 appendView
std::vector<std::vector<glm::mat4>> batchWorlds;
std::vector<std::vector<glm::mat4>> batchPrevWorlds;
std::unordered_map<Mesh*, u32> lookup;
void Clear()
{
result.batches.clear();
result.instanceCount = 0;
batchWorlds.clear();
batchPrevWorlds.clear();
lookup.clear();
}
};
ViewScratch m_MainScratch;
ViewScratch m_CascScratch[kMaxCascades];
// ---- TAA 时域状态 ----
u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样)
glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity)
glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity)
// 实体的上一帧世界矩阵(key = entt 实体 integral)。
// 每帧重建 m_CurWorlds 后与 m_PrevWorlds swap——自动清理销毁实体
std::unordered_map<u32, glm::mat4> m_PrevWorlds;
std::unordered_map<u32, glm::mat4> m_CurWorlds;
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
// ---- 运行时开关/调试视图状态(本类是唯一写入者)----
bool m_ShadowsEnabled = true;
bool m_TAAEnabled = true;
bool m_BloomEnabled = true;
int m_DebugView = 0;
};
} // namespace Engine
#endif //HELLOVULKAN_SCENERENDERER_H