Import material textures with mesh assets
This commit is contained in:
@@ -68,6 +68,7 @@ public:
|
||||
Core::int32 GetInt(const Containers::String& name) const;
|
||||
bool GetBool(const Containers::String& name) const;
|
||||
ResourceHandle<Texture> GetTexture(const Containers::String& name) const;
|
||||
Core::uint32 GetTextureBindingCount() const { return static_cast<Core::uint32>(m_textureBindings.Size()); }
|
||||
|
||||
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
|
||||
void UpdateConstantBuffer();
|
||||
@@ -77,6 +78,8 @@ public:
|
||||
void ClearAllProperties();
|
||||
|
||||
private:
|
||||
void UpdateMemorySize();
|
||||
|
||||
ResourceHandle<class Shader> m_shader;
|
||||
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
|
||||
Containers::Array<Core::uint8> m_constantBufferData;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
class Material;
|
||||
class Texture;
|
||||
|
||||
enum class VertexAttribute : Core::uint32 {
|
||||
Position = 1 << 0, Normal = 1 << 1, Tangent = 1 << 2,
|
||||
Color = 1 << 3, UV0 = 1 << 4, UV1 = 1 << 5,
|
||||
@@ -77,7 +80,12 @@ public:
|
||||
const Math::Bounds& GetBounds() const { return m_bounds; }
|
||||
|
||||
void AddSection(const MeshSection& section);
|
||||
void AddMaterial(Material* material);
|
||||
void AddTexture(Texture* texture);
|
||||
const Containers::Array<MeshSection>& GetSections() const { return m_sections; }
|
||||
const Containers::Array<Material*>& GetMaterials() const { return m_materials; }
|
||||
const Containers::Array<Texture*>& GetTextures() const { return m_textures; }
|
||||
Material* GetMaterial(Core::uint32 index) const;
|
||||
|
||||
private:
|
||||
void UpdateMemorySize();
|
||||
@@ -92,6 +100,8 @@ private:
|
||||
Containers::Array<Core::uint8> m_vertexData;
|
||||
Containers::Array<Core::uint8> m_indexData;
|
||||
Containers::Array<MeshSection> m_sections;
|
||||
Containers::Array<Material*> m_materials;
|
||||
Containers::Array<Texture*> m_textures;
|
||||
Math::Bounds m_bounds;
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
float GetThreshold() const { return m_threshold; }
|
||||
|
||||
private:
|
||||
MeshImportFlags m_importFlags = MeshImportFlags::None;
|
||||
MeshImportFlags m_importFlags = MeshImportFlags::ImportMaterials;
|
||||
float m_scale = 1.0f;
|
||||
Math::Vector3 m_offset = Math::Vector3::Zero();
|
||||
bool m_axisConversion = true;
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
Containers::Array<Containers::String> GetSupportedExtensions() const override;
|
||||
bool CanLoad(const Containers::String& path) const override;
|
||||
LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) override;
|
||||
LoadResult LoadFromMemory(const Containers::String& path, const void* data, size_t dataSize) const;
|
||||
ImportSettings* GetDefaultSettings() const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ void Material::Release() {
|
||||
m_textureBindings.Clear();
|
||||
m_constantBufferData.Clear();
|
||||
m_isValid = false;
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetShader(const ResourceHandle<Shader>& shader) {
|
||||
m_shader = shader;
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetFloat(const Containers::String& name, float value) {
|
||||
@@ -28,6 +30,7 @@ void Material::SetFloat(const Containers::String& name, float value) {
|
||||
prop.value.floatValue[0] = value;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetFloat2(const Containers::String& name, const Math::Vector2& value) {
|
||||
@@ -38,6 +41,7 @@ void Material::SetFloat2(const Containers::String& name, const Math::Vector2& va
|
||||
prop.value.floatValue[1] = value.y;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetFloat3(const Containers::String& name, const Math::Vector3& value) {
|
||||
@@ -49,6 +53,7 @@ void Material::SetFloat3(const Containers::String& name, const Math::Vector3& va
|
||||
prop.value.floatValue[2] = value.z;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetFloat4(const Containers::String& name, const Math::Vector4& value) {
|
||||
@@ -61,6 +66,7 @@ void Material::SetFloat4(const Containers::String& name, const Math::Vector4& va
|
||||
prop.value.floatValue[3] = value.w;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetInt(const Containers::String& name, Core::int32 value) {
|
||||
@@ -70,6 +76,7 @@ void Material::SetInt(const Containers::String& name, Core::int32 value) {
|
||||
prop.value.intValue[0] = value;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetBool(const Containers::String& name, bool value) {
|
||||
@@ -79,6 +86,7 @@ void Material::SetBool(const Containers::String& name, bool value) {
|
||||
prop.value.boolValue = value;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetTexture(const Containers::String& name, const ResourceHandle<Texture>& texture) {
|
||||
@@ -88,11 +96,20 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle<T
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
|
||||
for (auto& binding : m_textureBindings) {
|
||||
if (binding.name == name) {
|
||||
binding.texture = texture;
|
||||
UpdateMemorySize();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBinding binding;
|
||||
binding.name = name;
|
||||
binding.slot = static_cast<Core::uint32>(m_textureBindings.Size());
|
||||
binding.texture = texture;
|
||||
m_textureBindings.PushBack(binding);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
float Material::GetFloat(const Containers::String& name) const {
|
||||
@@ -155,6 +172,7 @@ ResourceHandle<Texture> Material::GetTexture(const Containers::String& name) con
|
||||
|
||||
void Material::UpdateConstantBuffer() {
|
||||
m_constantBufferData.Clear();
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
bool Material::HasProperty(const Containers::String& name) const {
|
||||
@@ -163,12 +181,26 @@ bool Material::HasProperty(const Containers::String& name) const {
|
||||
|
||||
void Material::RemoveProperty(const Containers::String& name) {
|
||||
m_properties.Erase(name);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::ClearAllProperties() {
|
||||
m_properties.Clear();
|
||||
m_textureBindings.Clear();
|
||||
m_constantBufferData.Clear();
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::UpdateMemorySize() {
|
||||
m_memorySize = m_constantBufferData.Size() +
|
||||
m_textureBindings.Size() * sizeof(TextureBinding) +
|
||||
m_properties.Size() * sizeof(MaterialProperty) +
|
||||
m_name.Length() +
|
||||
m_path.Length();
|
||||
|
||||
for (const auto& binding : m_textureBindings) {
|
||||
m_memorySize += binding.name.Length();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -11,6 +13,8 @@ void Mesh::Release() {
|
||||
m_vertexData.Clear();
|
||||
m_indexData.Clear();
|
||||
m_sections.Clear();
|
||||
m_materials.Clear();
|
||||
m_textures.Clear();
|
||||
m_vertexCount = 0;
|
||||
m_vertexStride = 0;
|
||||
m_attributes = VertexAttribute::Position;
|
||||
@@ -52,10 +56,41 @@ void Mesh::AddSection(const MeshSection& section) {
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Mesh::AddMaterial(Material* material) {
|
||||
m_materials.PushBack(material);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Mesh::AddTexture(Texture* texture) {
|
||||
m_textures.PushBack(texture);
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
Material* Mesh::GetMaterial(Core::uint32 index) const {
|
||||
if (index >= m_materials.Size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_materials[index];
|
||||
}
|
||||
|
||||
void Mesh::UpdateMemorySize() {
|
||||
m_memorySize = m_vertexData.Size() +
|
||||
m_indexData.Size() +
|
||||
m_sections.Size() * sizeof(MeshSection);
|
||||
m_sections.Size() * sizeof(MeshSection) +
|
||||
m_materials.Size() * sizeof(Material*) +
|
||||
m_textures.Size() * sizeof(Texture*);
|
||||
|
||||
for (const Material* material : m_materials) {
|
||||
if (material != nullptr) {
|
||||
m_memorySize += material->GetMemorySize();
|
||||
}
|
||||
}
|
||||
|
||||
for (const Texture* texture : m_textures) {
|
||||
if (texture != nullptr) {
|
||||
m_memorySize += texture->GetMemorySize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
|
||||
@@ -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(),
|
||||
@@ -314,6 +613,25 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,54 @@
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <stb_image.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
LoadResult CreateTextureResource(const Containers::String& path,
|
||||
TextureFormat format,
|
||||
Core::uint32 width,
|
||||
Core::uint32 height,
|
||||
const void* pixelData,
|
||||
size_t pixelDataSize) {
|
||||
auto* texture = new Texture();
|
||||
|
||||
IResource::ConstructParams params;
|
||||
params.name = GetResourceNameFromPath(path);
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
texture->Initialize(params);
|
||||
|
||||
if (!texture->Create(width,
|
||||
height,
|
||||
1,
|
||||
1,
|
||||
TextureType::Texture2D,
|
||||
format,
|
||||
pixelData,
|
||||
pixelDataSize)) {
|
||||
delete texture;
|
||||
return LoadResult(Containers::String("Failed to create texture resource: ") + path);
|
||||
}
|
||||
|
||||
return LoadResult(texture);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureLoader::TextureLoader() = default;
|
||||
TextureLoader::~TextureLoader() = default;
|
||||
|
||||
@@ -21,8 +66,7 @@ Containers::Array<Containers::String> TextureLoader::GetSupportedExtensions() co
|
||||
}
|
||||
|
||||
bool TextureLoader::CanLoad(const Containers::String& path) const {
|
||||
Containers::String ext = GetExtension(path);
|
||||
ext.ToLower();
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
return ext == "png" || ext == "jpg" || ext == "jpeg" ||
|
||||
ext == "tga" || ext == "bmp" || ext == "gif" ||
|
||||
@@ -32,29 +76,83 @@ bool TextureLoader::CanLoad(const Containers::String& path) const {
|
||||
LoadResult TextureLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
(void)settings;
|
||||
|
||||
Containers::String ext = GetExtension(path);
|
||||
ext.ToLower();
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported texture format: ") + ext);
|
||||
}
|
||||
|
||||
if (ext == "dds") {
|
||||
return LoadResult(Containers::String("DDS texture decoding is not implemented yet: ") + path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> fileData = ReadFileData(path);
|
||||
if (fileData.Empty()) {
|
||||
return LoadResult(Containers::String("Failed to read file: ") + path);
|
||||
}
|
||||
|
||||
auto* texture = new Texture();
|
||||
return LoadFromMemory(path, fileData.Data(), fileData.Size());
|
||||
}
|
||||
|
||||
IResource::ConstructParams params;
|
||||
params.name = path;
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = fileData.Size();
|
||||
LoadResult TextureLoader::LoadFromMemory(const Containers::String& path, const void* data, size_t dataSize) const {
|
||||
if (data == nullptr || dataSize == 0) {
|
||||
return LoadResult(Containers::String("Texture data is empty: ") + path);
|
||||
}
|
||||
|
||||
texture->Initialize(params);
|
||||
const auto* stbData = reinterpret_cast<const stbi_uc*>(data);
|
||||
const int stbDataSize = static_cast<int>(dataSize);
|
||||
|
||||
return LoadResult(texture);
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int channels = 0;
|
||||
|
||||
if (stbi_is_hdr_from_memory(stbData, stbDataSize) != 0) {
|
||||
float* pixels = stbi_loadf_from_memory(stbData,
|
||||
stbDataSize,
|
||||
&width,
|
||||
&height,
|
||||
&channels,
|
||||
STBI_rgb_alpha);
|
||||
if (pixels == nullptr) {
|
||||
return LoadResult(Containers::String("Failed to decode HDR texture: ") + path);
|
||||
}
|
||||
|
||||
const size_t pixelDataSize = static_cast<size_t>(width) *
|
||||
static_cast<size_t>(height) *
|
||||
4u *
|
||||
sizeof(float);
|
||||
LoadResult result = CreateTextureResource(path,
|
||||
TextureFormat::RGBA32_FLOAT,
|
||||
static_cast<Core::uint32>(width),
|
||||
static_cast<Core::uint32>(height),
|
||||
pixels,
|
||||
pixelDataSize);
|
||||
stbi_image_free(pixels);
|
||||
return result;
|
||||
}
|
||||
|
||||
stbi_uc* pixels = stbi_load_from_memory(stbData,
|
||||
stbDataSize,
|
||||
&width,
|
||||
&height,
|
||||
&channels,
|
||||
STBI_rgb_alpha);
|
||||
if (pixels == nullptr) {
|
||||
return LoadResult(Containers::String("Failed to decode texture: ") + path);
|
||||
}
|
||||
|
||||
const size_t pixelDataSize = static_cast<size_t>(width) *
|
||||
static_cast<size_t>(height) *
|
||||
4u *
|
||||
sizeof(stbi_uc);
|
||||
LoadResult result = CreateTextureResource(path,
|
||||
TextureFormat::RGBA8_UNORM,
|
||||
static_cast<Core::uint32>(width),
|
||||
static_cast<Core::uint32>(height),
|
||||
pixels,
|
||||
pixelDataSize);
|
||||
stbi_image_free(pixels);
|
||||
return result;
|
||||
}
|
||||
|
||||
ImportSettings* TextureLoader::GetDefaultSettings() const {
|
||||
|
||||
@@ -104,6 +104,18 @@ TEST(Material, SetGetTexture) {
|
||||
EXPECT_EQ(material.GetTexture("uDiffuse").Get(), texture);
|
||||
}
|
||||
|
||||
TEST(Material, SetTextureReplacesExistingBinding) {
|
||||
Material material;
|
||||
Texture* firstTexture = new Texture();
|
||||
Texture* secondTexture = new Texture();
|
||||
|
||||
material.SetTexture("uDiffuse", ResourceHandle<Texture>(firstTexture));
|
||||
material.SetTexture("uDiffuse", ResourceHandle<Texture>(secondTexture));
|
||||
|
||||
EXPECT_EQ(material.GetTextureBindingCount(), 1u);
|
||||
EXPECT_EQ(material.GetTexture("uDiffuse").Get(), secondTexture);
|
||||
}
|
||||
|
||||
TEST(Material, HasProperty) {
|
||||
Material material;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
@@ -31,6 +32,11 @@ TEST(Mesh, GetSections) {
|
||||
EXPECT_EQ(mesh.GetSections().Size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Mesh, GetMaterials) {
|
||||
Mesh mesh;
|
||||
EXPECT_EQ(mesh.GetMaterials().Size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Mesh, SetVertexAndIndexData) {
|
||||
Mesh mesh;
|
||||
|
||||
@@ -71,6 +77,17 @@ TEST(Mesh, AddSection) {
|
||||
EXPECT_EQ(mesh.GetSections()[0].materialID, 2u);
|
||||
}
|
||||
|
||||
TEST(Mesh, AddMaterial) {
|
||||
Mesh mesh;
|
||||
|
||||
auto* material = new Material();
|
||||
mesh.AddMaterial(material);
|
||||
|
||||
ASSERT_EQ(mesh.GetMaterials().Size(), 1u);
|
||||
EXPECT_EQ(mesh.GetMaterial(0), material);
|
||||
EXPECT_GT(mesh.GetMemorySize(), 0u);
|
||||
}
|
||||
|
||||
TEST(Mesh, SetBounds) {
|
||||
Mesh mesh;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace {
|
||||
|
||||
TEST(MeshImportSettings, DefaultConstructor) {
|
||||
MeshImportSettings settings;
|
||||
EXPECT_EQ(settings.GetImportFlags(), MeshImportFlags::None);
|
||||
EXPECT_EQ(settings.GetImportFlags(), MeshImportFlags::ImportMaterials);
|
||||
EXPECT_FLOAT_EQ(settings.GetScale(), 1.0f);
|
||||
EXPECT_EQ(settings.GetOffset(), Vector3::Zero());
|
||||
EXPECT_TRUE(settings.GetAxisConversion());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
#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/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
#include <filesystem>
|
||||
@@ -102,4 +104,32 @@ TEST(MeshLoader, GeneratesNormalsAndTangentsWhenRequested) {
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
|
||||
MeshLoader loader;
|
||||
const std::string path = GetMeshFixturePath("textured_triangle.obj");
|
||||
|
||||
LoadResult result = loader.Load(path.c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* mesh = static_cast<Mesh*>(result.resource);
|
||||
ASSERT_EQ(mesh->GetSections().Size(), 1u);
|
||||
ASSERT_GE(mesh->GetMaterials().Size(), 1u);
|
||||
EXPECT_LT(mesh->GetSections()[0].materialID, mesh->GetMaterials().Size());
|
||||
|
||||
Material* material = mesh->GetMaterial(mesh->GetSections()[0].materialID);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_TRUE(material->HasProperty("baseColorTexture"));
|
||||
EXPECT_EQ(material->GetTextureBindingCount(), 1u);
|
||||
|
||||
ResourceHandle<Texture> diffuseTexture = material->GetTexture("baseColorTexture");
|
||||
ASSERT_TRUE(diffuseTexture.IsValid());
|
||||
EXPECT_EQ(diffuseTexture->GetWidth(), 2u);
|
||||
EXPECT_EQ(diffuseTexture->GetHeight(), 2u);
|
||||
EXPECT_EQ(diffuseTexture->GetPixelDataSize(), 16u);
|
||||
EXPECT_EQ(mesh->GetTextures().Size(), 1u);
|
||||
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -28,5 +28,9 @@ target_include_directories(texture_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/fixtures
|
||||
)
|
||||
|
||||
target_compile_definitions(texture_tests PRIVATE
|
||||
XCENGINE_TEST_FIXTURES_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures"
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(texture_tests)
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace XCEngine::Resources;
|
||||
using namespace XCEngine::Containers;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetTextureFixturePath(const char* fileName) {
|
||||
return (std::filesystem::path(XCENGINE_TEST_FIXTURES_DIR) / "Resources" / "Texture" / fileName).string();
|
||||
}
|
||||
|
||||
TEST(TextureLoader, GetResourceType) {
|
||||
TextureLoader loader;
|
||||
EXPECT_EQ(loader.GetResourceType(), ResourceType::Texture);
|
||||
@@ -34,4 +39,22 @@ TEST(TextureLoader, LoadInvalidPath) {
|
||||
EXPECT_FALSE(result);
|
||||
}
|
||||
|
||||
TEST(TextureLoader, LoadValidBmpTexture) {
|
||||
TextureLoader loader;
|
||||
const std::string path = GetTextureFixturePath("checker.bmp");
|
||||
|
||||
LoadResult result = loader.Load(path.c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* texture = static_cast<Texture*>(result.resource);
|
||||
EXPECT_EQ(texture->GetWidth(), 2u);
|
||||
EXPECT_EQ(texture->GetHeight(), 2u);
|
||||
EXPECT_EQ(texture->GetTextureType(), TextureType::Texture2D);
|
||||
EXPECT_EQ(texture->GetFormat(), TextureFormat::RGBA8_UNORM);
|
||||
EXPECT_EQ(texture->GetPixelDataSize(), 16u);
|
||||
|
||||
delete texture;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BIN
tests/fixtures/Resources/Mesh/checker.bmp
vendored
Normal file
BIN
tests/fixtures/Resources/Mesh/checker.bmp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 B |
7
tests/fixtures/Resources/Mesh/textured_triangle.mtl
vendored
Normal file
7
tests/fixtures/Resources/Mesh/textured_triangle.mtl
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
newmtl TestMaterial
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 1.000000 1.000000 1.000000
|
||||
Ks 0.000000 0.000000 0.000000
|
||||
d 1.0
|
||||
illum 2
|
||||
map_Kd checker.bmp
|
||||
11
tests/fixtures/Resources/Mesh/textured_triangle.obj
vendored
Normal file
11
tests/fixtures/Resources/Mesh/textured_triangle.obj
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
mtllib textured_triangle.mtl
|
||||
o TexturedTriangle
|
||||
v 0.0 0.0 1.0
|
||||
v 1.0 0.0 1.0
|
||||
v 0.0 1.0 1.0
|
||||
vt 0.0 0.0
|
||||
vt 1.0 0.0
|
||||
vt 0.0 1.0
|
||||
vn 0.0 0.0 1.0
|
||||
usemtl TestMaterial
|
||||
f 1/1/1 2/2/1 3/3/1
|
||||
BIN
tests/fixtures/Resources/Texture/checker.bmp
vendored
Normal file
BIN
tests/fixtures/Resources/Texture/checker.bmp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 B |
Reference in New Issue
Block a user