entt entity 0's integral bits are 0, colliding with the "no selection"
sentinel: u32 selected == 0 meant "clear selection", so the scene's
first-created entity could never be outlined. Replaced with
std::optional<u32> + FrameSceneData::selectedEntityValid.
Also restore per-batch instance contiguity broken by the batchMats
flattening (e038cb3): entities sharing a mesh scatter across the flat
instance array in collection order. ViewScratch now tracks per-batch
singly-linked lists and appendView aggregates each batch into a
contiguous range, preserving the flat-array performance intent.
Hierarchy click selection now routes through SelectEntity so outline
stays in sync when picking from the panel.
179 lines
8.8 KiB
C++
179 lines
8.8 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/Mesh.h>
|
||
#include <Engine/Renderer/SceneTypes.h>
|
||
#include <glm/gtc/quaternion.hpp>
|
||
#include <vulkan/vulkan.hpp>
|
||
#include <array>
|
||
#include <optional>
|
||
#include <unordered_map>
|
||
|
||
#include <Engine/Renderer/EditorCamera.h>
|
||
|
||
namespace Engine {
|
||
|
||
namespace ECS { class Scene; }
|
||
namespace Assets { class AssetManager; }
|
||
|
||
class SceneRenderer {
|
||
public:
|
||
// 从场景构建当帧渲染数据。游戏形态无 primary 相机返回 false
|
||
//(渲染跳帧;编辑器形态恒用编辑器相机,不跳)。
|
||
// targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
|
||
// dt 驱动骨骼动画采样推进(crossfade 计时)。
|
||
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||
vk::Extent2D targetExtent, f32 dt);
|
||
|
||
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 SetLodEnabled(bool b) { m_LodEnabled = b; }
|
||
void SetDebugView(int v) { m_DebugView = v; }
|
||
void SetDisplayMode(int m) { m_DisplayMode = m; } // kDisplayDirect / kDisplayViewport
|
||
// 编辑器相机(视口 UI 状态,SceneRenderer 持有 = 渲染源在此分派);
|
||
// Application 驱动 Update,ViewportPanel/Inspector 经 Renderer 门面访问
|
||
EditorCamera& GetEditorCamera() { return m_EditorCamera; }
|
||
// Editor 视口拾取请求(UI 段写入,下一次 BuildFrame 落帧后清除)
|
||
void SetPickRequest(const glm::vec2& ndc)
|
||
{
|
||
m_PendingPick = PickRequest{true, ndc};
|
||
}
|
||
// Editor 选中实体(描边):UI 段写入,BuildFrame 落帧并筛出绘制。
|
||
// nullopt = 清选中(实体 0 号位模式 = 0,不能拿位值兼作哨兵)
|
||
void SetSelectedEntity(std::optional<u32> entityBits)
|
||
{
|
||
m_PendingSelected = entityBits;
|
||
}
|
||
|
||
private:
|
||
// CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
|
||
//(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样
|
||
//(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;
|
||
EditorCamera m_EditorCamera; // 编辑器相机(视口 UI 状态,见 GetEditorCamera)
|
||
PickRequest m_PendingPick{}; // 拾取请求缓冲(BuildFrame 落帧后清除)
|
||
std::optional<u32> m_PendingSelected; // 选中实体缓冲(nullopt = 无选中)
|
||
|
||
// ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)----
|
||
struct ViewScratch {
|
||
// cur + prev 合并写:两次随机堆访问 → 一次(cache miss 减半)
|
||
struct InstanceMatrices {
|
||
glm::mat4 cur;
|
||
glm::mat4 prev;
|
||
u32 skinBase = 0; // palette 区段基址(0 = identity 槽)
|
||
u32 entityBits = 0; // 实体位模式(HitProxy id pass 消费)
|
||
};
|
||
// 链表尾哨兵:kMaxInstances 远小于 u32 最大值,安全
|
||
static constexpr u32 kNoInstance = 0xFFFFFFFFu;
|
||
CullResult result;
|
||
// 扁平实例数组(所有 batch 的实例共用一份连续内存)。
|
||
// 实体收集序下同 batch 实例会散布(多实体共享 mesh 交错插入),
|
||
// 组内连接关系由 instanceNext 链表维护——appendView 时按 batch
|
||
// 聚合拷成连续区间(firstInstance 连续语义的前提)。大场景下
|
||
// 避免 vector<vector> 的小块分配,每帧 clear() 复用容量
|
||
std::vector<InstanceMatrices> instances;
|
||
std::vector<u32> instanceNext; // 与 instances 平行:同 batch 下一实例
|
||
// 每 batch 实例链头(与 result.batches 平行;kNoInstance = 空)
|
||
std::vector<u32> batchHead;
|
||
// 每 batch 实例距离累加(透明排序用:平均距离降序 = 远→近)
|
||
std::vector<f32> batchDistSum;
|
||
// batch 查找:数组寻址(下标 = Mesh::GetSlot(),值 = 每 LOD 的
|
||
// batchIdx+1,0 = 未用)——逐实例 hash 在大场景是毫秒级开销
|
||
std::vector<std::array<u32, kMeshLodCount>> lookup;
|
||
|
||
void Clear()
|
||
{
|
||
result.batches.clear();
|
||
result.instanceCount = 0;
|
||
instances.clear(); // 容量保留,复用
|
||
instanceNext.clear();
|
||
batchHead.clear();
|
||
batchDistSum.clear();
|
||
for (auto& e : lookup) e.fill(0); // 容量保留,置零复用
|
||
}
|
||
};
|
||
ViewScratch m_MainScratch;
|
||
ViewScratch m_CascScratch[kMaxCascades];
|
||
ViewScratch m_TranslucentScratch; // 透明材质(仅主视图,不投阴影)
|
||
|
||
// ---- 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)。
|
||
// 用 sorted vector + binary_search 替代 unordered_map:
|
||
// unordered_map 在每帧大量 emplace 时 rehash 抖动明显,
|
||
// vector 连续内存 + 单次 sort 对 cache 友好且无 rehash
|
||
std::vector<std::pair<u32, glm::mat4>> m_PrevWorlds;
|
||
std::vector<std::pair<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; // 实例缓冲打满只告警一次
|
||
|
||
// ---- 运行时开关/调试视图状态(本类是唯一写入者)----
|
||
bool m_ShadowsEnabled = true;
|
||
bool m_TAAEnabled = true;
|
||
bool m_BloomEnabled = true;
|
||
bool m_LodEnabled = true; // 关 = 全部强制 LOD0(选级跳过)
|
||
int m_DebugView = 0;
|
||
int m_DisplayMode = kDisplayViewport; // Editor 视口为默认形态
|
||
};
|
||
|
||
} // namespace Engine
|
||
|
||
#endif //HELLOVULKAN_SCENERENDERER_H
|