实例数组按实体序收集([实体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 单实例天然连续),容量跨帧复用。
111 lines
4.6 KiB
C++
111 lines
4.6 KiB
C++
//
|
||
// 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
|