perf: CSM 每级联独立剔除 + 光向 caster pad
- 场景剔除从 [主|阴影] 两视图扩展为 [主|级联0|级联1|级联2] 四视图: 每级联只画自己盒内的 batch,消除原先 3× 全量阴影 draw 的浪费 - 实例数组拼接为四段,batch.first 加段基址保持段内连续; ViewScratch 分桶收集复用容量,稳态零堆分配 - ComputeCascades 沿光方向扩展级联深度范围(pad=30)——切片盒外 caster(高树/城墙)投影进级联的影子不会被剔除裁掉,per-cascade 剔除正确性的前提;只扩 z 不缩 XY,分辨率不受影响 - 遥测 shadow 计数 = 各级联求和 - SunTemple 1056 mesh 全量加载,validation 零警告
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<f32>(kCascadeRes);
|
||||
const f32 texelY = (mx.y - mn.y) / static_cast<f32>(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<TransformComponent, MeshRendererComponent>(
|
||||
[&](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<Mesh*, u32>& 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<u32>(view.batches.size()));
|
||||
const auto [it, inserted] = s.lookup.emplace(
|
||||
mesh.get(), static_cast<u32>(s.worlds.size()));
|
||||
if (inserted) {
|
||||
view.batches.push_back({mesh.get(),
|
||||
static_cast<u32>(m_Frame.instanceModels.size()), 0});
|
||||
s.result.batches.push_back({mesh.get(),
|
||||
static_cast<u32>(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<u32>(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();
|
||||
|
||||
Reference in New Issue
Block a user