Skinned meshes keep vertices in raw mesh space (bakeTransform=false); rendering transforms them via bind palette (global(bone) x invBind, folds FBX unit scale). Physics MeshShape/ConvexHull and the mesh bounding sphere consumed the raw positions directly -> M1911 hull measured ~10x the visual size. ParseFromDisk now bakes bind-palette-skinned positions per vertex (same formula as the skinning shader) into ParsedMesh::cpuPositions; Mesh uses them for m_CpuPositions and bounds. GPU vertex data and rendering are untouched.
131 lines
5.0 KiB
C++
131 lines
5.0 KiB
C++
//
|
||
// Created by atdunbg on 2026/9/21.
|
||
//
|
||
|
||
#include <Engine/Renderer/Mesh.h>
|
||
#include <Engine/Renderer/Material.h>
|
||
#include <Engine/Vulkan/Device.h>
|
||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||
#include <Engine/Vulkan/Buffer.h>
|
||
#include <Engine/Core/Log.h>
|
||
|
||
#include <limits>
|
||
#include <atomic>
|
||
|
||
#include "Engine/Core/Stats.h"
|
||
|
||
namespace Engine
|
||
{
|
||
namespace {
|
||
// 全局连续 mesh 槽位:SceneRenderer 的 batch 查找用数组寻址
|
||
//(每实例每视图一次 hash 的代价在大场景下是 5ms 级)
|
||
std::atomic<u32> s_nextMeshSlot{0};
|
||
}
|
||
|
||
Mesh::Mesh(Device& device, MemoryAllocator& allocator,
|
||
const std::vector<Vertex>& vertices,
|
||
const std::vector<std::vector<u32>>& lods,
|
||
Material* material,
|
||
const std::vector<glm::vec3>* cpuPositions)
|
||
: m_Material(material)
|
||
, m_Slot(s_nextMeshSlot.fetch_add(1, std::memory_order_relaxed))
|
||
{
|
||
if (vertices.empty() || lods.empty() || lods[0].empty())
|
||
{
|
||
ENGINE_ERROR("Mesh: empty vertices or LOD0 indices");
|
||
return;
|
||
}
|
||
|
||
// 位置取数:蒙皮 mesh 用绑定姿态渲染空间副本(与视觉同源),
|
||
// 否则用原始顶点位置
|
||
const bool useBind = cpuPositions != nullptr &&
|
||
cpuPositions->size() == vertices.size();
|
||
auto posAt = [&](size_t i) -> const glm::vec3& {
|
||
return useBind ? (*cpuPositions)[i] : vertices[i].position;
|
||
};
|
||
|
||
// CPU 位置副本:物理 MeshShape/ConvexHull 数据源(GPU 上传后
|
||
// 完整 Vertex 不再保留,见 Mesh.h)
|
||
m_CpuPositions.reserve(vertices.size());
|
||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||
m_CpuPositions.push_back(posAt(i));
|
||
}
|
||
m_CpuIndices = lods[0];
|
||
|
||
u64 vbSize = sizeof(Vertex) * vertices.size();
|
||
m_VertexBuffer = CreateScope<Buffer>(
|
||
device, allocator, vbSize,
|
||
vk::BufferUsageFlagBits::eVertexBuffer,
|
||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||
m_VertexBuffer->Upload(vertices.data(), vbSize);
|
||
|
||
// 各级索引连续拼进同一 index buffer(共享顶点,只换索引区间)
|
||
std::vector<u32> allIndices;
|
||
for (const auto& idx : lods) {
|
||
allIndices.insert(allIndices.end(), idx.begin(), idx.end());
|
||
}
|
||
|
||
u64 ibSize = sizeof(u32) * allIndices.size();
|
||
m_IndexBuffer = CreateScope<Buffer>(
|
||
device, allocator, ibSize,
|
||
vk::BufferUsageFlagBits::eIndexBuffer,
|
||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||
m_IndexBuffer->Upload(allIndices.data(), ibSize);
|
||
|
||
// 记录各级区间。LOD 不足的级复用最后一级的"已有区间"
|
||
//(first 不得继续累加——buffer 里没有重复数据,累加必越界)
|
||
u32 first = 0;
|
||
for (u32 lod = 0; lod < kMeshLodCount; ++lod) {
|
||
if (lod < lods.size()) {
|
||
m_Lods[lod] = {first, static_cast<u32>(lods[lod].size())};
|
||
first += m_Lods[lod].indexCount;
|
||
} else {
|
||
m_Lods[lod] = m_Lods[lods.size() - 1];
|
||
}
|
||
}
|
||
|
||
// 局部空间包围球:顶点包围盒 → 中心 + 最大半径(视锥剔除用)
|
||
// 蒙皮 mesh 用绑定姿态副本(原始空间包围球大一个数量级 = 剔除/拾取失真)
|
||
{
|
||
glm::vec3 mn(std::numeric_limits<f32>::max());
|
||
glm::vec3 mx(std::numeric_limits<f32>::lowest());
|
||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||
mn = glm::min(mn, posAt(i));
|
||
mx = glm::max(mx, posAt(i));
|
||
}
|
||
m_Bounds.center = (mn + mx) * 0.5f;
|
||
m_Bounds.radius = 0.0f;
|
||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||
m_Bounds.radius = glm::max(m_Bounds.radius,
|
||
glm::length(posAt(i) - m_Bounds.center));
|
||
}
|
||
}
|
||
|
||
ENGINE_INFO("Mesh created: {} vertices, LOD0 {} indices", vertices.size(), lods[0].size());
|
||
|
||
m_VertexBytes = sizeof(Vertex) * vertices.size();
|
||
for (const auto& idx : lods) {
|
||
m_IndexBytes += sizeof(u32) * idx.size();
|
||
}
|
||
STATS.AddMesh(m_VertexBytes, m_IndexBytes);
|
||
}
|
||
|
||
Mesh::~Mesh()
|
||
{
|
||
STATS.RemoveMesh(m_VertexBytes, m_IndexBytes);
|
||
}
|
||
|
||
void Mesh::Draw(vk::CommandBuffer cmd, u32 instanceCount, u32 firstInstance,
|
||
u32 lod) const
|
||
{
|
||
const LodRange& range =
|
||
m_Lods[lod < kMeshLodCount ? lod : kMeshLodCount - 1];
|
||
cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0});
|
||
cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32);
|
||
cmd.drawIndexed(range.indexCount, instanceCount, range.firstIndex,
|
||
0, firstInstance);
|
||
}
|
||
} // namespace Engine
|