Import material textures with mesh assets

This commit is contained in:
2026-03-26 16:22:24 +08:00
parent c479595bf5
commit e174862b8a
18 changed files with 622 additions and 21 deletions

View File

@@ -1,14 +1,21 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Math/Matrix4.h>
#include <assimp/Importer.hpp>
#include <assimp/material.h>
#include <assimp/pbrmaterial.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <unordered_map>
#include <vector>
namespace XCEngine {
@@ -23,6 +30,15 @@ struct ImportedMeshData {
Math::Bounds bounds;
};
struct TextureImportContext {
const aiScene& scene;
const Containers::String& sourcePath;
std::filesystem::path sourceDirectory;
TextureLoader textureLoader;
std::unordered_map<std::string, Texture*> textureCache;
std::vector<Texture*> ownedTextures;
};
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
if (vertices.empty()) {
return Math::Bounds();
@@ -91,6 +107,290 @@ Core::uint32 BuildPostProcessFlags(const MeshImportSettings& settings) {
return flags;
}
Containers::String ToContainersString(const std::string& value) {
return Containers::String(value.c_str());
}
Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
const char* category,
Core::uint32 index,
const Containers::String& extension = Containers::String()) {
std::string path = sourcePath.CStr();
path += "#";
path += category;
path += "[";
path += std::to_string(index);
path += "]";
if (!extension.Empty()) {
path += extension.CStr();
}
return Containers::String(path.c_str());
}
Containers::String GetResourceNameFromPath(const Containers::String& path) {
const std::filesystem::path filePath(path.CStr());
const std::string fileName = filePath.filename().string();
if (!fileName.empty()) {
return Containers::String(fileName.c_str());
}
return path;
}
Core::uint32 FindEmbeddedTextureIndex(const aiScene& scene, const aiTexture& embeddedTexture) {
for (Core::uint32 index = 0; index < scene.mNumTextures; ++index) {
if (scene.mTextures[index] == &embeddedTexture) {
return index;
}
}
return 0;
}
Texture* CreateRawTexture(const Containers::String& texturePath,
TextureFormat format,
Core::uint32 width,
Core::uint32 height,
const void* data,
size_t dataSize) {
auto* texture = new Texture();
IResource::ConstructParams params;
params.name = GetResourceNameFromPath(texturePath);
params.path = texturePath;
params.guid = ResourceGUID::Generate(texturePath);
params.memorySize = dataSize;
texture->Initialize(params);
if (!texture->Create(width,
height,
1,
1,
TextureType::Texture2D,
format,
data,
dataSize)) {
delete texture;
return nullptr;
}
return texture;
}
Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
const Containers::String& texturePath,
TextureImportContext& context) {
const std::string cacheKey(texturePath.CStr());
const auto cacheIt = context.textureCache.find(cacheKey);
if (cacheIt != context.textureCache.end()) {
return cacheIt->second;
}
Texture* texture = nullptr;
if (embeddedTexture.mHeight == 0) {
LoadResult result = context.textureLoader.LoadFromMemory(texturePath,
embeddedTexture.pcData,
embeddedTexture.mWidth);
if (result) {
texture = static_cast<Texture*>(result.resource);
}
} else {
const size_t texelCount = static_cast<size_t>(embeddedTexture.mWidth) *
static_cast<size_t>(embeddedTexture.mHeight);
std::vector<Core::uint8> rgbaPixels(texelCount * 4u);
for (size_t i = 0; i < texelCount; ++i) {
const aiTexel& texel = embeddedTexture.pcData[i];
rgbaPixels[i * 4 + 0] = texel.r;
rgbaPixels[i * 4 + 1] = texel.g;
rgbaPixels[i * 4 + 2] = texel.b;
rgbaPixels[i * 4 + 3] = texel.a;
}
texture = CreateRawTexture(texturePath,
TextureFormat::RGBA8_UNORM,
embeddedTexture.mWidth,
embeddedTexture.mHeight,
rgbaPixels.data(),
rgbaPixels.size());
}
if (texture != nullptr) {
context.textureCache.emplace(cacheKey, texture);
context.ownedTextures.push_back(texture);
}
return texture;
}
Texture* LoadExternalTexture(const std::filesystem::path& textureFilePath,
TextureImportContext& context) {
const std::string normalizedPath = textureFilePath.lexically_normal().string();
const auto cacheIt = context.textureCache.find(normalizedPath);
if (cacheIt != context.textureCache.end()) {
return cacheIt->second;
}
LoadResult result = context.textureLoader.Load(Containers::String(normalizedPath.c_str()));
if (!result) {
return nullptr;
}
Texture* texture = static_cast<Texture*>(result.resource);
context.textureCache.emplace(normalizedPath, texture);
context.ownedTextures.push_back(texture);
return texture;
}
Texture* LoadTextureReference(const aiString& textureReference, TextureImportContext& context) {
if (textureReference.length == 0) {
return nullptr;
}
const aiTexture* embeddedTexture = context.scene.GetEmbeddedTexture(textureReference.C_Str());
if (embeddedTexture != nullptr) {
const Core::uint32 embeddedTextureIndex = FindEmbeddedTextureIndex(context.scene, *embeddedTexture);
std::string extension;
if (embeddedTexture->mHeight == 0 && embeddedTexture->achFormatHint[0] != '\0') {
extension = ".";
extension += embeddedTexture->achFormatHint;
} else {
extension = ".tex";
}
const Containers::String texturePath =
BuildSubResourcePath(context.sourcePath,
"embedded_texture",
embeddedTextureIndex,
Containers::String(extension.c_str()));
return LoadEmbeddedTexture(*embeddedTexture, texturePath, context);
}
std::filesystem::path resolvedPath(textureReference.C_Str());
if (!resolvedPath.is_absolute()) {
resolvedPath = context.sourceDirectory / resolvedPath;
}
return LoadExternalTexture(resolvedPath, context);
}
Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
std::initializer_list<aiTextureType> textureTypes,
TextureImportContext& context) {
for (aiTextureType textureType : textureTypes) {
if (assimpMaterial.GetTextureCount(textureType) == 0) {
continue;
}
aiString texturePath;
if (assimpMaterial.GetTexture(textureType, 0, &texturePath) != AI_SUCCESS) {
continue;
}
Texture* texture = LoadTextureReference(texturePath, context);
if (texture != nullptr) {
return texture;
}
}
return nullptr;
}
void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& material) {
float opacity = 1.0f;
if (assimpMaterial.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) {
material.SetFloat("opacity", opacity);
}
aiColor4D baseColor;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
Math::Vector4(baseColor.r, baseColor.g, baseColor.b, baseColor.a));
} else {
aiColor3D diffuseColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
}
}
aiColor3D emissiveColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColor) == AI_SUCCESS) {
material.SetFloat3("emissiveColor",
Math::Vector3(emissiveColor.r, emissiveColor.g, emissiveColor.b));
}
aiColor3D specularColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS) {
material.SetFloat3("specularColor",
Math::Vector3(specularColor.r, specularColor.g, specularColor.b));
}
float metallic = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallic) == AI_SUCCESS) {
material.SetFloat("metallic", metallic);
}
float roughness = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) {
material.SetFloat("roughness", roughness);
}
float shininess = 0.0f;
if (assimpMaterial.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
material.SetFloat("shininess", shininess);
}
int twoSided = 0;
if (assimpMaterial.Get(AI_MATKEY_TWOSIDED, twoSided) == AI_SUCCESS) {
material.SetBool("twoSided", twoSided != 0);
}
}
void ImportMaterialTextures(const aiMaterial& assimpMaterial,
Material& material,
TextureImportContext& context) {
auto assignTexture = [&](const char* propertyName,
std::initializer_list<aiTextureType> textureTypes) {
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, context);
if (texture != nullptr) {
material.SetTexture(Containers::String(propertyName), ResourceHandle<Texture>(texture));
}
};
assignTexture("baseColorTexture", { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
assignTexture("normalTexture", { aiTextureType_NORMAL_CAMERA, aiTextureType_NORMALS, aiTextureType_HEIGHT });
assignTexture("specularTexture", { aiTextureType_SPECULAR });
assignTexture("emissiveTexture", { aiTextureType_EMISSION_COLOR, aiTextureType_EMISSIVE });
assignTexture("metallicTexture", { aiTextureType_METALNESS });
assignTexture("roughnessTexture", { aiTextureType_DIFFUSE_ROUGHNESS });
assignTexture("occlusionTexture", { aiTextureType_AMBIENT_OCCLUSION, aiTextureType_LIGHTMAP });
assignTexture("opacityTexture", { aiTextureType_OPACITY });
}
Material* ImportSingleMaterial(const aiMaterial& assimpMaterial,
const Containers::String& sourcePath,
Core::uint32 materialIndex,
TextureImportContext& context) {
aiString assimpName;
const bool hasName = assimpMaterial.Get(AI_MATKEY_NAME, assimpName) == AI_SUCCESS &&
assimpName.length > 0;
const std::string materialName = hasName
? std::string(assimpName.C_Str())
: std::string("Material_") + std::to_string(materialIndex);
auto* material = new Material();
IResource::ConstructParams params;
params.name = Containers::String(materialName.c_str());
params.path = BuildSubResourcePath(sourcePath, "material", materialIndex, Containers::String(".mat"));
params.guid = ResourceGUID::Generate(params.path);
params.memorySize = materialName.length() + params.path.Length();
material->Initialize(params);
ImportMaterialProperties(assimpMaterial, *material);
ImportMaterialTextures(assimpMaterial, *material, context);
return material;
}
ImportedMeshData ImportSingleMesh(const aiMesh& mesh,
const Math::Matrix4& worldTransform,
float globalScale,
@@ -282,7 +582,6 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
mesh->Initialize(params);
mesh->SetVertexData(vertices.data(),
@@ -313,6 +612,25 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
mesh->AddSection(section);
}
mesh->SetBounds(ComputeBounds(vertices));
if (scene->HasMaterials() && resolvedSettings->HasImportFlag(MeshImportFlags::ImportMaterials)) {
TextureImportContext textureContext{
*scene,
path,
std::filesystem::path(path.CStr()).parent_path()
};
for (Core::uint32 materialIndex = 0; materialIndex < scene->mNumMaterials; ++materialIndex) {
const aiMaterial* assimpMaterial = scene->mMaterials[materialIndex];
mesh->AddMaterial(assimpMaterial != nullptr
? ImportSingleMaterial(*assimpMaterial, path, materialIndex, textureContext)
: nullptr);
}
for (Texture* texture : textureContext.ownedTextures) {
mesh->AddTexture(texture);
}
}
return LoadResult(mesh);
}