fix(renderer): mesh collider source positions in render space for skinned meshes
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.
This commit is contained in:
@@ -28,10 +28,13 @@ namespace Engine
|
|||||||
public:
|
public:
|
||||||
// material 非拥有指针:Model 的材质缓存拥有(同贴图共享一份)
|
// material 非拥有指针:Model 的材质缓存拥有(同贴图共享一份)
|
||||||
// lods[0] 必须为原始索引(与 vertices 对应),各级简化链由解析期生成
|
// lods[0] 必须为原始索引(与 vertices 对应),各级简化链由解析期生成
|
||||||
|
// cpuPositions 非空(蒙皮 mesh)= 物理数据源与包围球改用它
|
||||||
|
//(绑定姿态渲染空间,与视觉同源;vertices.position 是蒙皮原始空间)
|
||||||
Mesh(Device& device, MemoryAllocator& allocator,
|
Mesh(Device& device, MemoryAllocator& allocator,
|
||||||
const std::vector<Vertex>& vertices,
|
const std::vector<Vertex>& vertices,
|
||||||
const std::vector<std::vector<u32>>& lods,
|
const std::vector<std::vector<u32>>& lods,
|
||||||
Material* material);
|
Material* material,
|
||||||
|
const std::vector<glm::vec3>* cpuPositions = nullptr);
|
||||||
~Mesh();
|
~Mesh();
|
||||||
|
|
||||||
Mesh(const Mesh&) = delete;
|
Mesh(const Mesh&) = delete;
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ namespace Engine
|
|||||||
std::array<std::string, Material::kSlotCount> texPaths;
|
std::array<std::string, Material::kSlotCount> texPaths;
|
||||||
MaterialParams params;
|
MaterialParams params;
|
||||||
bool transparent = false; // glTF alphaMode=BLEND(透明绘制队列)
|
bool transparent = false; // glTF alphaMode=BLEND(透明绘制队列)
|
||||||
|
bool skinned = false; // 有骨骼(渲染走 palette 蒙皮)
|
||||||
|
|
||||||
|
// 蒙皮 mesh 专属:绑定姿态渲染空间的位置副本(palette(bind) 按
|
||||||
|
// 顶点权重混合,与 shader 同式)。非空 = Mesh 的物理数据源与
|
||||||
|
// 包围球改用它——FBX 单位换算折在骨骼链上(bind palette ≠ I),
|
||||||
|
// 直接用原始顶点会让物理凸包大一个数量级(M1911 实测 10×)
|
||||||
|
std::vector<glm::vec3> cpuPositions;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 骨骼动画数据(assimp aiNode/aiAnimation/aiBone 的引擎侧镜像)----
|
// ---- 骨骼动画数据(assimp aiNode/aiAnimation/aiBone 的引擎侧镜像)----
|
||||||
|
|||||||
@@ -25,7 +25,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<std::vector<u32>>& lods,
|
const std::vector<std::vector<u32>>& lods,
|
||||||
Material* material)
|
Material* material,
|
||||||
|
const std::vector<glm::vec3>* cpuPositions)
|
||||||
: m_Material(material)
|
: m_Material(material)
|
||||||
, m_Slot(s_nextMeshSlot.fetch_add(1, std::memory_order_relaxed))
|
, m_Slot(s_nextMeshSlot.fetch_add(1, std::memory_order_relaxed))
|
||||||
{
|
{
|
||||||
@@ -35,11 +36,19 @@ namespace Engine
|
|||||||
return;
|
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 上传后
|
// CPU 位置副本:物理 MeshShape/ConvexHull 数据源(GPU 上传后
|
||||||
// 完整 Vertex 不再保留,见 Mesh.h)
|
// 完整 Vertex 不再保留,见 Mesh.h)
|
||||||
m_CpuPositions.reserve(vertices.size());
|
m_CpuPositions.reserve(vertices.size());
|
||||||
for (const auto& v : vertices) {
|
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||||
m_CpuPositions.push_back(v.position);
|
m_CpuPositions.push_back(posAt(i));
|
||||||
}
|
}
|
||||||
m_CpuIndices = lods[0];
|
m_CpuIndices = lods[0];
|
||||||
|
|
||||||
@@ -78,18 +87,19 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 局部空间包围球:顶点包围盒 → 中心 + 最大半径(视锥剔除用)
|
// 局部空间包围球:顶点包围盒 → 中心 + 最大半径(视锥剔除用)
|
||||||
|
// 蒙皮 mesh 用绑定姿态副本(原始空间包围球大一个数量级 = 剔除/拾取失真)
|
||||||
{
|
{
|
||||||
glm::vec3 mn(std::numeric_limits<f32>::max());
|
glm::vec3 mn(std::numeric_limits<f32>::max());
|
||||||
glm::vec3 mx(std::numeric_limits<f32>::lowest());
|
glm::vec3 mx(std::numeric_limits<f32>::lowest());
|
||||||
for (const auto& v : vertices) {
|
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||||
mn = glm::min(mn, v.position);
|
mn = glm::min(mn, posAt(i));
|
||||||
mx = glm::max(mx, v.position);
|
mx = glm::max(mx, posAt(i));
|
||||||
}
|
}
|
||||||
m_Bounds.center = (mn + mx) * 0.5f;
|
m_Bounds.center = (mn + mx) * 0.5f;
|
||||||
m_Bounds.radius = 0.0f;
|
m_Bounds.radius = 0.0f;
|
||||||
for (const auto& v : vertices) {
|
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||||
m_Bounds.radius = glm::max(m_Bounds.radius,
|
m_Bounds.radius = glm::max(m_Bounds.radius,
|
||||||
glm::length(v.position - m_Bounds.center));
|
glm::length(posAt(i) - m_Bounds.center));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,42 @@ ParsedModel Model::ParseFromDisk(const std::string& path)
|
|||||||
bone.name);
|
bone.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 蒙皮 mesh 的 CPU 位置副本换算到渲染空间:渲染 = palette(t)×raw
|
||||||
|
//(palette = global(bone)×invBind,与 shader 同式),物理/包围球取
|
||||||
|
// 绑定姿态。FBX 的单位换算折在骨骼链上(bind palette ≠ I),直接
|
||||||
|
// 用 raw 会让物理凸包大一个数量级(M1911 实测 10×)
|
||||||
|
if (!out.bones.empty()) {
|
||||||
|
std::vector<glm::mat4> globals(out.nodes.size());
|
||||||
|
for (size_t i = 0; i < out.nodes.size(); ++i) {
|
||||||
|
globals[i] = out.nodes[i].parent < 0
|
||||||
|
? out.nodes[i].localTransform
|
||||||
|
: globals[out.nodes[i].parent] * out.nodes[i].localTransform;
|
||||||
|
}
|
||||||
|
std::vector<glm::mat4> bindPalette(out.bones.size());
|
||||||
|
for (size_t b = 0; b < out.bones.size(); ++b) {
|
||||||
|
bindPalette[b] = globals[out.bones[b].nodeIdx]
|
||||||
|
* out.bones[b].invBind;
|
||||||
|
}
|
||||||
|
for (auto& pm : out.meshes) {
|
||||||
|
if (!pm.skinned) continue;
|
||||||
|
pm.cpuPositions.resize(pm.vertices.size());
|
||||||
|
for (size_t v = 0; v < pm.vertices.size(); ++v) {
|
||||||
|
const Vertex& vert = pm.vertices[v];
|
||||||
|
glm::vec3 p(0.0f);
|
||||||
|
f32 wsum = 0.0f;
|
||||||
|
for (int k = 0; k < 4; ++k) {
|
||||||
|
const f32 w = vert.weights[k];
|
||||||
|
if (w <= 0.0f) continue;
|
||||||
|
p += w * glm::vec3(bindPalette[vert.boneIds[k]]
|
||||||
|
* glm::vec4(vert.position, 1.0f));
|
||||||
|
wsum += w;
|
||||||
|
}
|
||||||
|
// 零权重顶点(shader 侧塌到原点)物理侧保留原始位置兜底
|
||||||
|
pm.cpuPositions[v] = wsum > 1e-6f ? p : vert.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ENGINE_INFO("Model parsed: {} ({} meshes, {} bones, {} anims)",
|
ENGINE_INFO("Model parsed: {} ({} meshes, {} bones, {} anims)",
|
||||||
@@ -383,7 +419,8 @@ Scope<Model> Model::FromParsed(Device& device, MemoryAllocator& allocator,
|
|||||||
for (auto& pm : parsed.meshes) {
|
for (auto& pm : parsed.meshes) {
|
||||||
Material* mat = model->GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
Material* mat = model->GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
||||||
model->m_Meshes.push_back(CreateScope<Mesh>(
|
model->m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
device, allocator, pm.vertices, pm.lods, mat));
|
device, allocator, pm.vertices, pm.lods, mat,
|
||||||
|
pm.cpuPositions.empty() ? nullptr : &pm.cpuPositions));
|
||||||
}
|
}
|
||||||
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
||||||
parsed.path, model->m_Meshes.size(), model->m_TextureCache.size());
|
parsed.path, model->m_Meshes.size(), model->m_TextureCache.size());
|
||||||
@@ -406,7 +443,8 @@ void Model::LoadModel(const std::string& path)
|
|||||||
for (auto& pm : parsed.meshes) {
|
for (auto& pm : parsed.meshes) {
|
||||||
Material* mat = GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
Material* mat = GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
||||||
m_Meshes.push_back(CreateScope<Mesh>(
|
m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, pm.vertices, pm.lods, mat));
|
m_Device, m_Allocator, pm.vertices, pm.lods, mat,
|
||||||
|
pm.cpuPositions.empty() ? nullptr : &pm.cpuPositions));
|
||||||
}
|
}
|
||||||
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
||||||
path, m_Meshes.size(), m_TextureCache.size());
|
path, m_Meshes.size(), m_TextureCache.size());
|
||||||
@@ -577,6 +615,7 @@ ParsedMesh Model::ProcessMesh(const aiMesh* mesh, const aiScene* scene,
|
|||||||
|
|
||||||
// 骨骼权重灌顶点(无骨骼 mesh 顶点保持默认 idx0/w1 → identity 直通)
|
// 骨骼权重灌顶点(无骨骼 mesh 顶点保持默认 idx0/w1 → identity 直通)
|
||||||
CollectMeshBones(mesh, vertices, bones, boneSlots);
|
CollectMeshBones(mesh, vertices, bones, boneSlots);
|
||||||
|
out.skinned = hasBones;
|
||||||
|
|
||||||
for (u32 i = 0; i < mesh->mNumFaces; ++i) {
|
for (u32 i = 0; i < mesh->mNumFaces; ++i) {
|
||||||
aiFace face = mesh->mFaces[i];
|
aiFace face = mesh->mFaces[i];
|
||||||
|
|||||||
Reference in New Issue
Block a user