fix: 视锥剔除平面归一化(透视因数误剔)+ 材质去重 + 描述符池可增长
- Frustum::FromMatrix 平面归一化:Gribb-Hartmann 提取的 left/right/ bottom/top 法线模长含 1/tan(fov/2) 因子(>1),未归一化时包围球 距离测试被放大,视锥边缘物体被误剔(SunTemple 全场景可见)—— 修正注释:必须传 [0,1] 重映射矩阵 - 材质去重:Model 新增 MaterialCache(按贴图路径共享 Material), Mesh 改持 Material* 非拥有指针。1056 mesh 渲染只需 48 个描述符集 (对齐 UE/Unity 材质实例化思路,大场景第一道闸门) - DescriptorManager 池改可增长分块(chunk=4096,OutOfPool/Fragmented 自动续块):材质集数量不可预估(1G 资产可能上万),固定上限迟早爆
This commit is contained in:
@@ -6,7 +6,10 @@
|
|||||||
// 平面从 view-proj 矩阵的行组合中直接抽出(无需分解参数),
|
// 平面从 view-proj 矩阵的行组合中直接抽出(无需分解参数),
|
||||||
// 用于按视图剔除(相机视图 / 光源视图各自独立一份)。
|
// 用于按视图剔除(相机视图 / 光源视图各自独立一份)。
|
||||||
// 注意:near/far 平面按 Vulkan [0,1] 深度约定提取
|
// 注意:near/far 平面按 Vulkan [0,1] 深度约定提取
|
||||||
//(z_clip ∈ [0,1]:near = row2,far = row3 - row2)。
|
//(z_clip ∈ [0,1]:near = row2,far = row3 - row2)——
|
||||||
|
// 因此必须传已做 [0,1] 重映射的矩阵(相机 proj / lightSpace 均是)。
|
||||||
|
// 平面在提取后归一化:包围球测试的 radius 是欧氏长度,
|
||||||
|
// 未归一化的平面距离会被透视因数放大,导致视锥边缘误剔。
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_FRUSTUM_H
|
#ifndef HELLOVULKAN_FRUSTUM_H
|
||||||
@@ -20,11 +23,11 @@ namespace Engine {
|
|||||||
|
|
||||||
struct Frustum {
|
struct Frustum {
|
||||||
// 依次:left, right, bottom, top, near, far
|
// 依次:left, right, bottom, top, near, far
|
||||||
// xyz = 平面法线(未归一化,仅做符号/点积测试),w = 偏移
|
// xyz = 单位法线,w = 原点到平面的有符号距离
|
||||||
std::array<glm::vec4, 6> planes{};
|
std::array<glm::vec4, 6> planes{};
|
||||||
|
|
||||||
// 从 view-proj 矩阵提取 6 平面(列主序 glm 矩阵的"行"组合)。
|
// 从 view-proj 矩阵提取 6 平面(列主序 glm 矩阵的"行"组合)。
|
||||||
// 注意必须传未做 [0,1] 深度重映射的矩阵,提取公式才成立。
|
// 必须传 [0,1] 深度重映射后的矩阵。
|
||||||
static Frustum FromMatrix(const glm::mat4& m)
|
static Frustum FromMatrix(const glm::mat4& m)
|
||||||
{
|
{
|
||||||
Frustum f;
|
Frustum f;
|
||||||
@@ -43,6 +46,14 @@ namespace Engine {
|
|||||||
f.planes[3] = r3 - r1; // top: y' <= w
|
f.planes[3] = r3 - r1; // top: y' <= w
|
||||||
f.planes[4] = r2; // near: z' >= 0(Vulkan [0,1])
|
f.planes[4] = r2; // near: z' >= 0(Vulkan [0,1])
|
||||||
f.planes[5] = r3 - r2; // far: z' <= w
|
f.planes[5] = r3 - r2; // far: z' <= w
|
||||||
|
|
||||||
|
// 归一化:法线长度把透视/缩放因数从距离里除掉
|
||||||
|
for (auto& p : f.planes) {
|
||||||
|
const f32 len = glm::length(glm::vec3(p));
|
||||||
|
if (len > 1e-8f) {
|
||||||
|
p /= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ namespace Engine
|
|||||||
class Mesh
|
class Mesh
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// material 非拥有指针:Model 的材质缓存拥有(同贴图共享一份)
|
||||||
Mesh(Device& device, MemoryAllocator& allocator,
|
Mesh(Device& device, MemoryAllocator& allocator,
|
||||||
const std::vector<Vertex>& vertices,
|
const std::vector<Vertex>& vertices,
|
||||||
const std::vector<u32>& indices,
|
const std::vector<u32>& indices,
|
||||||
Scope<Material> material);
|
Material* material);
|
||||||
~Mesh();
|
~Mesh();
|
||||||
|
|
||||||
Mesh(const Mesh&) = delete;
|
Mesh(const Mesh&) = delete;
|
||||||
@@ -34,7 +35,7 @@ namespace Engine
|
|||||||
// 同 mesh 的 N 个实体一次 draw 完成,模型矩阵经实例索引取
|
// 同 mesh 的 N 个实体一次 draw 完成,模型矩阵经实例索引取
|
||||||
void Draw(vk::CommandBuffer cmd, u32 instanceCount = 1, u32 firstInstance = 0) const;
|
void Draw(vk::CommandBuffer cmd, u32 instanceCount = 1, u32 firstInstance = 0) const;
|
||||||
|
|
||||||
Material* GetMaterial() const { return m_Material.get(); }
|
Material* GetMaterial() const { return m_Material; }
|
||||||
u32 GetIndexCount() const { return m_IndexCount; }
|
u32 GetIndexCount() const { return m_IndexCount; }
|
||||||
|
|
||||||
// 局部空间包围球(构造时从顶点算出),视锥剔除用
|
// 局部空间包围球(构造时从顶点算出),视锥剔除用
|
||||||
@@ -47,7 +48,7 @@ namespace Engine
|
|||||||
private:
|
private:
|
||||||
Scope<Buffer> m_VertexBuffer;
|
Scope<Buffer> m_VertexBuffer;
|
||||||
Scope<Buffer> m_IndexBuffer;
|
Scope<Buffer> m_IndexBuffer;
|
||||||
Scope<Material> m_Material;
|
Material* m_Material;
|
||||||
u32 m_IndexCount = 0;
|
u32 m_IndexCount = 0;
|
||||||
Bounds m_Bounds;
|
Bounds m_Bounds;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace Engine
|
|||||||
class MemoryAllocator;
|
class MemoryAllocator;
|
||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
class Material;
|
||||||
|
|
||||||
class Model
|
class Model
|
||||||
{
|
{
|
||||||
@@ -61,17 +62,23 @@ namespace Engine
|
|||||||
|
|
||||||
Texture* GetOrLoadTexture(const std::string& path);
|
Texture* GetOrLoadTexture(const std::string& path);
|
||||||
|
|
||||||
|
// 材质缓存入口:同贴图路径共享同一 Material(一个描述符集),
|
||||||
|
// 空路径/缺文件折到兜底材质。路径已做反斜杠归一化。
|
||||||
|
Material* GetOrCreateMaterial(const std::string& path);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
DescriptorManager& m_DescriptorManager;
|
DescriptorManager& m_DescriptorManager;
|
||||||
vk::DescriptorSetLayout m_TextureLayout;
|
vk::DescriptorSetLayout m_TextureLayout;
|
||||||
|
|
||||||
std::vector<Scope<Mesh>> m_Meshes;
|
// 缓存声明在 m_Meshes 之前:mesh 持 Material* 裸指针,
|
||||||
std::string m_ModelPath;
|
// 析构序 = meshes 先于材质/纹理缓存
|
||||||
|
std::unordered_map<std::string, Scope<Material>> m_MaterialCache;
|
||||||
// 纹理缓存:同一路径只加载一次
|
|
||||||
std::unordered_map<std::string, Scope<Texture>> m_TextureCache;
|
std::unordered_map<std::string, Scope<Texture>> m_TextureCache;
|
||||||
Scope<Texture> m_DefaultTexture;
|
Scope<Texture> m_DefaultTexture;
|
||||||
|
|
||||||
|
std::vector<Scope<Mesh>> m_Meshes;
|
||||||
|
std::string m_ModelPath;
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -54,12 +54,17 @@ namespace Engine {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
vk::DescriptorPool m_Pool;
|
|
||||||
|
|
||||||
// 描述符池容量:需 ≥ 场景全部材质集合数 + 每帧集合。
|
// 可增长描述符池(分块):场景材质集数量不可预估(1G 资产场景
|
||||||
// SunTemple(984 mesh 材质)把 1000 打爆过;材质去重(同贴图共享
|
// 可能上万),固定上限迟早打爆。耗尽时追加一块继续分配——
|
||||||
// Material)属于材质系统任务,在那之前留足余量
|
// 对齐 UE/Unity 的"growable pool/bindless 堆"思路的 Vulkan 版。
|
||||||
static constexpr u32 kMaxSets = 4096;
|
// 材质去重(同贴图共享 Material)仍是大场景的第一道闸门。
|
||||||
|
std::vector<vk::DescriptorPool> m_Pools;
|
||||||
|
|
||||||
|
// 每块的容量(set 数;各类型描述符数同值)
|
||||||
|
static constexpr u32 kChunkSets = 4096;
|
||||||
|
|
||||||
|
vk::DescriptorPool CreateChunk();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ namespace Engine
|
|||||||
Mesh::Mesh(Device& device, MemoryAllocator& allocator,
|
Mesh::Mesh(Device& device, MemoryAllocator& allocator,
|
||||||
const std::vector<Vertex>& vertices,
|
const std::vector<Vertex>& vertices,
|
||||||
const std::vector<u32>& indices,
|
const std::vector<u32>& indices,
|
||||||
Scope<Material> material)
|
Material* material)
|
||||||
: m_Material(std::move(material))
|
: m_Material(material)
|
||||||
{
|
{
|
||||||
if (vertices.empty() || indices.empty())
|
if (vertices.empty() || indices.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,14 +59,12 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
};
|
};
|
||||||
const std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
const std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
// 贴图/材质生命周期随模型(缓存持有)
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, tex.get());
|
m_DefaultTexture = std::move(tex);
|
||||||
|
Material* mat = GetOrCreateMaterial(""); // 兜底材质
|
||||||
|
|
||||||
m_Meshes.push_back(CreateScope<Mesh>(
|
m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, std::move(mat)));
|
m_Device, m_Allocator, vertices, indices, mat));
|
||||||
|
|
||||||
// 贴图生命周期随模型
|
|
||||||
m_DefaultTexture = std::move(tex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
||||||
@@ -134,6 +132,34 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath)
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Material* Model::GetOrCreateMaterial(const std::string& rawPath)
|
||||||
|
{
|
||||||
|
// 路径归一化 + 空路径/缺文件统一折到兜底材质(缓存键 "")
|
||||||
|
std::string path = rawPath;
|
||||||
|
std::replace(path.begin(), path.end(), '\\', '/');
|
||||||
|
|
||||||
|
if (path.empty() || !std::filesystem::exists(path)) {
|
||||||
|
if (!rawPath.empty()) {
|
||||||
|
ENGINE_WARN("Texture not found, using default: {}", path);
|
||||||
|
}
|
||||||
|
path.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto it = m_MaterialCache.find(path); it != m_MaterialCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture* tex = path.empty()
|
||||||
|
? m_DefaultTexture.get()
|
||||||
|
: GetOrLoadTexture(path);
|
||||||
|
|
||||||
|
auto mat = CreateScope<Material>(
|
||||||
|
m_Device, m_DescriptorManager, m_TextureLayout, tex);
|
||||||
|
Material* ptr = mat.get();
|
||||||
|
m_MaterialCache.emplace(path, std::move(mat));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
||||||
{
|
{
|
||||||
std::vector<Vertex> vertices;
|
std::vector<Vertex> vertices;
|
||||||
@@ -189,16 +215,11 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从缓存拿,没有才加载
|
// 材质缓存:同贴图 → 同一 Material(一个描述符集),mesh 数 ≠ 集数
|
||||||
Texture* tex = texturePath.empty()
|
Material* mat = GetOrCreateMaterial(texturePath);
|
||||||
? m_DefaultTexture.get()
|
|
||||||
: GetOrLoadTexture(texturePath);
|
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, tex);
|
|
||||||
|
|
||||||
return CreateScope<Mesh>(
|
return CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, std::move(mat));
|
m_Device, m_Allocator, vertices, indices, mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
@@ -11,24 +11,32 @@ namespace Engine
|
|||||||
DescriptorManager::DescriptorManager(Device& device)
|
DescriptorManager::DescriptorManager(Device& device)
|
||||||
: m_Device(device)
|
: m_Device(device)
|
||||||
{
|
{
|
||||||
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
m_Pools.push_back(CreateChunk());
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kMaxSets),
|
ENGINE_INFO("DescriptorManager created (growable, chunk = {} sets)",
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kMaxSets),
|
kChunkSets);
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kMaxSets),
|
|
||||||
};
|
|
||||||
|
|
||||||
vk::DescriptorPoolCreateInfo poolInfo;
|
|
||||||
poolInfo.setMaxSets(kMaxSets)
|
|
||||||
.setPoolSizes(poolSizes)
|
|
||||||
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
|
||||||
|
|
||||||
m_Pool = m_Device.GetDevice().createDescriptorPool(poolInfo);
|
|
||||||
ENGINE_INFO("DescriptorManager created");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DescriptorManager::~DescriptorManager()
|
DescriptorManager::~DescriptorManager()
|
||||||
{
|
{
|
||||||
m_Device.GetDevice().destroyDescriptorPool(m_Pool);
|
for (auto pool : m_Pools) {
|
||||||
|
m_Device.GetDevice().destroyDescriptorPool(pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorPool DescriptorManager::CreateChunk()
|
||||||
|
{
|
||||||
|
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kChunkSets),
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kChunkSets),
|
||||||
|
};
|
||||||
|
|
||||||
|
vk::DescriptorPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.setMaxSets(kChunkSets)
|
||||||
|
.setPoolSizes(poolSizes)
|
||||||
|
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
||||||
|
|
||||||
|
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
||||||
@@ -52,12 +60,24 @@ namespace Engine
|
|||||||
|
|
||||||
vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout)
|
vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout)
|
||||||
{
|
{
|
||||||
|
// 从最新块分配;耗尽(OutOfPool/Fragmented)则追加一块重试
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
vk::DescriptorSetAllocateInfo allocInfo;
|
vk::DescriptorSetAllocateInfo allocInfo;
|
||||||
allocInfo.setDescriptorPool(m_Pool)
|
allocInfo.setDescriptorPool(m_Pools.back())
|
||||||
.setSetLayouts(layout);
|
.setSetLayouts(layout);
|
||||||
|
|
||||||
auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo);
|
auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo);
|
||||||
return sets[0];
|
return sets[0];
|
||||||
|
} catch (const vk::OutOfPoolMemoryError&) {
|
||||||
|
m_Pools.push_back(CreateChunk());
|
||||||
|
ENGINE_INFO("DescriptorManager: pool chunk grown -> {} chunks",
|
||||||
|
m_Pools.size());
|
||||||
|
} catch (const vk::FragmentedPoolError&) {
|
||||||
|
m_Pools.push_back(CreateChunk());
|
||||||
|
ENGINE_INFO("DescriptorManager: pool fragmented, new chunk -> {}",
|
||||||
|
m_Pools.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
|
|||||||
Reference in New Issue
Block a user