feat: 模型异步加载——assimp 解析移后台线程(SunTemple 3s 卡顿消除)
Model 拆两段式:ParseFromDisk(static 纯 CPU,assimp 解析 → 几何 + 材质引用,worker 可调)/ FromParsed(GPU 构建:纹理上传 / mesh 缓冲 / 材质描述符,主线程),同步路径等价两段串行。第二个异步任务类型落地, 线程收割骨架抽成 Core/AsyncTask<T>(thread + atomic + Take,覆盖式 Run;pending 接力语义留调用方)。 实测(SunTemple 1056 mesh):解析 2.5s 全程在 worker,主线程持续 渲染;GPU 构建 0.56s。assets 层经 IAssetUploader/AssetManager 重载 传预解析产物(路径缓存去重,路径已缓存则产物丢弃)。 已知边界:纹理解码(backpack 4k PNG ~1s)仍在 GPU 段主线程—— Texture 需支持预解码像素注入,后续演进。
This commit is contained in:
@@ -23,14 +23,22 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout,
|
||||
const std::string& path)
|
||||
: Model(device, allocator, descriptorManager, textureLayout)
|
||||
{
|
||||
// 同步便捷路径 = 两段式串行(等价异步收割后的主线程消费)
|
||||
LoadModel(path);
|
||||
}
|
||||
|
||||
// 空模型骨架:只建默认纹理;mesh/材质由 FromParsed 填充
|
||||
Model::Model(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout)
|
||||
: m_Device(device)
|
||||
, m_Allocator(allocator)
|
||||
, m_DescriptorManager(descriptorManager)
|
||||
, m_TextureLayout(textureLayout)
|
||||
{
|
||||
InitDefaultTextures();
|
||||
|
||||
LoadModel(path);
|
||||
}
|
||||
|
||||
Model::~Model() = default;
|
||||
@@ -92,9 +100,10 @@ Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
||||
halfSize, std::array<u8, 3>{r, g, b}));
|
||||
}
|
||||
|
||||
void Model::LoadModel(const std::string& path)
|
||||
ParsedModel Model::ParseFromDisk(const std::string& path)
|
||||
{
|
||||
m_ModelPath = path;
|
||||
ParsedModel out;
|
||||
out.path = path;
|
||||
|
||||
// UV 翻转按格式区分:
|
||||
// OBJ/FBX —— v 原点在底部,需翻到左上惯例(配合"v=0=图像顶"采样)
|
||||
@@ -106,36 +115,84 @@ void Model::LoadModel(const std::string& path)
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return s;
|
||||
}();
|
||||
m_IsGltf = lower.size() >= 5 &&
|
||||
out.isGltf = lower.size() >= 5 &&
|
||||
(lower.rfind(".gltf") == lower.size() - 5 ||
|
||||
lower.rfind(".glb") == lower.size() - 4);
|
||||
|
||||
Assimp::Importer importer;
|
||||
const aiScene* scene = importer.ReadFile(path,
|
||||
aiProcess_Triangulate |
|
||||
(m_IsGltf ? 0 : aiProcess_FlipUVs) |
|
||||
(out.isGltf ? 0 : aiProcess_FlipUVs) |
|
||||
aiProcess_GenSmoothNormals |
|
||||
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
||||
aiProcess_JoinIdenticalVertices);
|
||||
|
||||
if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
|
||||
ENGINE_ERROR("Assimp error: {}", importer.GetErrorString());
|
||||
return;
|
||||
return out; // meshes 空 = 无效
|
||||
}
|
||||
|
||||
ProcessNode(scene->mRootNode, scene);
|
||||
const std::filesystem::path modelDir =
|
||||
std::filesystem::path(path).parent_path();
|
||||
ProcessNode(scene->mRootNode, scene, out.meshes, modelDir, out.isGltf);
|
||||
ENGINE_INFO("Model parsed: {} ({} meshes)", path, out.meshes.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
Scope<Model> Model::FromParsed(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout,
|
||||
ParsedModel&& parsed)
|
||||
{
|
||||
if (!parsed.Valid()) {
|
||||
ENGINE_ERROR("Model::FromParsed: invalid parsed model ({})", parsed.path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto model = Scope<Model>(new Model(device, allocator,
|
||||
descriptorManager, textureLayout));
|
||||
model->m_ModelPath = parsed.path;
|
||||
model->m_IsGltf = parsed.isGltf;
|
||||
|
||||
// 材质缓存去重 + 纹理上传 + mesh 缓冲(全部 GPU,主线程)
|
||||
for (auto& pm : parsed.meshes) {
|
||||
Material* mat = model->GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
||||
model->m_Meshes.push_back(CreateScope<Mesh>(
|
||||
device, allocator, std::move(pm.vertices), std::move(pm.indices), mat));
|
||||
}
|
||||
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
||||
parsed.path, model->m_Meshes.size(), model->m_TextureCache.size());
|
||||
return model;
|
||||
}
|
||||
|
||||
void Model::LoadModel(const std::string& path)
|
||||
{
|
||||
// 同步路径:解析 + GPU 构建串行(与 FromParsed 相同的消费逻辑)
|
||||
ParsedModel parsed = ParseFromDisk(path);
|
||||
if (!parsed.Valid()) {
|
||||
return;
|
||||
}
|
||||
m_ModelPath = parsed.path;
|
||||
m_IsGltf = parsed.isGltf;
|
||||
for (auto& pm : parsed.meshes) {
|
||||
Material* mat = GetOrCreateMaterial(pm.texPaths.data(), pm.params);
|
||||
m_Meshes.push_back(CreateScope<Mesh>(
|
||||
m_Device, m_Allocator, std::move(pm.vertices), std::move(pm.indices), mat));
|
||||
}
|
||||
ENGINE_INFO("Model loaded: {} ({} meshes, {} textures)",
|
||||
path, m_Meshes.size(), m_TextureCache.size());
|
||||
}
|
||||
|
||||
void Model::ProcessNode(aiNode* node, const aiScene* scene)
|
||||
void Model::ProcessNode(const aiNode* node, const aiScene* scene,
|
||||
std::vector<ParsedMesh>& out,
|
||||
const std::filesystem::path& modelDir, bool isGltf)
|
||||
{
|
||||
for (u32 i = 0; i < node->mNumMeshes; ++i) {
|
||||
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
||||
m_Meshes.push_back(ProcessMesh(mesh, scene));
|
||||
out.push_back(ProcessMesh(mesh, scene, modelDir, isGltf));
|
||||
}
|
||||
for (u32 i = 0; i < node->mNumChildren; ++i) {
|
||||
ProcessNode(node->mChildren[i], scene);
|
||||
ProcessNode(node->mChildren[i], scene, out, modelDir, isGltf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,10 +250,13 @@ Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCoun
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
||||
ParsedMesh Model::ProcessMesh(const aiMesh* mesh, const aiScene* scene,
|
||||
const std::filesystem::path& modelDir,
|
||||
bool isGltf)
|
||||
{
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<u32> indices;
|
||||
ParsedMesh out;
|
||||
std::vector<Vertex>& vertices = out.vertices;
|
||||
std::vector<u32>& indices = out.indices;
|
||||
|
||||
vertices.reserve(mesh->mNumVertices);
|
||||
|
||||
@@ -248,9 +308,7 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
||||
MaterialParams params;
|
||||
|
||||
if (mesh->mMaterialIndex >= 0) {
|
||||
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||||
const std::filesystem::path modelDir =
|
||||
std::filesystem::path(m_ModelPath).parent_path();
|
||||
const aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||||
|
||||
// glTF2 贴图在 assimp 5.x 的落点:MR 合图 → METALNESS(15)/
|
||||
// DIFFUSE_ROUGHNESS(16)(同图双槽);baseColor → BASE_COLOR(12)。
|
||||
@@ -304,7 +362,7 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
||||
// roughness 有独立贴图 → 因子 1(G 通道直达);
|
||||
// 无贴图 → Blinn-Phong Ns 换算 R=sqrt(2/(Ns+2)),
|
||||
// 补偿默认 MR 贴图 G=0.5 → 因子 = 2R
|
||||
if (m_IsGltf) {
|
||||
if (isGltf) {
|
||||
ai_real metallic = 1.0f, roughness = 1.0f;
|
||||
material->Get(AI_MATKEY_METALLIC_FACTOR, metallic);
|
||||
material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness);
|
||||
@@ -323,11 +381,12 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
||||
}
|
||||
}
|
||||
|
||||
// 材质缓存:同贴图组合+因子 → 同一 Material(一个描述符集)
|
||||
Material* mat = GetOrCreateMaterial(texPaths, params);
|
||||
|
||||
return CreateScope<Mesh>(
|
||||
m_Device, m_Allocator, vertices, indices, mat);
|
||||
// 材质创建延迟到 GPU 构建段(FromParsed):解析期只留引用
|
||||
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
||||
out.texPaths[slot] = std::move(texPaths[slot]);
|
||||
}
|
||||
out.params = params;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
Reference in New Issue
Block a user