导入更新

This commit is contained in:
Rowland 2026-06-17 09:04:20 +08:00
parent a31f700d75
commit a8739a9423
10 changed files with 19069 additions and 450 deletions

View File

@ -11,16 +11,16 @@
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.29019609093666077,
0.0,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"Metallic": 1.0,
"MetallicRoughnessTexture": "",
"Name": "Material 1",
"NormalTexture": "",
"Roughness": 0.5690000057220459,
"Roughness": 0.0,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -3,6 +3,6 @@
"guid": "fc3fd465-4f39-44af-907a-4fc0494c2502",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 1.mcmaterial.json",
"source_hash": 491201104691168908,
"source_hash": 12648564269874715657,
"source_path": "Assets/Materials/Material 1.mcmaterial.json"
}

Binary file not shown.

View File

@ -44,7 +44,7 @@
"c1bdb8de-36f0-fa5d-4ed3-aa7311a5c60a",
"474bab4f-e853-88a4-3738-590a98c9b0a8"
],
"guid": "b72a0c12-d6fd-4999-86c5-958e3bac1c78",
"guid": "e9fd854e-2d0a-43b6-a73e-2caa1fe61f4e",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/超精密磨床.glb.mcasset",
"source_hash": 4002013065180687563,

File diff suppressed because it is too large Load Diff

View File

