Add model and GaussianSplat asset pipelines

This commit is contained in:
2026-04-10 20:55:48 +08:00
parent 8f5c342799
commit 503e6408ed
39 changed files with 5900 additions and 141 deletions

View File

@@ -1,10 +1,13 @@
#pragma once
#include <XCEngine/Core/Asset/AssetGUID.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Bounds.h>
#include <XCEngine/Core/Types.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
#include <XCEngine/Resources/UI/UIDocumentTypes.h>
namespace XCEngine {
@@ -16,6 +19,8 @@ constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
constexpr Core::uint32 kShaderArtifactSchemaVersion = 5;
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
constexpr Core::uint32 kVolumeFieldArtifactSchemaVersion = 2;
constexpr Core::uint32 kModelArtifactSchemaVersion = 1;
constexpr Core::uint32 kGaussianSplatArtifactSchemaVersion = 1;
struct TextureArtifactHeader {
char magic[8] = { 'X', 'C', 'T', 'E', 'X', '0', '1', '\0' };
@@ -66,6 +71,38 @@ struct MaterialPropertyArtifact {
MaterialProperty::Value value = {};
};
struct ModelArtifactFileHeader {
char magic[8] = { 'X', 'C', 'M', 'O', 'D', '0', '1', '\0' };
Core::uint32 schemaVersion = kModelArtifactSchemaVersion;
};
struct ModelArtifactHeader {
Core::uint32 nodeCount = 0;
Core::uint32 meshBindingCount = 0;
Core::uint32 materialBindingCount = 0;
Core::uint32 rootNodeIndex = 0xffffffffu;
};
struct ModelNodeArtifactHeader {
Core::int32 parentIndex = -1;
Core::uint32 meshBindingStart = 0;
Core::uint32 meshBindingCount = 0;
Math::Vector3 localPosition = Math::Vector3::Zero();
Math::Quaternion localRotation = Math::Quaternion::Identity();
Math::Vector3 localScale = Math::Vector3(1.0f, 1.0f, 1.0f);
};
struct ModelMeshBindingArtifact {
LocalID meshLocalID = kInvalidLocalID;
Core::uint32 materialBindingStart = 0;
Core::uint32 materialBindingCount = 0;
};
struct ModelMaterialBindingArtifact {
Core::uint32 slotIndex = 0;
LocalID materialLocalID = kInvalidLocalID;
};
struct ShaderArtifactFileHeader {
char magic[8] = { 'X', 'C', 'S', 'H', 'D', '0', '5', '\0' };
Core::uint32 schemaVersion = kShaderArtifactSchemaVersion;
@@ -144,5 +181,36 @@ struct VolumeFieldArtifactHeader {
Core::uint64 payloadSize = 0;
};
struct GaussianSplatArtifactFileHeader {
char magic[8] = { 'X', 'C', 'G', 'S', 'P', '0', '1', '\0' };
Core::uint32 schemaVersion = kGaussianSplatArtifactSchemaVersion;
};
struct GaussianSplatArtifactHeader {
Core::uint32 contentVersion = 1;
Core::uint32 splatCount = 0;
Core::uint32 chunkCount = 0;
Core::uint32 cameraCount = 0;
Math::Vector3 boundsMin = Math::Vector3::Zero();
Math::Vector3 boundsMax = Math::Vector3::Zero();
Core::uint32 positionFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::VectorFloat32);
Core::uint32 otherFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::OtherFloat32);
Core::uint32 colorFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::ColorRGBA32F);
Core::uint32 shFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::SHFloat32);
Core::uint32 chunkFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::Unknown);
Core::uint32 cameraFormat = static_cast<Core::uint32>(GaussianSplatSectionFormat::Unknown);
Core::uint32 sectionCount = 0;
Core::uint64 payloadSize = 0;
};
struct GaussianSplatArtifactSectionRecord {
Core::uint32 sectionType = static_cast<Core::uint32>(GaussianSplatSectionType::Unknown);
Core::uint32 format = static_cast<Core::uint32>(GaussianSplatSectionFormat::Unknown);
Core::uint64 payloadOffset = 0;
Core::uint64 dataSize = 0;
Core::uint32 elementCount = 0;
Core::uint32 elementStride = 0;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -82,6 +82,7 @@ public:
bool TryGetAssetGuid(const Containers::String& requestPath, AssetGUID& outGuid) const;
bool TryGetImportableResourceType(const Containers::String& requestPath, ResourceType& outType) const;
bool TryGetAssetRef(const Containers::String& requestPath, ResourceType resourceType, AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath);
bool ReimportAsset(const Containers::String& requestPath,
ResolvedAsset& outAsset,
MaintenanceStats* outStats = nullptr);
@@ -139,6 +140,8 @@ private:
ArtifactRecord& outRecord);
bool ImportShaderAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
bool ImportGaussianSplatAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
bool ImportVolumeFieldAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
bool ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
@@ -161,7 +164,7 @@ private:
ArtifactDependencyRecord& outRecord) const;
bool AreDependenciesCurrent(const std::vector<ArtifactDependencyRecord>& dependencies) const;
bool CollectModelDependencies(const SourceAssetRecord& sourceRecord,
const Mesh& mesh,
const std::vector<Containers::String>& importedTexturePaths,
std::vector<ArtifactDependencyRecord>& outDependencies) const;
bool CollectMaterialDependencies(const Material& material,
std::vector<ArtifactDependencyRecord>& outDependencies) const;

