feat: 双视图视锥剔除(相机/光源各自剔除)
- Frustum(Gribb-Hartmann):从 view-proj 行组合提取 6 平面, 包围球相交测试;按 Vulkan [0,1] 深度约定取 near=row2 - Mesh 构造时缓存局部空间包围球(顶点包围盒 → 中心+半径) - 实例收集重写:单趟遍历,每个 (实体, mesh 部件) 对相机视锥与 光源视锥分别做剔除——相机外实体仍可投影子进视野,Shadow 必须用 光源视锥独立剔除,不能用相机结果 - 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 全局下标; 两视图共享同一份 ObjectBuffer SSBO - 聚合改哈希(unordered_map),单趟填充替代原两趟遍历 - 面板显示 instances main/shadow 计数(剔除效果实时可见) 实测:1 背包 80 main / 80 shadow(修复前 shadow=21——near 平面 按 [-1,1] 矩阵误用 [0,1] 公式,光锥近半段被误剔)
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
#include <Engine/Vulkan/Buffer.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "Engine/Core/Stats.h"
|
||||
|
||||
namespace Engine
|
||||
@@ -43,6 +45,22 @@ namespace Engine
|
||||
|
||||
m_IndexCount = static_cast<u32>(indices.size());
|
||||
|
||||
// 局部空间包围球:顶点包围盒 → 中心 + 最大半径(视锥剔除用)
|
||||
{
|
||||
glm::vec3 mn(std::numeric_limits<f32>::max());
|
||||
glm::vec3 mx(std::numeric_limits<f32>::lowest());
|
||||
for (const auto& v : vertices) {
|
||||
mn = glm::min(mn, v.position);
|
||||
mx = glm::max(mx, v.position);
|
||||
}
|
||||
m_Bounds.center = (mn + mx) * 0.5f;
|
||||
m_Bounds.radius = 0.0f;
|
||||
for (const auto& v : vertices) {
|
||||
m_Bounds.radius = glm::max(m_Bounds.radius,
|
||||
glm::length(v.position - m_Bounds.center));
|
||||
}
|
||||
}
|
||||
|
||||
ENGINE_INFO("Mesh created: {} vertices, {} indices", vertices.size(), indices.size());
|
||||
|
||||
m_VertexBytes = sizeof(Vertex) * vertices.size();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <Engine/Core/Window.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/ImGui/ImGuiLayer.h>
|
||||
#include <Engine/Renderer/Frustum.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
@@ -446,9 +447,8 @@ namespace Engine
|
||||
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize});
|
||||
cmd.setScissorWithCount(scissor);
|
||||
|
||||
// ---- 绘制:GPUDriven——与 Forward 共用同一批 batch ----
|
||||
// lightVP 全 pass 只 push 一次;模型矩阵经实例索引从
|
||||
// ObjectBuffer 取(与 Forward 同一份数据)
|
||||
// ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用
|
||||
// 光源视锥剔除后的 batch(相机外实体仍投影子)----
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
m_ShadowPipelineLayout, 0,
|
||||
m_UBODescriptorSets[ctx.frame.frameSlot], {});
|
||||
@@ -459,7 +459,7 @@ namespace Engine
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0, sizeof(glm::mat4), &m_LightSpace);
|
||||
|
||||
for (const auto& batch : m_InstanceBatches) {
|
||||
for (const auto& batch : m_ShadowView.batches) {
|
||||
batch.mesh->Draw(cmd, batch.count, batch.first);
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ namespace Engine
|
||||
|
||||
vk::ClearValue clearValue;
|
||||
clearValue.color = vk::ClearColorValue(
|
||||
std::array<float, 4>{0.0f, 0.0f, 0.0f, 0.0f});
|
||||
std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f});
|
||||
|
||||
vk::RenderingAttachmentInfo colorAttachment;
|
||||
colorAttachment.setImageView(ctx.View(m_HDRColor))
|
||||
@@ -570,11 +570,11 @@ namespace Engine
|
||||
vk::ColorComponentFlagBits::eA;
|
||||
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||
|
||||
// ---- 绘制:GPUDriven 按 mesh 分 batch ----
|
||||
// ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)----
|
||||
// 实例矩阵已在 Render() 里写入 ObjectBuffer(per-slot SSBO);
|
||||
// 同 mesh 的 N 个实体合并为一次 drawIndexed(instanceCount=N),
|
||||
// draw 数 = 唯一 mesh 数,与实体数量解耦
|
||||
for (const auto& batch : m_InstanceBatches) {
|
||||
// 同 mesh 的 N 个可见实体合并为一次 drawIndexed(instanceCount=N),
|
||||
// draw 数 = 可见唯一 mesh 数,与实体数量解耦
|
||||
for (const auto& batch : m_MainView.batches) {
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
m_PipelineLayout, kTextureSetIndex,
|
||||
batch.mesh->GetMaterial()->GetDescriptorSet(), {});
|
||||
@@ -945,49 +945,69 @@ namespace Engine
|
||||
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
|
||||
m_SyncManager->ResetFence(frameSlot);
|
||||
|
||||
// ---- GPUDriven:收集实例数据(按 mesh 聚合 batch)----
|
||||
// 两趟遍历:先收集唯一 mesh(保持首现序),再逐 batch 追加实例矩阵,
|
||||
// 保证每个 batch 的矩阵区间连续(draw 用 firstInstance 指向起点)。
|
||||
// TODO: 实体量大时改哈希聚合 + 单趟填充
|
||||
// ---- GPUDriven:单趟收集 + 双视图剔除 ----
|
||||
// 每个实例对相机视锥、光源视锥分别做包围球测试:
|
||||
// 进相机视锥 → 主视图 batch(Forward 画)
|
||||
// 进光源视锥 → 阴影 batch(Shadow 画)——相机外实体仍可投影子
|
||||
// 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。
|
||||
// 哈希聚合保证各视图内 batch 区间连续。
|
||||
const auto collectStart = std::chrono::steady_clock::now();
|
||||
m_InstanceBatches.clear();
|
||||
m_InstanceModels.clear();
|
||||
m_MainView.batches.clear();
|
||||
m_ShadowView.batches.clear();
|
||||
m_MainView.instanceCount = 0;
|
||||
m_ShadowView.instanceCount = 0;
|
||||
m_MainBatchLookup.clear();
|
||||
m_ShadowBatchLookup.clear();
|
||||
bool instanceTruncated = false;
|
||||
|
||||
std::vector<Mesh*> uniqueMeshes;
|
||||
const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view);
|
||||
// 注意:剔除平面提取按 [0,1] 深度约定——相机 proj 与 m_LightSpace
|
||||
// 都已做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用
|
||||
const Frustum shadowFrustum = Frustum::FromMatrix(m_LightSpace);
|
||||
|
||||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent&, MeshRendererComponent& mr) {
|
||||
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||
Model* model = assets.GetModel(mr.model);
|
||||
if (!model) return;
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
if (std::find(uniqueMeshes.begin(), uniqueMeshes.end(),
|
||||
mesh.get()) == uniqueMeshes.end()) {
|
||||
uniqueMeshes.push_back(mesh.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (Mesh* meshPtr : uniqueMeshes) {
|
||||
InstanceBatch batch{meshPtr,
|
||||
static_cast<u32>(m_InstanceModels.size()), 0};
|
||||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||
Model* model = assets.GetModel(mr.model);
|
||||
if (!model) return;
|
||||
for (auto& m : model->GetMeshes()) {
|
||||
if (m.get() != meshPtr) continue;
|
||||
// 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放)
|
||||
const f32 worldScale = glm::max(
|
||||
glm::length(glm::vec3(t.world[0])),
|
||||
glm::max(glm::length(glm::vec3(t.world[1])),
|
||||
glm::length(glm::vec3(t.world[2]))));
|
||||
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
const auto& bounds = mesh->GetBounds();
|
||||
const glm::vec3 worldCenter =
|
||||
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) {
|
||||
if (!frustum.IntersectsSphere(worldCenter, worldRadius)) {
|
||||
return;
|
||||
}
|
||||
if (m_InstanceModels.size() >= kMaxInstances) {
|
||||
instanceTruncated = true;
|
||||
return;
|
||||
}
|
||||
const auto [it, inserted] = lookup.emplace(
|
||||
mesh.get(), static_cast<u32>(view.batches.size()));
|
||||
if (inserted) {
|
||||
view.batches.push_back({mesh.get(),
|
||||
static_cast<u32>(m_InstanceModels.size()), 0});
|
||||
}
|
||||
view.batches[it->second].count++;
|
||||
view.instanceCount++;
|
||||
m_InstanceModels.push_back(t.world);
|
||||
++batch.count;
|
||||
}
|
||||
});
|
||||
if (batch.count > 0) {
|
||||
m_InstanceBatches.push_back(batch);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tryAppend(m_MainView, mainFrustum, m_MainBatchLookup);
|
||||
tryAppend(m_ShadowView, shadowFrustum, m_ShadowBatchLookup);
|
||||
}
|
||||
});
|
||||
|
||||
if (instanceTruncated) {
|
||||
// 只告警一次(每帧告警会刷屏)
|
||||
static bool warned = false;
|
||||
|
||||
Reference in New Issue
Block a user