diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index 6965ff3..9e346bc 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -41,7 +41,12 @@ namespace Engine { // ---- 遥测 ---- u32 GetMainInstanceCount() const { return m_Frame.mainView.instanceCount; } - u32 GetShadowInstanceCount() const { return m_Frame.shadowView.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; } private: @@ -52,9 +57,24 @@ namespace Engine { FrameSceneData m_Frame; - // batch 聚合查找表(mesh → batch 下标),每帧 clear 复用桶 - std::unordered_map m_MainLookup; - std::unordered_map m_ShadowLookup; + // ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)---- + struct ViewScratch { + CullResult result; + std::vector worlds; + std::vector prevWorlds; + std::unordered_map lookup; + + void Clear() + { + result.batches.clear(); + result.instanceCount = 0; + worlds.clear(); + prevWorlds.clear(); + lookup.clear(); + } + }; + ViewScratch m_MainScratch; + ViewScratch m_CascScratch[kMaxCascades]; // ---- TAA 时域状态 ---- u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样) diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index d218132..12f4c6c 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -71,7 +71,9 @@ namespace Engine { // 实例的上一帧姿态,TAA velocity 用);首帧/新实体 = 当前矩阵 std::vector prevInstanceModels; CullResult mainView; // 相机视锥 → Forward pass - CullResult shadowView; // 光源视锥 → Shadow pass(相机外实体仍投影子) + // 每级联独立剔除结果(Shadow pass 按 region 各画各的—— + // 级联盒沿光方向加了 caster pad,影子不会被剔除裁掉) + CullResult shadowCascades[kMaxCascades]; f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测) }; diff --git a/Engine/src/Renderer/Passes/ShadowPass.cpp b/Engine/src/Renderer/Passes/ShadowPass.cpp index e480a13..2efb140 100644 --- a/Engine/src/Renderer/Passes/ShadowPass.cpp +++ b/Engine/src/Renderer/Passes/ShadowPass.cpp @@ -92,7 +92,7 @@ namespace Engine::Passes 0, sizeof(glm::mat4), &m_Frame.ubo.lightSpace[c]); - for (const auto& batch : m_Frame.shadowView.batches) { + for (const auto& batch : m_Frame.shadowCascades[c].batches) { batch.mesh->Draw(cmd, batch.count, batch.first); } diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 22b3106..6d96ca1 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -100,6 +100,13 @@ namespace Engine mx = glm::max(mx, lv); } + // 沿光方向扩展深度范围:切片盒外的 caster(如高树、城墙) + // 也能投影子进本级联——per-cascade 剔除的正确性前提。 + // 只扩 z 不缩 XY,阴影分辨率不受影响 + constexpr f32 kCasterPad = 30.0f; + mn.z -= kCasterPad; + mx.z += kCasterPad; + // 纹素对齐:X/Y 边界量化到纹素网格(消除相机移动时的阴影边缘闪烁) const f32 texelX = (mx.x - mn.x) / static_cast(kCascadeRes); const f32 texelY = (mx.y - mn.y) / static_cast(kCascadeRes); @@ -179,28 +186,28 @@ namespace Engine ComputeCascades(shadowDir); ubo.cameraPos = glm::vec4(camera.GetPosition(), 1.0f); - // ---- 3. GPUDriven:单趟收集 + 双视图剔除 ---- - // 每个实例对相机视锥、光源视锥分别做包围球测试: - // 进相机视锥 → 主视图 batch(Forward 画) - // 进光源视锥 → 阴影 batch(Shadow 画)——相机外实体仍可投影子 - // 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。 - // 哈希聚合保证各视图内 batch 区间连续。 + // ---- 3. GPUDriven:单趟收集 + 多视图剔除 ---- + // 每个实例的包围球对 4 个视锥分别测试: + // 相机视锥 → 主视图(Forward 画) + // 各级联光视锥 → 各自的阴影 batch(Shadow 按 region 各画各的) + // 级联盒沿光方向加了 caster pad(见 ComputeCascades): + // 盒外 caster 投进级联的影子不会被剔除裁掉。 + // 收集按视图分桶,最后拼接成 [主段|级联0|级联1|级联2], + // batch.first 拼接时加上段基址,保证段内区间连续。 m_Frame.instanceModels.clear(); m_Frame.prevInstanceModels.clear(); - m_Frame.mainView.batches.clear(); - m_Frame.shadowView.batches.clear(); - m_Frame.mainView.instanceCount = 0; - m_Frame.shadowView.instanceCount = 0; - m_MainLookup.clear(); - m_ShadowLookup.clear(); + m_MainScratch.Clear(); + for (auto& s : m_CascScratch) s.Clear(); + u32 totalInstances = 0; bool instanceTruncated = false; // 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已 - // 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用。 - // 光源视锥用最远级联(覆盖全阴影范围) + // 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用 const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view); - const Frustum shadowFrustum = - Frustum::FromMatrix(ubo.lightSpace[kMaxCascades - 1]); + Frustum cascFrustum[kMaxCascades]; + for (u32 c = 0; c < kMaxCascades; ++c) { + cascFrustum[c] = Frustum::FromMatrix(ubo.lightSpace[c]); + } scene.Each( [&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) { @@ -228,33 +235,55 @@ namespace Engine glm::vec3(t.world * glm::vec4(bounds.center, 1.0f)); const f32 worldRadius = bounds.radius * worldScale; - auto tryAppend = [&](CullResult& view, const Frustum& frustum, - std::unordered_map& lookup) { + auto tryAppend = [&](ViewScratch& s, const Frustum& frustum) { if (!frustum.IntersectsSphere(worldCenter, worldRadius)) { return; } - if (m_Frame.instanceModels.size() >= kMaxInstances) { + if (totalInstances >= kMaxInstances) { instanceTruncated = true; return; } - const auto [it, inserted] = lookup.emplace( - mesh.get(), static_cast(view.batches.size())); + const auto [it, inserted] = s.lookup.emplace( + mesh.get(), static_cast(s.worlds.size())); if (inserted) { - view.batches.push_back({mesh.get(), - static_cast(m_Frame.instanceModels.size()), 0}); + s.result.batches.push_back({mesh.get(), + static_cast(s.worlds.size()), 0}); } - view.batches[it->second].count++; - view.instanceCount++; - // 两个数组平行 push:同下标 = 同一实例的当前/上一帧姿态 - m_Frame.instanceModels.push_back(t.world); - m_Frame.prevInstanceModels.push_back(prevWorld); + s.result.batches[it->second].count++; + s.result.instanceCount++; + s.worlds.push_back(t.world); + s.prevWorlds.push_back(prevWorld); + ++totalInstances; }; - tryAppend(m_Frame.mainView, mainFrustum, m_MainLookup); - tryAppend(m_Frame.shadowView, shadowFrustum, m_ShadowLookup); + tryAppend(m_MainScratch, mainFrustum); + for (u32 c = 0; c < kMaxCascades; ++c) { + tryAppend(m_CascScratch[c], cascFrustum[c]); + } } }); + // ---- 4. 拼接:[主段|级联0|级联1|级联2],batch.first 加段基址 ---- + auto appendView = [&](CullResult& dst, ViewScratch& s) { + const 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; + dst.batches.push_back(b); + } + 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) { + appendView(m_Frame.shadowCascades[c], m_CascScratch[c]); + } + // 帧末滚动时域状态:cur → prev(每帧重建,销毁的实体自动清出) m_PrevWorlds.swap(m_CurWorlds); m_CurWorlds.clear();