View File

@@ -75,6 +75,8 @@ public:
bool TryGetAssetRef(const Containers::String& requestPath,
ResourceType resourceType,
AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetRef& assetRef,
Containers::String& outPath);
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
void BuildLookupSnapshot(LookupSnapshot& outSnapshot) const;

View File

@@ -21,7 +21,7 @@ public:
const Containers::String& path,
ResourceType resourceType,
AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetImportService& importService,
bool TryResolveAssetPath(AssetImportService& importService,
const AssetRef& assetRef,
Containers::String& outPath) const;
void RememberResolvedPath(const AssetGUID& assetGuid, const Containers::String& relativePath);

View File

@@ -26,7 +26,9 @@ enum class ResourceType : Core::uint8 {
UIView,
UITheme,
UISchema,
VolumeField
VolumeField,
Model,
GaussianSplat
};
constexpr const char* GetResourceTypeName(ResourceType type) {
@@ -47,6 +49,8 @@ constexpr const char* GetResourceTypeName(ResourceType type) {
case ResourceType::UITheme: return "UITheme";
case ResourceType::UISchema: return "UISchema";
case ResourceType::VolumeField: return "VolumeField";
case ResourceType::Model: return "Model";
case ResourceType::GaussianSplat: return "GaussianSplat";
default: return "Unknown";
}
}
@@ -101,6 +105,8 @@ template<> inline ResourceType GetResourceType<class UIView>() { return Resource
template<> inline ResourceType GetResourceType<class UITheme>() { return ResourceType::UITheme; }
template<> inline ResourceType GetResourceType<class UISchema>() { return ResourceType::UISchema; }
template<> inline ResourceType GetResourceType<class VolumeField>() { return ResourceType::VolumeField; }
template<> inline ResourceType GetResourceType<class Model>() { return ResourceType::Model; }
template<> inline ResourceType GetResourceType<class GaussianSplat>() { return ResourceType::GaussianSplat; }
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,138 @@
#pragma once
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Math/Bounds.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Core/Types.h>
namespace XCEngine {
namespace Resources {
enum class GaussianSplatSectionType : Core::uint32 {
Unknown = 0,
Positions = 1,
Other = 2,
Color = 3,
SH = 4,
Chunks = 5,
Cameras = 6
};
enum class GaussianSplatSectionFormat : Core::uint32 {
Unknown = 0,
VectorFloat32 = 1,
VectorFloat16 = 2,
VectorNorm16 = 3,
VectorNorm11 = 4,
VectorNorm6 = 5,
OtherFloat32 = 16,
OtherPacked32 = 17,
ColorRGBA32F = 32,
ColorRGBA16F = 33,
ColorRGBA8 = 34,
ColorBC7 = 35,
SHFloat32 = 48,
SHFloat16 = 49,
SHNorm11 = 50,
SHNorm6 = 51,
ChunkFloat32 = 64,
CameraFloat32 = 80
};
constexpr Core::uint32 kGaussianSplatSHCoefficientCount = 45;
struct GaussianSplatPositionRecord {
Math::Vector3 position = Math::Vector3::Zero();
};
struct GaussianSplatOtherRecord {
Math::Quaternion rotation = Math::Quaternion::Identity();
Math::Vector3 scale = Math::Vector3::Zero();
float reserved = 0.0f;
};
struct GaussianSplatColorRecord {
Math::Vector4 colorOpacity = Math::Vector4::Zero();
};
struct GaussianSplatSHRecord {
float coefficients[kGaussianSplatSHCoefficientCount] = {};
};
struct GaussianSplatSection {
GaussianSplatSectionType type = GaussianSplatSectionType::Unknown;
GaussianSplatSectionFormat format = GaussianSplatSectionFormat::Unknown;
Core::uint64 dataOffset = 0;
Core::uint64 dataSize = 0;
Core::uint32 elementCount = 0;
Core::uint32 elementStride = 0;
};
struct GaussianSplatMetadata {
Core::uint32 contentVersion = 1;
Core::uint32 splatCount = 0;
Core::uint32 chunkCount = 0;
Core::uint32 cameraCount = 0;
Math::Bounds bounds;
GaussianSplatSectionFormat positionFormat = GaussianSplatSectionFormat::VectorFloat32;
GaussianSplatSectionFormat otherFormat = GaussianSplatSectionFormat::OtherFloat32;
GaussianSplatSectionFormat colorFormat = GaussianSplatSectionFormat::ColorRGBA32F;
GaussianSplatSectionFormat shFormat = GaussianSplatSectionFormat::SHFloat32;
GaussianSplatSectionFormat chunkFormat = GaussianSplatSectionFormat::Unknown;
GaussianSplatSectionFormat cameraFormat = GaussianSplatSectionFormat::Unknown;
};
class GaussianSplat : public IResource {
public:
GaussianSplat();
~GaussianSplat() override;
ResourceType GetType() const override { return ResourceType::GaussianSplat; }
const Containers::String& GetName() const override { return m_name; }
const Containers::String& GetPath() const override { return m_path; }
ResourceGUID GetGUID() const override { return m_guid; }
bool IsValid() const override { return m_isValid; }
size_t GetMemorySize() const override { return m_memorySize; }
void Release() override;
bool CreateOwned(const GaussianSplatMetadata& metadata,
Containers::Array<GaussianSplatSection>&& sections,
Containers::Array<Core::uint8>&& payload);
void Clear();
const GaussianSplatMetadata& GetMetadata() const { return m_metadata; }
Core::uint32 GetContentVersion() const { return m_metadata.contentVersion; }
Core::uint32 GetSplatCount() const { return m_metadata.splatCount; }
Core::uint32 GetChunkCount() const { return m_metadata.chunkCount; }
Core::uint32 GetCameraCount() const { return m_metadata.cameraCount; }
const Math::Bounds& GetBounds() const { return m_metadata.bounds; }
GaussianSplatSectionFormat GetPositionFormat() const { return m_metadata.positionFormat; }
GaussianSplatSectionFormat GetOtherFormat() const { return m_metadata.otherFormat; }
GaussianSplatSectionFormat GetColorFormat() const { return m_metadata.colorFormat; }
GaussianSplatSectionFormat GetSHFormat() const { return m_metadata.shFormat; }
GaussianSplatSectionFormat GetChunkFormat() const { return m_metadata.chunkFormat; }
GaussianSplatSectionFormat GetCameraFormat() const { return m_metadata.cameraFormat; }
const Containers::Array<GaussianSplatSection>& GetSections() const { return m_sections; }
const GaussianSplatSection* FindSection(GaussianSplatSectionType type) const;
const void* GetSectionData(GaussianSplatSectionType type) const;
const GaussianSplatPositionRecord* GetPositionRecords() const;
const GaussianSplatOtherRecord* GetOtherRecords() const;
const GaussianSplatColorRecord* GetColorRecords() const;
const GaussianSplatSHRecord* GetSHRecords() const;
const Core::uint8* GetPayloadData() const { return m_payload.Data(); }
size_t GetPayloadSize() const { return m_payload.Size(); }
private:
bool ValidateSections(const Containers::Array<GaussianSplatSection>& sections, size_t payloadSize) const;
void UpdateMemorySize();
GaussianSplatMetadata m_metadata = {};
Containers::Array<GaussianSplatSection> m_sections;
Containers::Array<Core::uint8> m_payload;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,18 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/IO/IResourceLoader.h>
namespace XCEngine {
namespace Resources {
class GaussianSplat;
bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
const GaussianSplat& gaussianSplat,
Containers::String* outErrorMessage = nullptr);
LoadResult LoadGaussianSplatArtifact(const Containers::String& path);
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,21 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
namespace XCEngine {
namespace Resources {
class GaussianSplatLoader : public IResourceLoader {
public:
GaussianSplatLoader();
~GaussianSplatLoader() override;
ResourceType GetResourceType() const override { return ResourceType::GaussianSplat; }
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;
ImportSettings* GetDefaultSettings() const override;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,43 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Asset/AssetGUID.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <vector>
namespace XCEngine {
namespace Resources {
class Model;
class Mesh;
class Material;
class Texture;
struct ImportedModelMesh {
LocalID localID = kInvalidLocalID;
Mesh* mesh = nullptr;
std::vector<LocalID> materialLocalIDs;
};
struct ImportedModelMaterial {
LocalID localID = kInvalidLocalID;
Material* material = nullptr;
};
struct ImportedModelData {
Model* model = nullptr;
std::vector<ImportedModelMesh> meshes;
std::vector<ImportedModelMaterial> materials;
std::vector<Texture*> textures;
void Reset();
};
bool ImportAssimpModel(const Containers::String& sourcePath,
const MeshImportSettings& settings,
ImportedModelData& outData,
Containers::String* outErrorMessage = nullptr);
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,72 @@
#pragma once
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Asset/AssetGUID.h>
#include <XCEngine/Core/Containers/Array.h>
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Vector3.h>
namespace XCEngine {
namespace Resources {
constexpr Core::uint32 kInvalidModelNodeIndex = 0xffffffffu;
struct ModelMaterialBinding {
Core::uint32 slotIndex = 0;
LocalID materialLocalID = kInvalidLocalID;
};
struct ModelMeshBinding {
LocalID meshLocalID = kInvalidLocalID;
Core::uint32 materialBindingStart = 0;
Core::uint32 materialBindingCount = 0;
};
struct ModelNode {
Containers::String name;
Core::int32 parentIndex = -1;
Core::uint32 meshBindingStart = 0;
Core::uint32 meshBindingCount = 0;
Math::Vector3 localPosition = Math::Vector3::Zero();
Math::Quaternion localRotation = Math::Quaternion::Identity();
Math::Vector3 localScale = Math::Vector3(1.0f, 1.0f, 1.0f);
};
class Model : public IResource {
public:
Model();
~Model() override;
ResourceType GetType() const override { return ResourceType::Model; }
const Containers::String& GetName() const override { return m_name; }
const Containers::String& GetPath() const override { return m_path; }
ResourceGUID GetGUID() const override { return m_guid; }
bool IsValid() const override { return m_isValid; }
size_t GetMemorySize() const override { return m_memorySize; }
void Release() override;
void SetRootNodeIndex(Core::uint32 index);
Core::uint32 GetRootNodeIndex() const { return m_rootNodeIndex; }
bool HasRootNode() const { return m_rootNodeIndex != kInvalidModelNodeIndex; }
void AddNode(const ModelNode& node);
void AddMeshBinding(const ModelMeshBinding& binding);
void AddMaterialBinding(const ModelMaterialBinding& binding);
void ClearGraph();
const Containers::Array<ModelNode>& GetNodes() const { return m_nodes; }
const Containers::Array<ModelMeshBinding>& GetMeshBindings() const { return m_meshBindings; }
const Containers::Array<ModelMaterialBinding>& GetMaterialBindings() const { return m_materialBindings; }
private:
void UpdateMemorySize();
Core::uint32 m_rootNodeIndex = kInvalidModelNodeIndex;
Containers::Array<ModelNode> m_nodes;
Containers::Array<ModelMeshBinding> m_meshBindings;
Containers::Array<ModelMaterialBinding> m_materialBindings;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,18 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/IO/IResourceLoader.h>
namespace XCEngine {
namespace Resources {
class Model;
bool WriteModelArtifactFile(const Containers::String& artifactPath,
const Model& model,
Containers::String* outErrorMessage = nullptr);
LoadResult LoadModelArtifact(const Containers::String& path);
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,21 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
namespace XCEngine {
namespace Resources {
class ModelLoader : public IResourceLoader {
public:
ModelLoader();
~ModelLoader() override;
ResourceType GetResourceType() const override { return ResourceType::Model; }
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;
ImportSettings* GetDefaultSettings() const override;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -14,6 +14,12 @@
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h>
#include <XCEngine/Resources/GaussianSplat/GaussianSplatLoader.h>
#include <XCEngine/Resources/Model/Model.h>
#include <XCEngine/Resources/Model/ModelArtifactIO.h>
#include <XCEngine/Resources/Model/ModelLoader.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>