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