From fa9681aa4122a3338591e4d06a2358cb7cf4bbb4 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Thu, 24 Sep 2026 16:23:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20GPUDriven=20batch=20=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E6=80=A7=E2=80=94=E2=80=94=E5=A4=9A=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E5=85=B1=E4=BA=AB=20mesh=20=E6=97=B6=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E6=95=B0=E7=BB=84=E9=94=99=E4=B9=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实例数组按实体序收集([实体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 单实例天然连续),容量跨帧复用。 --- .../include/Engine/Renderer/SceneRenderer.h | 11 +++--- Engine/src/Renderer/SceneRenderer.cpp | 36 +++++++++++-------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index 8b78546..f38b825 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -68,16 +68,19 @@ namespace Engine { // ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)---- struct ViewScratch { CullResult result; - std::vector worlds; - std::vector prevWorlds; + // 每 batch 自己的实例列表(按 mesh 分组)。GPU 端 batch 语义 = + // firstInstance + 连续区间,实体序收集不满足(多实体共享 mesh + // 时实例分散各处),扁平化成连续数组推迟到 appendView + std::vector> batchWorlds; + std::vector> batchPrevWorlds; std::unordered_map lookup; void Clear() { result.batches.clear(); result.instanceCount = 0; - worlds.clear(); - prevWorlds.clear(); + batchWorlds.clear(); + batchPrevWorlds.clear(); lookup.clear(); } }; diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 1003394..0a2786d 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -310,13 +310,17 @@ namespace Engine const auto [it, inserted] = s.lookup.emplace( mesh.get(), static_cast(s.result.batches.size())); if (inserted) { - s.result.batches.push_back({mesh.get(), - static_cast(s.worlds.size()), 0}); + s.result.batches.push_back({mesh.get(), 0, 0}); + s.batchWorlds.emplace_back(); + s.batchPrevWorlds.emplace_back(); } - s.result.batches[it->second].count++; + // 实例归入自己的 batch(mesh 分组),保证同 batch + // 实例在扁平化后连续——firstInstance 语义的前提 + const u32 batchIdx = it->second; + s.result.batches[batchIdx].count++; + s.batchWorlds[batchIdx].push_back(t.World()); + s.batchPrevWorlds[batchIdx].push_back(prevWorld); s.result.instanceCount++; - s.worlds.push_back(t.World()); - s.prevWorlds.push_back(prevWorld); ++totalInstances; }; @@ -327,21 +331,25 @@ namespace Engine } }); - // ---- 4. 拼接:[主段|级联0|级联1|级联2],batch.first 加段基址 ---- + // ---- 4. 拼接:[主段|级联0|级联1|级联2],batch 内实例连续扁平化 ---- auto appendView = [&](CullResult& dst, ViewScratch& s) { - const u32 base = static_cast(m_Frame.instanceModels.size()); + u32 base = static_cast(m_Frame.instanceModels.size()); dst.batches.clear(); dst.batches.reserve(s.result.batches.size()); - for (auto& b : s.result.batches) { - b.first += base; + for (size_t i = 0; i < s.result.batches.size(); ++i) { + InstanceBatch b = s.result.batches[i]; + b.first = base; // 扁平化后的连续区间基址 + base += b.count; dst.batches.push_back(b); + m_Frame.instanceModels.insert(m_Frame.instanceModels.end(), + s.batchWorlds[i].begin(), + s.batchWorlds[i].end()); + m_Frame.prevInstanceModels.insert( + m_Frame.prevInstanceModels.end(), + s.batchPrevWorlds[i].begin(), + s.batchPrevWorlds[i].end()); } dst.instanceCount = s.result.instanceCount; - m_Frame.instanceModels.insert(m_Frame.instanceModels.end(), - s.worlds.begin(), s.worlds.end()); - m_Frame.prevInstanceModels.insert(m_Frame.prevInstanceModels.end(), - s.prevWorlds.begin(), - s.prevWorlds.end()); }; appendView(m_Frame.mainView, m_MainScratch); for (u32 c = 0; c < kMaxCascades; ++c) {