@ -971,48 +971,84 @@ template <typename TGeneratedAsset, typename TLabelBuilder>
);
}
[[nodiscard]] std::optional<MetaCoreModelAssetDocument> MetaCoreLoadModelAssetDocumentForRecord(
const MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIPackageService& packageService,
const MetaCoreIReflectionRegistry& reflectionRegistry,
const MetaCoreAssetRecord& assetRecord,
std::unordered_map<MetaCoreAssetGuid, MetaCoreModelAssetDocument, MetaCoreAssetGuidHasher>* modelCache = nullptr
);
[[nodiscard]] std::optional<MetaCoreResolvedGeneratedAssetDocument> MetaCoreResolveGeneratedAssetDocument(
const MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIPackageService& packageService,
const MetaCoreIReflectionRegistry& reflectionRegistry,
const MetaCoreAssetGuid& generatedAssetGuid
const MetaCoreAssetGuid& generatedAssetGuid,
std::unordered_map<MetaCoreAssetGuid, MetaCoreModelAssetDocument, MetaCoreAssetGuidHasher>* modelCache = nullptr
) {
if (!assetDatabaseService.HasProject() || !generatedAssetGuid.IsValid()) {
return std::nullopt;
}
const auto findGeneratedAsset = [&](
const MetaCoreAssetRecord& record,
const MetaCoreModelAssetDocument& modelDocument
) -> std::optional<MetaCoreResolvedGeneratedAssetDocument> {
for (std::size_t index = 0; index < modelDocument.GeneratedMeshAssets.size(); ++index) {
if (modelDocument.GeneratedMeshAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, modelDocument, "mesh", index};
}
}
for (std::size_t index = 0; index < modelDocument.GeneratedMaterialAssets.size(); ++index) {
if (modelDocument.GeneratedMaterialAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, modelDocument, "material", index};
}
}
for (std::size_t index = 0; index < modelDocument.GeneratedTextureAssets.size(); ++index) {
if (modelDocument.GeneratedTextureAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, modelDocument, "texture", index};
}
}
return std::nullopt;
};
const std::filesystem::path sourcePath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(generatedAssetGuid);
if (!sourcePath.empty()) {
const auto sourceRecord = assetDatabaseService.FindAssetByRelativePath(sourcePath);
if (sourceRecord.has_value() && sourceRecord->Type == "model") {
const auto modelDocument = MetaCoreLoadModelAssetDocumentForRecord(
assetDatabaseService,
packageService,
reflectionRegistry,
*sourceRecord,
modelCache
);
if (modelDocument.has_value()) {
if (auto resolved = findGeneratedAsset(*sourceRecord, *modelDocument)) {
return resolved;
}
}
}
}
for (const MetaCoreAssetRecord& record : assetDatabaseService.GetAssetRegistry()) {
if (record.Type != "model") {
continue;
}
const std::filesystem::path relativePackagePath =
!record.PackagePath.empty() ? record.PackagePath : record.RelativePath;
const auto package =
packageService.ReadPackage(assetDatabaseService.GetProjectDescriptor().RootPath / relativePackagePath);
if (!package.has_value()) {
continue;
}
const auto modelDocument = MetaCoreReadModelAssetDocument(*package, reflectionRegistry.GetTypeRegistry());
const auto modelDocument = MetaCoreLoadModelAssetDocumentForRecord(
assetDatabaseService,
packageService,
reflectionRegistry,
record,
modelCache
);
if (!modelDocument.has_value()) {
continue;
}
for (std::size_t index = 0; index < modelDocument->GeneratedMeshAssets.size(); ++index) {
if (modelDocument->GeneratedMeshAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, *modelDocument, "mesh", index};
}
}
for (std::size_t index = 0; index < modelDocument->GeneratedMaterialAssets.size(); ++index) {
if (modelDocument->GeneratedMaterialAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, *modelDocument, "material", index};
}
}
for (std::size_t index = 0; index < modelDocument->GeneratedTextureAssets.size(); ++index) {
if (modelDocument->GeneratedTextureAssets[index].AssetGuid == generatedAssetGuid) {
return MetaCoreResolvedGeneratedAssetDocument{record, *modelDocument, "texture", index};
}
if (auto resolved = findGeneratedAsset(record, *modelDocument)) {
return resolved;
}
}
@ -4498,7 +4534,7 @@ void MetaCorePublishAssetRecordChanges(
materialAsset.Name = absoluteSourcePath.stem().string() + "_Material";
materialAsset.StableImportKey = MetaCoreBuildStableImportKey("material", materialAsset.Name, 0);
materialAsset.Metallic = 0.0F;
materialAsset.Roughness = 0.0F;
materialAsset.Roughness = 1.0F;
document.GeneratedMaterialAssets.push_back(std::move(materialAsset));
document.ImportReport.UsedFallback = true;
@ -4514,6 +4550,102 @@ void MetaCorePublishAssetRecordChanges(
return document;
}
[[nodiscard]] std::optional<MetaCoreModelAssetDocument> MetaCoreLoadModelAssetDocumentForRecord(
const MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIPackageService& packageService,
const MetaCoreIReflectionRegistry& reflectionRegistry,
const MetaCoreAssetRecord& assetRecord,
std::unordered_map<MetaCoreAssetGuid, MetaCoreModelAssetDocument, MetaCoreAssetGuidHasher>* modelCache
) {
if (!assetDatabaseService.HasProject() || assetRecord.Type != "model") {
return std::nullopt;
}
if (modelCache != nullptr && assetRecord.Guid.IsValid()) {
const auto cacheIterator = modelCache->find(assetRecord.Guid);
if (cacheIterator != modelCache->end() &&
(assetRecord.SourceHash == 0 || cacheIterator->second.SourceHash == assetRecord.SourceHash)) {
return cacheIterator->second;
}
}
const auto cacheDocument = [&](const MetaCoreModelAssetDocument& document) {
if (modelCache != nullptr && assetRecord.Guid.IsValid()) {
(*modelCache)[assetRecord.Guid] = document;
}
return document;
};
const std::filesystem::path projectRoot = assetDatabaseService.GetProjectDescriptor().RootPath;
const std::filesystem::path relativePackagePath =
!assetRecord.PackagePath.empty()
? assetRecord.PackagePath
: MetaCoreBuildPackagePathFromSource(!assetRecord.SourcePath.empty() ? assetRecord.SourcePath : assetRecord.RelativePath);
if (!relativePackagePath.empty()) {
const auto package = packageService.ReadPackage(projectRoot / relativePackagePath);
if (package.has_value()) {
if (auto document = MetaCoreReadModelAssetDocument(*package, reflectionRegistry.GetTypeRegistry())) {
return cacheDocument(*document);
}
}
}
std::filesystem::path relativeSourcePath =
!assetRecord.SourcePath.empty() ? assetRecord.SourcePath : assetRecord.RelativePath;
if (relativeSourcePath.empty()) {
return std::nullopt;
}
const std::filesystem::path absoluteSourcePath = projectRoot / relativeSourcePath;
if (!std::filesystem::exists(absoluteSourcePath) || std::filesystem::is_directory(absoluteSourcePath)) {
return std::nullopt;
}
relativeSourcePath = absoluteSourcePath.lexically_relative(projectRoot);
const std::string importerId =
!assetRecord.ImporterId.empty() ? assetRecord.ImporterId : MetaCoreDetectImporterId(absoluteSourcePath);
if (importerId != "GltfModelImporter" && importerId != "ModelImporter") {
return std::nullopt;
}
const std::uint64_t sourceHash = MetaCoreHashFile(absoluteSourcePath).value_or(assetRecord.SourceHash);
MetaCoreModelImportSettingsDocument importSettings = MetaCoreBuildDefaultModelImportSettings();
MetaCoreAssetMetadataDocument metadata;
std::vector<MetaCoreSubAssetMetadata> previousSubAssets;
if (MetaCoreReadMetaJson(MetaCoreBuildMetaPath(absoluteSourcePath), metadata)) {
previousSubAssets = metadata.SubAssets;
if (!metadata.SourcePath.empty()) {
relativeSourcePath = metadata.SourcePath;
}
}
std::optional<MetaCoreModelAssetDocument> document;
if (importerId == "GltfModelImporter") {
std::vector<std::vector<std::byte>> meshPayloads;
document = MetaCoreBuildImportedGltfAssetDocument(
absoluteSourcePath,
relativeSourcePath,
sourceHash,
meshPayloads,
importSettings
);
} else {
document = MetaCoreBuildImportedGenericModelAssetDocument(
absoluteSourcePath,
relativeSourcePath,
sourceHash,
importSettings
);
}
if (document.has_value()) {
MetaCoreApplyStableSubAssetGuids(previousSubAssets, *document);
return cacheDocument(*document);
}
return std::nullopt;
}
[[nodiscard]] MetaCorePackageDocument MetaCoreBuildTypedPackage(
MetaCorePackageType packageType,
const MetaCoreAssetGuid& assetGuid,
@ -5576,6 +5708,7 @@ public:
AssetDatabaseService_.reset();
PackageService_.reset();
ReflectionRegistry_.reset();
ModelCache_.clear();
}
[[nodiscard]] std::optional<MetaCoreModelAssetDocument> LoadModelAsset(
@ -5771,15 +5904,13 @@ std::optional<MetaCoreModelAssetDocument> MetaCoreBuiltinAssetEditingService::Lo
return std::nullopt;
}
const std::filesystem::path relativePackagePath =
!assetRecord->PackagePath.empty() ? assetRecord->PackagePath : assetRecord->RelativePath;
const auto package =
PackageService_->ReadPackage(AssetDatabaseService_->GetProjectDescriptor().RootPath / relativePackagePath);
if (!package.has_value()) {
return std::nullopt;
}
auto doc = MetaCoreReadModelAssetDocument(*package, ReflectionRegistry_->GetTypeRegistry());
auto doc = MetaCoreLoadModelAssetDocumentForRecord(
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
*assetRecord,
&ModelCache_
);
if (doc.has_value()) {
ModelCache_[assetGuid] = *doc;
}
@ -5797,7 +5928,8 @@ std::optional<MetaCoreMeshAssetDocument> MetaCoreBuiltinAssetEditingService::Loa
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
meshGuid
meshGuid,
&ModelCache_
);
if (!resolved.has_value() || resolved->GeneratedKind != "mesh") {
return std::nullopt;
@ -5829,7 +5961,8 @@ std::optional<MetaCoreMaterialAssetDocument> MetaCoreBuiltinAssetEditingService:
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
materialGuid
materialGuid,
&ModelCache_
);
if (resolved.has_value() && resolved->GeneratedKind == "material") {
if (resolved->GeneratedIndex < resolved->Document.GeneratedMaterialAssets.size()) {
@ -5881,7 +6014,8 @@ std::optional<MetaCoreTextureAssetDocument> MetaCoreBuiltinAssetEditingService::
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
textureGuid
textureGuid,
&ModelCache_
);
if (!resolved.has_value() || resolved->GeneratedKind != "texture") {
return std::nullopt;
@ -5952,7 +6086,8 @@ bool MetaCoreBuiltinAssetEditingService::SaveMaterialAsset(
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
materialGuid
materialGuid,
&ModelCache_
);
if (resolved.has_value() && resolved->GeneratedKind == "material") {
if (resolved->GeneratedIndex < resolved->Document.GeneratedMaterialAssets.size()) {
@ -5988,7 +6123,8 @@ std::optional<MetaCoreResolvedGeneratedAssetRecord> MetaCoreBuiltinAssetEditingS
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
assetGuid
assetGuid,
&ModelCache_
);
if (!resolved.has_value()) {
return std::nullopt;
@ -6604,6 +6740,7 @@ public:
AssetDatabaseService_.reset();
PackageService_.reset();
ReflectionRegistry_.reset();
ModelCache_.clear();
}
[[nodiscard]] std::optional<MetaCoreId> CreateGameObject(
@ -6632,6 +6769,7 @@ private:
std::shared_ptr<MetaCoreIAssetDatabaseService> AssetDatabaseService_{};
std::shared_ptr<MetaCoreIPackageService> PackageService_{};
std::shared_ptr<MetaCoreIReflectionRegistry> ReflectionRegistry_{};
std::unordered_map<MetaCoreAssetGuid, MetaCoreModelAssetDocument, MetaCoreAssetGuidHasher> ModelCache_{};
};
class MetaCoreBuiltinPrefabService final : public MetaCoreIPrefabService {
@ -8381,42 +8519,13 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::InstantiateModelAs
return std::nullopt;
}
const std::filesystem::path relativePackagePath =
!assetRecord->PackagePath.empty() ? assetRecord->PackagePath : assetRecord->RelativePath;
const auto package = PackageService_->ReadPackage(AssetDatabaseService_->GetProjectDescriptor().RootPath / relativePackagePath);
std::optional<MetaCoreModelAssetDocument> importedDocument;
if (package.has_value()) {
importedDocument = MetaCoreReadModelAssetDocument(*package, ReflectionRegistry_->GetTypeRegistry());
}
if (!importedDocument.has_value() || importedDocument->Nodes.empty()) {
// 【敏捷开发决策】:如果磁盘上不存在物理的 .mcasset 包文件,直接动态解析原始 model 原始文件,构建文档结构以供实例化
const std::filesystem::path absoluteSourcePath =
AssetDatabaseService_->GetProjectDescriptor().RootPath / assetRecord->RelativePath;
if (std::filesystem::exists(absoluteSourcePath)) {
const std::string extension = absoluteSourcePath.extension().generic_string();
MetaCoreModelImportSettingsDocument defaultSettings{};
if (extension == ".glb" || extension == ".gltf") {
std::vector<std::vector<std::byte>> meshPayloads;
importedDocument = MetaCoreBuildImportedGltfAssetDocument(
absoluteSourcePath,
assetRecord->RelativePath,
assetRecord->SourceHash,
meshPayloads,
defaultSettings
);
} else if (extension == ".obj" || extension == ".fbx") {
importedDocument = MetaCoreBuildImportedGenericModelAssetDocument(
absoluteSourcePath,
assetRecord->RelativePath,
assetRecord->SourceHash,
defaultSettings
);
}
}
}
std::optional<MetaCoreModelAssetDocument> importedDocument = MetaCoreLoadModelAssetDocumentForRecord(
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
*assetRecord,
&ModelCache_
);
if (!importedDocument.has_value() || importedDocument->Nodes.empty()) {
return std::nullopt;

View File

@ -11,6 +11,8 @@
#include <string>
#include <cstddef>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <optional>
@ -18,6 +20,11 @@
namespace MetaCore {
void MetaCoreGltfTrace(const char* message) {
const char* traceEnv = std::getenv("METACORE_GLTF_IMPORT_TRACE");
if (traceEnv == nullptr || traceEnv[0] == '\0' || std::strcmp(traceEnv, "0") == 0) {
return;
}
FILE* file = nullptr;
#if defined(_MSC_VER)
if (fopen_s(&file, "editor.crash.log", "a") == 0 && file != nullptr) {
@ -439,7 +446,7 @@ struct MetaCoreGltfMaterialScalarPresence {
genMat.Name = "PumpMaterial";
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "PumpMaterial", 0);
genMat.Metallic = 0.0F;
genMat.Roughness = 0.0F;
genMat.Roughness = 1.0F;
genMat.BaseColorTexture = genTex.AssetGuid;
genMat.BaseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
document.GeneratedMaterialAssets.push_back(genMat);
@ -517,7 +524,7 @@ struct MetaCoreGltfMaterialScalarPresence {
genMat.Name = "ValveMaterial";
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "ValveMaterial", 0);
genMat.Metallic = 0.0F;
genMat.Roughness = 0.0F;
genMat.Roughness = 1.0F;
genMat.BaseColorTexture = genTex.AssetGuid;
document.GeneratedMaterialAssets.push_back(genMat);
@ -594,7 +601,7 @@ struct MetaCoreGltfMaterialScalarPresence {
genMat.Name = "EmbeddedTextureMaterial";
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "EmbeddedTextureMaterial", 0);
genMat.Metallic = 0.0F;
genMat.Roughness = 0.0F;
genMat.Roughness = 1.0F;
genMat.BaseColorTexture = genTex.AssetGuid;
document.GeneratedMaterialAssets.push_back(genMat);
@ -679,7 +686,7 @@ struct MetaCoreGltfMaterialScalarPresence {
genMat.Name = "Tank_Material";
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "Tank_Material", 0);
genMat.Metallic = 0.0F;
genMat.Roughness = 0.0F;
genMat.Roughness = 1.0F;
document.GeneratedMaterialAssets.push_back(genMat);
// 3. Node
@ -751,7 +758,7 @@ struct MetaCoreGltfMaterialScalarPresence {
materialAsset.Name = absoluteSourcePath.stem().string() + "_Material";
materialAsset.StableImportKey = MetaCoreBuildStableImportKey("material", materialAsset.Name, 0);
materialAsset.Metallic = 0.0F;
materialAsset.Roughness = 0.0F;
materialAsset.Roughness = 1.0F;
document.GeneratedMaterialAssets.push_back(std::move(materialAsset));
};
@ -818,7 +825,7 @@ struct MetaCoreGltfMaterialScalarPresence {
genMat.AlphaCutoff = mat.alpha_cutoff;
genMat.AlphaMode = mat.alpha_mode == cgltf_alpha_mode_blend ? MetaCoreMaterialAlphaMode::Blend : (mat.alpha_mode == cgltf_alpha_mode_mask ? MetaCoreMaterialAlphaMode::Mask : MetaCoreMaterialAlphaMode::Opaque);
genMat.Metallic = 0.0F;
genMat.Roughness = 0.0F;
genMat.Roughness = 1.0F;
if (mat.has_pbr_metallic_roughness) {
genMat.BaseColor = glm::vec3(mat.pbr_metallic_roughness.base_color_factor[0], mat.pbr_metallic_roughness.base_color_factor[1], mat.pbr_metallic_roughness.base_color_factor[2]);

View File

@ -25,11 +25,16 @@
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/TextureSampler.h>
#include <filament/Skybox.h>
#include <filament/IndirectLight.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <gltfio/MaterialProvider.h>
#include <gltfio/TextureProvider.h>
#include <ktxreader/Ktx1Reader.h>
#include <utils/Entity.h>
#include <utils/EntityManager.h>
@ -46,8 +51,11 @@
#include <iostream>
#include <fstream>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <cstdint>
#include <cmath>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <map>
@ -114,9 +122,95 @@ void MetaCoreFilamentDebugLog(const std::string& message) {
std::cerr << "[MetaCoreDebug][Filament] " << message << std::endl;
}
[[nodiscard]] bool MetaCorePreserveGltfBlendAlphaMode() {
const char* preserveBlend = std::getenv("METACORE_GLTF_PRESERVE_BLEND_ALPHA");
if (preserveBlend == nullptr) {
return false;
}
return std::strcmp(preserveBlend, "1") == 0 ||
std::strcmp(preserveBlend, "true") == 0 ||
std::strcmp(preserveBlend, "TRUE") == 0;
}
class MetaCoreGltfMaterialProvider final : public filament::gltfio::MaterialProvider {
public:
explicit MetaCoreGltfMaterialProvider(filament::gltfio::MaterialProvider* innerProvider)
: InnerProvider_(innerProvider) {}
~MetaCoreGltfMaterialProvider() override {
delete InnerProvider_;
InnerProvider_ = nullptr;
}
filament::MaterialInstance* createMaterialInstance(
filament::gltfio::MaterialKey* config,
filament::gltfio::UvMap* uvmap,
const char* label = "material",
const char* extras = nullptr
) override {
NormalizeAlphaMode(config, label);
return InnerProvider_ != nullptr
? InnerProvider_->createMaterialInstance(config, uvmap, label, extras)
: nullptr;
}
filament::Material* getMaterial(
filament::gltfio::MaterialKey* config,
filament::gltfio::UvMap* uvmap,
const char* label = "material"
) override {
NormalizeAlphaMode(config, label);
return InnerProvider_ != nullptr ? InnerProvider_->getMaterial(config, uvmap, label) : nullptr;
}
const filament::Material* const* getMaterials() const noexcept override {
return InnerProvider_ != nullptr ? InnerProvider_->getMaterials() : nullptr;
}
size_t getMaterialsCount() const noexcept override {
return InnerProvider_ != nullptr ? InnerProvider_->getMaterialsCount() : 0;
}
void destroyMaterials() override {
if (InnerProvider_ != nullptr) {
InnerProvider_->destroyMaterials();
}
}
bool needsDummyData(filament::VertexAttribute attrib) const noexcept override {
return InnerProvider_ != nullptr && InnerProvider_->needsDummyData(attrib);
}
private:
void NormalizeAlphaMode(filament::gltfio::MaterialKey* config, const char* label) {
if (config == nullptr ||
MetaCorePreserveGltfBlendAlphaMode() ||
config->alphaMode != filament::gltfio::AlphaMode::BLEND) {
return;
}
config->alphaMode = filament::gltfio::AlphaMode::MASK;
if (!LoggedBlendAsMask_) {
MetaCoreFilamentDebugLog(
"GLTF alphaMode=BLEND is rendered as MASK by default for stable foliage depth. "
"Set METACORE_GLTF_PRESERVE_BLEND_ALPHA=1 to keep GLTF blend materials."
);
LoggedBlendAsMask_ = true;
}
if (label != nullptr && label[0] != '\0') {
MetaCoreFilamentDebugLog(std::string("GLTF material alphaMode adjusted to MASK label=\"") + label + "\"");
}
}
filament::gltfio::MaterialProvider* InnerProvider_ = nullptr;
bool LoggedBlendAsMask_ = false;
};
} // namespace
class MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridgeImpl {
using EntityChildrenMap = std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher>;
public:
MetaCoreFilamentSceneBridgeImpl() = default;
@ -152,6 +246,7 @@ public:
// 创建场景
Scene_ = Engine_->createScene();
LoadDefaultEnvironmentLighting();
// 创建默认灯光
Light_ = utils::EntityManager::get().create();
@ -172,6 +267,7 @@ public:
View_ = Engine_->createView();
View_->setScene(Scene_);
View_->setCamera(Camera_);
ConfigureSceneViewRenderState();
if (offscreen) {
// 创建 UI 视图
@ -210,16 +306,21 @@ public:
// 4. 设置到 View
View_->setRenderTarget(RenderTarget_);
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
ConfigureSceneViewRenderState();
} else {
auto [width, height] = window.GetFramebufferSize();
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
ConfigureSceneViewRenderState();
}
// 确保开启后处理为了HDR
View_->setPostProcessingEnabled(true);
// 初始化 gltfio
MaterialProvider_ = filament::gltfio::createJitShaderProvider(Engine_);
MaterialProvider_ = new MetaCoreGltfMaterialProvider(
filament::gltfio::createJitShaderProvider(Engine_)
);
StbTextureProvider_ = filament::gltfio::createStbProvider(Engine_);
NameManager_ = new utils::NameComponentManager(utils::EntityManager::get());
AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ });
RuntimeAssetSubscriptionId_ = MetaCoreRuntimeAssetRegistry::Get().Subscribe(
@ -310,6 +411,8 @@ public:
}
AssetDuplicatedMaterials_.clear();
DestroyMaterialTextureCache();
DestroyEnvironmentLighting();
DestroySolidColorSkybox();
for (auto& [id, entity] : SceneLightEntities_) {
if (Scene_) {
@ -333,6 +436,10 @@ public:
delete MaterialProvider_;
MaterialProvider_ = nullptr;
}
if (StbTextureProvider_) {
delete StbTextureProvider_;
StbTextureProvider_ = nullptr;
}
DestroyDeferredRenderTargetResources(true);
@ -399,6 +506,7 @@ public:
DestroyMaterialTextureCache();
}
ProjectRootPath_ = projectRootPath;
LoadDefaultEnvironmentLighting();
}
void DestroyMaterialTextureCache() {
@ -413,6 +521,196 @@ public:
MaterialTextureCache_.clear();
}
[[nodiscard]] static std::optional<std::vector<std::uint8_t>> ReadBinaryFile(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary | std::ios::ate);
if (!input.is_open()) {
return std::nullopt;
}
const std::streamsize fileSize = input.tellg();
if (fileSize <= 0) {
return std::nullopt;
}
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(fileSize));
input.seekg(0, std::ios::beg);
if (!input.read(reinterpret_cast<char*>(bytes.data()), fileSize)) {
return std::nullopt;
}
return bytes;
}
[[nodiscard]] std::filesystem::path ResolveDefaultEnvironmentPath(std::string_view filename) const {
const std::array<std::filesystem::path, 4> candidates{
std::filesystem::path("third_party") / "filament_installed" / "bin" / "assets" / "ibl" / "lightroom_14b" / filename,
std::filesystem::path("third_party") / "filament_installed.before-linux-sdk" / "bin" / "assets" / "ibl" / "lightroom_14b" / filename,
std::filesystem::path("build_filament") / "samples" / "assets" / "ibl" / "lightroom_14b" / filename,
std::filesystem::path("build_filament") / "libs" / "ktxreader" / filename
};
const std::filesystem::path cwd = std::filesystem::current_path();
for (const std::filesystem::path& relativePath : candidates) {
const std::filesystem::path absolutePath = (cwd / relativePath).lexically_normal();
if (std::filesystem::exists(absolutePath)) {
return absolutePath;
}
const std::filesystem::path fromProjectRoot = (ProjectRootPath_.empty()
? std::filesystem::path{}
: ProjectRootPath_ / ".." / relativePath).lexically_normal();
if (!fromProjectRoot.empty() && std::filesystem::exists(fromProjectRoot)) {
return fromProjectRoot;
}
}
return {};
}
[[nodiscard]] filament::Texture* LoadKtx1Texture(
const std::filesystem::path& path,
bool srgb,
filament::math::float3* outSphericalHarmonics = nullptr
) const {
if (Engine_ == nullptr || path.empty()) {
return nullptr;
}
const auto bytes = ReadBinaryFile(path);
if (!bytes.has_value()) {
return nullptr;
}
auto bundle = std::make_unique<ktxreader::Ktx1Bundle>(
bytes->data(),
static_cast<std::uint32_t>(bytes->size())
);
if (outSphericalHarmonics != nullptr) {
if (!bundle->getSphericalHarmonics(outSphericalHarmonics)) {
std::fill(outSphericalHarmonics, outSphericalHarmonics + 9, filament::math::float3{0.0f, 0.0f, 0.0f});
}
}
return ktxreader::Ktx1Reader::createTexture(Engine_, bundle.release(), srgb);
}
void LoadDefaultEnvironmentLighting() {
if (Engine_ == nullptr || Scene_ == nullptr || EnvironmentLoaded_) {
return;
}
const std::filesystem::path iblPath = ResolveDefaultEnvironmentPath("lightroom_14b_ibl.ktx");
const std::filesystem::path skyboxPath = ResolveDefaultEnvironmentPath("lightroom_14b_skybox.ktx");
if (iblPath.empty() || skyboxPath.empty()) {
MetaCoreFilamentDebugLog("Default IBL assets not found; falling back to solid clear color");
return;
}
filament::math::float3 sphericalHarmonics[9]{};
EnvironmentIblTexture_ = LoadKtx1Texture(iblPath, false, sphericalHarmonics);
EnvironmentSkyboxTexture_ = LoadKtx1Texture(skyboxPath, false);
if (EnvironmentIblTexture_ == nullptr || EnvironmentSkyboxTexture_ == nullptr) {
MetaCoreFilamentDebugLog("Default IBL KTX load failed");
DestroyEnvironmentLighting();
return;
}
EnvironmentIndirectLight_ = filament::IndirectLight::Builder()
.reflections(EnvironmentIblTexture_)
.irradiance(3, sphericalHarmonics)
.intensity(EnvironmentIntensity_)
.build(*Engine_);
EnvironmentSkybox_ = filament::Skybox::Builder()
.environment(EnvironmentSkyboxTexture_)
.intensity(EnvironmentIntensity_)
.build(*Engine_);
if (EnvironmentIndirectLight_ == nullptr || EnvironmentSkybox_ == nullptr) {
MetaCoreFilamentDebugLog("Default IBL object build failed");
DestroyEnvironmentLighting();
return;
}
Scene_->setIndirectLight(EnvironmentIndirectLight_);
Scene_->setSkybox(EnvironmentSkybox_);
EnvironmentLoaded_ = true;
MetaCoreFilamentDebugLog(
"Default IBL loaded ibl=\"" + iblPath.generic_string() +
"\" skybox=\"" + skyboxPath.generic_string() + "\""
);
}
void DestroyEnvironmentLighting() {
if (Scene_ != nullptr) {
if (Scene_->getIndirectLight() == EnvironmentIndirectLight_) {
Scene_->setIndirectLight(nullptr);
}
if (Scene_->getSkybox() == EnvironmentSkybox_) {
Scene_->setSkybox(nullptr);
}
}
if (Engine_ != nullptr) {
Engine_->flushAndWait();
if (EnvironmentSkybox_ != nullptr) {
Engine_->destroy(EnvironmentSkybox_);
EnvironmentSkybox_ = nullptr;
}
if (EnvironmentIndirectLight_ != nullptr) {
Engine_->destroy(EnvironmentIndirectLight_);
EnvironmentIndirectLight_ = nullptr;
}
if (EnvironmentSkyboxTexture_ != nullptr) {
Engine_->destroy(EnvironmentSkyboxTexture_);
EnvironmentSkyboxTexture_ = nullptr;
}
if (EnvironmentIblTexture_ != nullptr) {
Engine_->destroy(EnvironmentIblTexture_);
EnvironmentIblTexture_ = nullptr;
}
}
EnvironmentLoaded_ = false;
}
[[nodiscard]] filament::Skybox* GetOrCreateSolidColorSkybox(const MetaCoreSceneView& sceneView) {
if (Engine_ == nullptr) {
return nullptr;
}
const filament::math::float4 color{
sceneView.BackgroundColor.x,
sceneView.BackgroundColor.y,
sceneView.BackgroundColor.z,
sceneView.BackgroundAlpha
};
constexpr float epsilon = 0.0001f;
const bool sameColor =
std::abs(SolidColorSkyboxColor_.x - color.x) <= epsilon &&
std::abs(SolidColorSkyboxColor_.y - color.y) <= epsilon &&
std::abs(SolidColorSkyboxColor_.z - color.z) <= epsilon &&
std::abs(SolidColorSkyboxColor_.w - color.w) <= epsilon;
if (SolidColorSkybox_ != nullptr && sameColor) {
return SolidColorSkybox_;
}
DestroySolidColorSkybox();
SolidColorSkybox_ = filament::Skybox::Builder()
.color(color)
.build(*Engine_);
SolidColorSkyboxColor_ = color;
return SolidColorSkybox_;
}
void DestroySolidColorSkybox() {
if (Scene_ != nullptr && Scene_->getSkybox() == SolidColorSkybox_) {
Scene_->setSkybox(nullptr);
}
if (Engine_ != nullptr && SolidColorSkybox_ != nullptr) {
Engine_->destroy(SolidColorSkybox_);
SolidColorSkybox_ = nullptr;
}
SolidColorSkyboxColor_ = filament::math::float4{-1.0f, -1.0f, -1.0f, -1.0f};
}
[[nodiscard]] std::filesystem::path ResolveMaterialTexturePath(
const std::string& texturePath,
const std::string& sourceModelPath
@ -861,9 +1159,20 @@ public:
" entities=" + std::to_string(asset->getEntityCount())
);
// 加载贴图等资源
filament::gltfio::ResourceLoader resourceLoader({ Engine_ });
resourceLoader.loadResources(asset);
// 加载贴图等资源。gltfio 不会默认解码 PNG/JPEG必须注册 TextureProvider。
const std::string gltfPath = absolutePath.string();
filament::gltfio::ResourceConfiguration resourceConfig{};
resourceConfig.engine = Engine_;
resourceConfig.gltfPath = gltfPath.c_str();
resourceConfig.normalizeSkinningWeights = true;
filament::gltfio::ResourceLoader resourceLoader(resourceConfig);
if (StbTextureProvider_) {
resourceLoader.addTextureProvider("image/png", StbTextureProvider_);
resourceLoader.addTextureProvider("image/jpeg", StbTextureProvider_);
}
if (!resourceLoader.loadResources(asset)) {
std::cerr << "Warning: GLTF resources were not fully loaded for: " << absolutePath << std::endl;
}
// 添加到场景
Scene_->addEntities(asset->getEntities(), asset->getEntityCount());
@ -1155,6 +1464,9 @@ public:
filament::Texture* texture,
const filament::TextureSampler& sampler
) -> bool {
if (texture == nullptr) {
return false;
}
const filament::Material* material = materialInstance.getMaterial();
if (material == nullptr || !material->hasParameter(name)) {
return false;
@ -1165,6 +1477,8 @@ public:
// 5. 同步所有存活网格的材质预览属性。MaterialInstance 参数必须在渲染前更新。
auto& rm = Engine_->getRenderableManager();
std::unordered_map<filament::gltfio::FilamentAsset*, EntityChildrenMap> assetChildMapCache;
std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher> materialTargetCache;
const filament::TextureSampler materialSampler(
filament::TextureSampler::MinFilter::LINEAR,
filament::TextureSampler::MagFilter::LINEAR,
@ -1175,8 +1489,29 @@ public:
if (itEntity == ObjectToFilamentEntity_.end()) {
continue;
}
std::vector<utils::Entity> materialTargetEntities;
CollectRenderableDescendants(itEntity->second.first, itEntity->second.second, materialTargetEntities);
const auto materialTargetCacheIterator = materialTargetCache.find(itEntity->second.second);
if (materialTargetCacheIterator == materialTargetCache.end()) {
std::vector<utils::Entity> materialTargetEntities;
if (itEntity->second.first != nullptr) {
auto childMapIterator = assetChildMapCache.find(itEntity->second.first);
if (childMapIterator == assetChildMapCache.end()) {
childMapIterator = assetChildMapCache.emplace(
itEntity->second.first,
BuildDirectParentToChildrenMap(itEntity->second.first)
).first;
}
CollectRenderableDescendantsFromChildMap(
itEntity->second.second,
childMapIterator->second,
materialTargetEntities
);
} else {
CollectRenderableDescendants(itEntity->second.first, itEntity->second.second, materialTargetEntities);
}
materialTargetCache.emplace(itEntity->second.second, std::move(materialTargetEntities));
}
const std::vector<utils::Entity>& materialTargetEntities = materialTargetCache.find(itEntity->second.second)->second;
if (materialTargetEntities.empty()) {
continue;
}
@ -1236,7 +1571,8 @@ public:
renderable.EmissiveColor.z
}
);
if (renderable.AlphaMode == MetaCoreMeshAlphaMode::Mask) {
if (renderable.AlphaMode == MetaCoreMeshAlphaMode::Mask ||
renderable.AlphaMode == MetaCoreMeshAlphaMode::Blend) {
setFloatParameterIfPresent(*matInst, "alphaCutoff", renderable.AlphaCutoff);
}
setTextureParameterIfPresent(*matInst, "baseColorMap", baseColorTexture, materialSampler);
@ -1257,6 +1593,15 @@ public:
void ApplySceneView(const MetaCoreSceneView& sceneView) {
if (!Camera_ || !View_) return;
CurrentSceneView_ = sceneView;
if (Scene_ != nullptr) {
filament::Skybox* skybox = nullptr;
if (sceneView.ClearFlags == MetaCoreCameraClearFlags::Skybox) {
skybox = EnvironmentSkybox_ != nullptr ? EnvironmentSkybox_ : GetOrCreateSolidColorSkybox(sceneView);
} else if (sceneView.ClearFlags == MetaCoreCameraClearFlags::SolidColor) {
skybox = GetOrCreateSolidColorSkybox(sceneView);
}
Scene_->setSkybox(skybox);
}
// 1. 视图矩阵同步
const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
@ -1413,6 +1758,7 @@ public:
View_->setRenderTarget(RenderTarget_);
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
ConfigureSceneViewRenderState();
DebugRenderTargetState("Resize after recreate resources");
}
@ -1624,6 +1970,65 @@ private:
return parentToChildren;
}
EntityChildrenMap BuildDirectParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const {
EntityChildrenMap parentToChildren;
if (!asset || Engine_ == nullptr) {
return parentToChildren;
}
auto& tm = Engine_->getTransformManager();
const utils::Entity* entities = asset->getEntities();
const size_t entityCount = asset->getEntityCount();
for (size_t i = 0; i < entityCount; ++i) {
const utils::Entity entity = entities[i];
if (entity.isNull()) {
continue;
}
const auto instance = tm.getInstance(entity);
if (!instance) {
continue;
}
const utils::Entity parent = tm.getParent(instance);
if (!parent.isNull()) {
parentToChildren[parent].push_back(entity);
}
}
return parentToChildren;
}
void CollectRenderableDescendantsFromChildMap(
utils::Entity rootEntity,
const EntityChildrenMap& parentToChildren,
std::vector<utils::Entity>& outEntities
) const {
if (!Engine_ || rootEntity.isNull()) {
return;
}
auto& rm = Engine_->getRenderableManager();
std::vector<utils::Entity> pending{rootEntity};
std::unordered_set<utils::Entity, utils::Entity::Hasher> visited;
while (!pending.empty()) {
const utils::Entity entity = pending.back();
pending.pop_back();
if (entity.isNull() || !visited.insert(entity).second) {
continue;
}
if (rm.getInstance(entity)) {
outEntities.push_back(entity);
}
const auto childrenIterator = parentToChildren.find(entity);
if (childrenIterator == parentToChildren.end()) {
continue;
}
pending.insert(pending.end(), childrenIterator->second.begin(), childrenIterator->second.end());
}
}
void CollectRenderableDescendants(
filament::gltfio::FilamentAsset* asset,
utils::Entity rootEntity,
@ -1788,6 +2193,17 @@ private:
}
}
void ConfigureSceneViewRenderState() {
if (View_ == nullptr) {
return;
}
View_->setBlendMode(filament::View::BlendMode::OPAQUE);
for (uint8_t channel = 0; channel < 8; ++channel) {
View_->setChannelDepthClearEnabled(channel, true);
}
}
void DebugRenderTargetState(const char* event) const {
std::ostringstream stream;
stream
@ -1809,9 +2225,18 @@ private:
filament::Scene* Scene_ = nullptr;
filament::Camera* Camera_ = nullptr;
filament::View* View_ = nullptr;
filament::Texture* EnvironmentIblTexture_ = nullptr;
filament::Texture* EnvironmentSkyboxTexture_ = nullptr;
filament::IndirectLight* EnvironmentIndirectLight_ = nullptr;
filament::Skybox* EnvironmentSkybox_ = nullptr;
filament::Skybox* SolidColorSkybox_ = nullptr;
filament::math::float4 SolidColorSkyboxColor_{-1.0f, -1.0f, -1.0f, -1.0f};
float EnvironmentIntensity_ = 30000.0f;
bool EnvironmentLoaded_ = false;
filament::gltfio::AssetLoader* AssetLoader_ = nullptr;
filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr;
filament::gltfio::TextureProvider* StbTextureProvider_ = nullptr;
utils::NameComponentManager* NameManager_ = nullptr;
MetaCoreSceneRenderSync RenderSync_{};

View File

@ -23,6 +23,7 @@ function(metacore_use_filament target_name)
"${METACORE_FILAMENT_LIB_DIR}/bluegl.lib"
"${METACORE_FILAMENT_LIB_DIR}/gltfio.lib"
"${METACORE_FILAMENT_LIB_DIR}/gltfio_core.lib"
"${METACORE_FILAMENT_LIB_DIR}/ktxreader.lib"
"${METACORE_FILAMENT_LIB_DIR}/abseil.lib"
"${METACORE_FILAMENT_LIB_DIR}/perfetto.lib"
"${METACORE_FILAMENT_LIB_DIR}/dracodec.lib"

View File

@ -1880,13 +1880,19 @@ void MetaCoreTestInstantiateImportedModelAssetIntoScene() {
);
const auto assetDatabase = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>();
const auto assetEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetEditingService>();
const auto sceneEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreISceneEditingService>();
MetaCoreExpect(assetDatabase != nullptr, "应解析到 AssetDatabaseService");
MetaCoreExpect(assetEditingService != nullptr, "应解析到 AssetEditingService");
MetaCoreExpect(sceneEditingService != nullptr, "应解析到 SceneEditingService");
const auto modelRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Pump.gltf");
MetaCoreExpect(modelRecord.has_value(), "应能找到导入后的模型资源");
const auto sourceOnlyModelDocument = assetEditingService->LoadModelAsset(modelRecord->Guid);
MetaCoreExpect(sourceOnlyModelDocument.has_value(), "开发期无模型包文件时仍应能读取模型资产文档");
MetaCoreExpect(!sourceOnlyModelDocument->GeneratedMaterialAssets.empty(), "模型资产文档应包含生成材质");
const auto instantiatedRootId = sceneEditingService->InstantiateModelAsset(editorContext, modelRecord->Guid, std::nullopt);
MetaCoreExpect(instantiatedRootId.has_value(), "应能将导入模型实例化到场景");
@ -1909,9 +1915,24 @@ void MetaCoreTestInstantiateImportedModelAssetIntoScene() {
);
MetaCoreExpect(instantiatedRoot.GetComponent<MetaCore::MetaCoreMeshRendererComponent>().BaseColor.x == 1.0F, "根节点应继承材质颜色通道数值");
MetaCoreExpect(std::abs(instantiatedRoot.GetComponent<MetaCore::MetaCoreMeshRendererComponent>().Metallic) < 0.0001F, "缺少金属度的导入材质 Metallic 应默认为 0");
MetaCoreExpect(std::abs(instantiatedRoot.GetComponent<MetaCore::MetaCoreMeshRendererComponent>().Roughness) < 0.0001F, "缺少粗糙度的导入材质 Roughness 应默认为 0");
MetaCoreExpect(std::abs(instantiatedRoot.GetComponent<MetaCore::MetaCoreMeshRendererComponent>().Roughness - 1.0F) < 0.0001F, "缺少粗糙度的导入材质 Roughness 应默认为 1");
MetaCoreExpect(editorContext.GetActiveObjectId() == *instantiatedRootId, "实例化后应正确选中新对象");
const MetaCore::MetaCoreAssetGuid generatedMaterialGuid =
instantiatedRoot.GetComponent<MetaCore::MetaCoreMeshRendererComponent>().MaterialAssetGuids.front();
auto generatedMaterial = assetEditingService->LoadMaterialAsset(generatedMaterialGuid);
MetaCoreExpect(generatedMaterial.has_value(), "源模型生成材质应可作为材质资源读取");
generatedMaterial->BaseColor = glm::vec3(0.25F, 0.5F, 0.75F);
generatedMaterial->Metallic = 0.35F;
generatedMaterial->Roughness = 0.8F;
MetaCoreExpect(assetEditingService->SaveMaterialAsset(generatedMaterialGuid, *generatedMaterial), "源模型生成材质应可保存编辑结果");
const auto reloadedGeneratedMaterial = assetEditingService->LoadMaterialAsset(generatedMaterialGuid);
MetaCoreExpect(reloadedGeneratedMaterial.has_value(), "保存后应能重新读取源模型生成材质");
MetaCoreExpectVec3Near(reloadedGeneratedMaterial->BaseColor, glm::vec3(0.25F, 0.5F, 0.75F), "生成材质基础色保存后应保持");
MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Metallic - 0.35F) < 0.0001F, "生成材质 Metallic 保存后应保持");
MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Roughness - 0.8F) < 0.0001F, "生成材质 Roughness 保存后应保持");
coreServicesModule->Shutdown(moduleRegistry);
moduleRegistry.ShutdownServices();
_putenv_s("METACORE_PROJECT_PATH", "");
@ -2007,7 +2028,7 @@ void MetaCoreTestInstantiateRealGltfHierarchyIntoScene() {
importedChild.GetComponent<MetaCore::MetaCoreMeshRendererComponent>();
MetaCoreExpectVec3Near(childMeshRenderer.BaseColor, glm::vec3(0.2F, 0.4F, 0.6F), "真实 GLTF 子节点应继承导入材质基础色");
MetaCoreExpect(std::abs(childMeshRenderer.Metallic) < 0.0001F, "真实 GLTF 材质缺少 metallicFactor 时 Metallic 应默认为 0");
MetaCoreExpect(std::abs(childMeshRenderer.Roughness) < 0.0001F, "真实 GLTF 材质缺少 roughnessFactor 时 Roughness 应默认为 0");
MetaCoreExpect(std::abs(childMeshRenderer.Roughness - 1.0F) < 0.0001F, "真实 GLTF 材质缺少 roughnessFactor 时 Roughness 应默认为 1");
const std::vector<MetaCore::MetaCoreId> hierarchyOrder = scene.BuildHierarchyPreorder();
MetaCoreExpect(hierarchyOrder.size() == 3, "真实 GLTF 层级应进入层级树遍历结果");