材质更新

This commit is contained in:
Rowland 2026-06-16 09:50:30 +08:00
parent 97b704428a
commit 0df9b93ce2
18 changed files with 1688 additions and 338 deletions

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "fc3fd465-4f39-44af-907a-4fc0494c2502",
"BaseColor": [
1.0,
0.8078431487083435,
0.5843137502670288
],
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"MetallicRoughnessTexture": "",
"Name": "Material 1",
"NormalTexture": "",
"Roughness": 0.5690000057220459,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "fc3fd465-4f39-44af-907a-4fc0494c2502",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 1.mcmaterial.json",
"source_hash": 1388934689574133717,
"source_path": "Assets/Materials/Material 1.mcmaterial.json"
}

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "579d1aa2-baae-4b56-9b05-86f9d2e9a2ff",
"BaseColor": [
0.7137255072593689,
0.95686274766922,
0.7647058963775635
],
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"MetallicRoughnessTexture": "",
"Name": "Material 2",
"NormalTexture": "",
"Roughness": 0.4129999876022339,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "579d1aa2-baae-4b56-9b05-86f9d2e9a2ff",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 2.mcmaterial.json",
"source_hash": 12206391660553642631,
"source_path": "Assets/Materials/Material 2.mcmaterial.json"
}

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "fbe8b648-5368-4af6-b822-d2573ed5f194",
"BaseColor": [
0.545098066329956,
0.30588236451148987,
0.5647059082984924
],
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.0,
0.27843138575553894,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"MetallicRoughnessTexture": "",
"Name": "Material 3",
"NormalTexture": "",
"Roughness": 0.781000018119812,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "fbe8b648-5368-4af6-b822-d2573ed5f194",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 3.mcmaterial.json",
"source_hash": 7449433069631146521,
"source_path": "Assets/Materials/Material 3.mcmaterial.json"
}

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "a23b9298-b98c-47ac-a2ad-3c9dac65c5af",
"BaseColor": [
0.7647058963775635,
0.8392156958580017,
0.7882353067398071
],
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.6890000104904175,
"MetallicRoughnessTexture": "",
"Name": "Material 4",
"NormalTexture": "",
"Roughness": 0.5450000166893005,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "a23b9298-b98c-47ac-a2ad-3c9dac65c5af",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 4.mcmaterial.json",
"source_hash": 12594992031322235982,
"source_path": "Assets/Materials/Material 4.mcmaterial.json"
}

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "39f65f8e-cdac-43fb-9762-e6c544547a71",
"BaseColor": [
0.8705882430076599,
0.7921568751335144,
0.8627451062202454
],
"BaseColorTexture": "",
"DoubleSided": false,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"MetallicRoughnessTexture": "",
"Name": "Material 5",
"NormalTexture": "",
"Roughness": 1.0,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "39f65f8e-cdac-43fb-9762-e6c544547a71",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material 5.mcmaterial.json",
"source_hash": 10101379247114018828,
"source_path": "Assets/Materials/Material 5.mcmaterial.json"
}

View File

@ -0,0 +1,26 @@
{
"AlphaCutoff": 0.4129999876022339,
"AlphaMode": 0,
"AoTexture": "",
"AssetGuid": "5edbdbc8-4a0d-4991-82a7-782de9ed4e3a",
"BaseColor": [
0.9999995231628418,
0.0,
1.0
],
"BaseColorTexture": "",
"DoubleSided": true,
"EmissiveColor": [
0.12941177189350128,
0.0,
0.0
],
"EmissiveTexture": "",
"Metallic": 0.0,
"MetallicRoughnessTexture": "",
"Name": "Material",
"NormalTexture": "",
"Roughness": 1.0,
"ShaderModel": 0,
"StableImportKey": ""
}

View File

@ -0,0 +1,8 @@
{
"asset_type": "material",
"guid": "5edbdbc8-4a0d-4991-82a7-782de9ed4e3a",
"importer_id": "MaterialImporter",
"package_path": "Assets/Materials/Material.mcmaterial.json",
"source_hash": 7326866736359587430,
"source_path": "Assets/Materials/Material.mcmaterial.json"
}

View File

@ -869,12 +869,33 @@ template <typename TGeneratedAsset, typename TLabelBuilder>
}
[[nodiscard]] std::vector<MetaCoreGeneratedAssetChoice> MetaCoreCollectMaterialAssetChoices(MetaCoreEditorContext& editorContext) {
return MetaCoreCollectGeneratedAssetChoices<MetaCoreMaterialAssetDocument>(
std::vector<MetaCoreGeneratedAssetChoice> choices;
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
for (const MetaCoreAssetRecord& record : assetDatabaseService->GetAssetRegistry()) {
if (record.Type != "material" || !record.Guid.IsValid()) {
continue;
}
choices.push_back(MetaCoreGeneratedAssetChoice{
record.Guid,
record.RelativePath.filename().string() + " / Material"
});
}
}
auto generatedChoices = MetaCoreCollectGeneratedAssetChoices<MetaCoreMaterialAssetDocument>(
editorContext,
[](const MetaCoreAssetRecord& record, const MetaCoreMaterialAssetDocument& asset) {
return record.RelativePath.filename().string() + " / Material / " + asset.Name;
}
);
choices.insert(choices.end(), generatedChoices.begin(), generatedChoices.end());
std::sort(choices.begin(), choices.end(), [](const MetaCoreGeneratedAssetChoice& lhs, const MetaCoreGeneratedAssetChoice& rhs) {
return lhs.Label < rhs.Label;
});
return choices;
}
[[nodiscard]] std::vector<MetaCoreGeneratedAssetChoice> MetaCoreCollectTextureAssetChoices(MetaCoreEditorContext& editorContext) {
@ -1047,73 +1068,249 @@ void MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer(
assignTextureReference(materialAsset.AoTexture, meshRenderer.AoTextureGuid, meshRenderer.AoTexturePath);
}
[[nodiscard]] std::string MetaCoreNormalizeModelRelativePath(std::string path) {
if (path.empty()) {
return {};
}
std::replace(path.begin(), path.end(), '\\', '/');
return std::filesystem::path(path).lexically_normal().generic_string();
}
[[nodiscard]] bool MetaCoreMaterialGuidListIsEmpty(const std::vector<MetaCoreAssetGuid>& materialGuids) {
return std::all_of(materialGuids.begin(), materialGuids.end(), [](const MetaCoreAssetGuid& guid) {
return !guid.IsValid();
});
}
[[nodiscard]] std::string MetaCoreResolveSceneObjectModelPath(
const MetaCoreScene& scene,
MetaCoreGameObject object
) {
MetaCoreGameObject current = object;
while (current) {
if (current.HasComponent<MetaCoreMeshRendererComponent>()) {
const auto& meshRenderer = current.GetComponent<MetaCoreMeshRendererComponent>();
if (!meshRenderer.SourceModelPath.empty()) {
return MetaCoreNormalizeModelRelativePath(meshRenderer.SourceModelPath);
}
}
if (current.HasComponent<MetaCoreModelRootTag>()) {
const auto& tag = current.GetComponent<MetaCoreModelRootTag>();
if (!tag.SourceModelPath.empty()) {
return MetaCoreNormalizeModelRelativePath(tag.SourceModelPath);
}
}
if (current.GetParentId() == 0) {
break;
}
current = scene.FindGameObject(current.GetParentId());
}
return {};
}
[[nodiscard]] std::optional<std::size_t> MetaCoreFindGeneratedMaterialIndexByGuid(
const MetaCoreModelAssetDocument& document,
const MetaCoreAssetGuid& materialGuid
) {
if (!materialGuid.IsValid()) {
return std::nullopt;
}
for (std::size_t index = 0; index < document.GeneratedMaterialAssets.size(); ++index) {
if (document.GeneratedMaterialAssets[index].AssetGuid == materialGuid) {
return index;
}
}
return std::nullopt;
}
[[nodiscard]] std::optional<std::size_t> MetaCoreResolveFirstGeneratedMaterialIndexForMeshRenderer(
const MetaCoreModelAssetDocument& document,
const MetaCoreMeshRendererComponent& meshRenderer
) {
if (meshRenderer.ModelNodeIndex < 0 ||
static_cast<std::size_t>(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) {
return std::nullopt;
}
const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast<std::size_t>(meshRenderer.ModelNodeIndex)];
if (node.MeshIndex < 0 || static_cast<std::size_t>(node.MeshIndex) >= document.Meshes.size()) {
return std::nullopt;
}
const auto& materialSlots = document.Meshes[static_cast<std::size_t>(node.MeshIndex)].MaterialSlots;
for (const std::int32_t materialIndex : materialSlots) {
if (materialIndex >= 0 && static_cast<std::size_t>(materialIndex) < document.GeneratedMaterialAssets.size()) {
return static_cast<std::size_t>(materialIndex);
}
}
return std::nullopt;
}
void MetaCoreRepairLegacyModelMaterialBinding(
MetaCoreMeshRendererComponent& meshRenderer,
const MetaCoreModelAssetDocument& document,
const MetaCoreAssetGuid& modelGuid
) {
if (meshRenderer.SourceModelPath.empty()) {
meshRenderer.SourceModelPath = document.SourcePath.generic_string();
}
if (!meshRenderer.SourceModelAssetGuid.IsValid() && modelGuid.IsValid()) {
meshRenderer.SourceModelAssetGuid = modelGuid;
}
if (!MetaCoreMaterialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) {
return;
}
if (meshRenderer.ModelNodeIndex < 0 ||
static_cast<std::size_t>(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) {
return;
}
const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast<std::size_t>(meshRenderer.ModelNodeIndex)];
if (node.MeshIndex < 0 || static_cast<std::size_t>(node.MeshIndex) >= document.Meshes.size()) {
return;
}
const auto& materialSlots = document.Meshes[static_cast<std::size_t>(node.MeshIndex)].MaterialSlots;
meshRenderer.MaterialAssetGuids.clear();
for (const std::int32_t materialIndex : materialSlots) {
if (materialIndex >= 0 && static_cast<std::size_t>(materialIndex) < document.GeneratedMaterialAssets.size()) {
meshRenderer.MaterialAssetGuids.push_back(document.GeneratedMaterialAssets[static_cast<std::size_t>(materialIndex)].AssetGuid);
} else {
meshRenderer.MaterialAssetGuids.push_back(MetaCoreAssetGuid{});
}
}
}
void MetaCoreApplyGeneratedMaterialPreviewToScene(
MetaCoreEditorContext& editorContext,
const MetaCoreModelAssetDocument& document
) {
std::unordered_map<MetaCoreAssetGuid, std::string, MetaCoreAssetGuidHasher> texturePaths;
for (const MetaCoreTextureAssetDocument& textureAsset : document.GeneratedTextureAssets) {
if (textureAsset.AssetGuid.IsValid()) {
texturePaths[textureAsset.AssetGuid] = textureAsset.SourcePath.generic_string();
}
}
const std::string documentSourcePath = MetaCoreNormalizeModelRelativePath(document.SourcePath.generic_string());
const MetaCoreAssetGuid modelGuid = documentSourcePath.empty()
? MetaCoreAssetGuid{}
: MetaCoreAssetRegistry::Get().ResolvePathToGuid(documentSourcePath);
bool changed = false;
for (MetaCoreGameObject& sceneObject : editorContext.GetScene().GetGameObjects()) {
if (!sceneObject.HasComponent<MetaCoreMeshRendererComponent>()) {
continue;
}
for (const MetaCoreAssetGuid& materialGuid : sceneObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids) {
const auto materialIterator = std::find_if(
document.GeneratedMaterialAssets.begin(),
document.GeneratedMaterialAssets.end(),
[&](const MetaCoreMaterialAssetDocument& materialAsset) {
return materialAsset.AssetGuid == materialGuid;
}
);
if (materialIterator == document.GeneratedMaterialAssets.end()) {
continue;
auto& meshRenderer = sceneObject.GetComponent<MetaCoreMeshRendererComponent>();
std::optional<std::size_t> materialIndex;
for (const MetaCoreAssetGuid& materialGuid : meshRenderer.MaterialAssetGuids) {
materialIndex = MetaCoreFindGeneratedMaterialIndexByGuid(document, materialGuid);
if (materialIndex.has_value()) {
break;
}
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor = materialIterator->BaseColor;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().Metallic = materialIterator->Metallic;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().Roughness = materialIterator->Roughness;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().DoubleSided = materialIterator->DoubleSided;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveColor = materialIterator->EmissiveColor;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaCutoff = materialIterator->AlphaCutoff;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaMode =
materialIterator->AlphaMode == MetaCoreMaterialAlphaMode::Mask
? MetaCoreMeshAlphaMode::Mask
: (materialIterator->AlphaMode == MetaCoreMaterialAlphaMode::Blend
? MetaCoreMeshAlphaMode::Blend
: MetaCoreMeshAlphaMode::Opaque);
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTextureGuid = materialIterator->BaseColorTexture;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTexturePath =
materialIterator->BaseColorTexture.IsValid() && texturePaths.contains(materialIterator->BaseColorTexture)
? texturePaths[materialIterator->BaseColorTexture]
: std::string{};
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTextureGuid = materialIterator->MetallicRoughnessTexture;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTexturePath =
materialIterator->MetallicRoughnessTexture.IsValid() && texturePaths.contains(materialIterator->MetallicRoughnessTexture)
? texturePaths[materialIterator->MetallicRoughnessTexture]
: std::string{};
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTextureGuid = materialIterator->NormalTexture;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTexturePath =
materialIterator->NormalTexture.IsValid() && texturePaths.contains(materialIterator->NormalTexture)
? texturePaths[materialIterator->NormalTexture]
: std::string{};
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTextureGuid = materialIterator->EmissiveTexture;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTexturePath =
materialIterator->EmissiveTexture.IsValid() && texturePaths.contains(materialIterator->EmissiveTexture)
? texturePaths[materialIterator->EmissiveTexture]
: std::string{};
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().AoTextureGuid = materialIterator->AoTexture;
sceneObject.GetComponent<MetaCoreMeshRendererComponent>().AoTexturePath =
materialIterator->AoTexture.IsValid() && texturePaths.contains(materialIterator->AoTexture)
? texturePaths[materialIterator->AoTexture]
: std::string{};
break;
}
const bool sameModelByGuid =
modelGuid.IsValid() &&
meshRenderer.SourceModelAssetGuid.IsValid() &&
meshRenderer.SourceModelAssetGuid == modelGuid;
const std::string sceneObjectModelPath = MetaCoreResolveSceneObjectModelPath(editorContext.GetScene(), sceneObject);
const bool sameModelByPath = !documentSourcePath.empty() && sceneObjectModelPath == documentSourcePath;
const bool sameModel = sameModelByGuid || sameModelByPath;
if (!materialIndex.has_value() && sameModel && MetaCoreMaterialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) {
materialIndex = MetaCoreResolveFirstGeneratedMaterialIndexForMeshRenderer(document, meshRenderer);
}
if (!materialIndex.has_value()) {
continue;
}
if (sameModel) {
MetaCoreRepairLegacyModelMaterialBinding(meshRenderer, document, modelGuid);
}
MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer(meshRenderer, document, *materialIndex);
changed = true;
}
if (changed) {
editorContext.GetScene().IncrementRevision();
}
}
void MetaCoreSyncMaterialDocumentPreviewToMeshRenderer(
MetaCoreIAssetDatabaseService* assetDatabaseService,
MetaCoreIAssetEditingService* assetEditingService,
MetaCoreMeshRendererComponent& meshRenderer,
const MetaCoreMaterialAssetDocument& materialAsset
) {
const auto texturePathForGuid = [&](const MetaCoreAssetGuid& textureGuid) -> std::string {
if (!textureGuid.IsValid()) {
return {};
}
if (assetDatabaseService != nullptr) {
if (const auto textureRecord = assetDatabaseService->FindAssetByGuid(textureGuid); textureRecord.has_value()) {
return textureRecord->RelativePath.generic_string();
}
}
if (assetEditingService != nullptr) {
if (const auto textureAsset = assetEditingService->LoadTextureAsset(textureGuid);
textureAsset.has_value() && !textureAsset->SourcePath.empty()) {
return textureAsset->SourcePath.generic_string();
}
}
return {};
};
meshRenderer.BaseColor = materialAsset.BaseColor;
meshRenderer.DoubleSided = materialAsset.DoubleSided;
meshRenderer.Metallic = materialAsset.Metallic;
meshRenderer.Roughness = materialAsset.Roughness;
meshRenderer.AlphaCutoff = materialAsset.AlphaCutoff;
meshRenderer.AlphaMode =
materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask
? MetaCoreMeshAlphaMode::Mask
: (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Blend
? MetaCoreMeshAlphaMode::Blend
: MetaCoreMeshAlphaMode::Opaque);
meshRenderer.EmissiveColor = materialAsset.EmissiveColor;
meshRenderer.BaseColorTextureGuid = materialAsset.BaseColorTexture;
meshRenderer.BaseColorTexturePath = texturePathForGuid(materialAsset.BaseColorTexture);
meshRenderer.MetallicRoughnessTextureGuid = materialAsset.MetallicRoughnessTexture;
meshRenderer.MetallicRoughnessTexturePath = texturePathForGuid(materialAsset.MetallicRoughnessTexture);
meshRenderer.NormalTextureGuid = materialAsset.NormalTexture;
meshRenderer.NormalTexturePath = texturePathForGuid(materialAsset.NormalTexture);
meshRenderer.EmissiveTextureGuid = materialAsset.EmissiveTexture;
meshRenderer.EmissiveTexturePath = texturePathForGuid(materialAsset.EmissiveTexture);
meshRenderer.AoTextureGuid = materialAsset.AoTexture;
meshRenderer.AoTexturePath = texturePathForGuid(materialAsset.AoTexture);
}
void MetaCoreApplyMaterialPreviewToSceneUsers(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetGuid& materialGuid,
const MetaCoreMaterialAssetDocument& materialAsset
) {
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
bool changed = false;
for (MetaCoreGameObject& sceneObject : editorContext.GetScene().GetGameObjects()) {
if (!sceneObject.HasComponent<MetaCoreMeshRendererComponent>()) {
continue;
}
auto& meshRenderer = sceneObject.GetComponent<MetaCoreMeshRendererComponent>();
if (std::find(meshRenderer.MaterialAssetGuids.begin(), meshRenderer.MaterialAssetGuids.end(), materialGuid) == meshRenderer.MaterialAssetGuids.end()) {
continue;
}
MetaCoreSyncMaterialDocumentPreviewToMeshRenderer(
assetDatabaseService.get(),
assetEditingService.get(),
meshRenderer,
materialAsset
);
changed = true;
}
if (changed) {
editorContext.GetScene().IncrementRevision();
}
}
@ -1159,17 +1356,20 @@ void MetaCoreForEachSelectedGameObject(MetaCoreEditorContext& editorContext, TAc
return false;
}
const auto resolvedMaterial = assetEditingService->ResolveGeneratedAsset(materialGuid);
if (!resolvedMaterial.has_value() || resolvedMaterial->GeneratedKind != "material") {
auto materialAsset = assetEditingService->LoadMaterialAsset(materialGuid);
if (!materialAsset.has_value()) {
return false;
}
const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid);
if (!modelDocument.has_value() || resolvedMaterial->GeneratedIndex >= modelDocument->GeneratedMaterialAssets.size()) {
return false;
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
const auto resolvedMaterial = assetEditingService->ResolveGeneratedAsset(materialGuid);
std::optional<MetaCoreModelAssetDocument> modelDocument;
if (resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") {
modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid);
}
MetaCoreTrackComponentInspectorEdit(editorContext, "拖拽材质到材质槽", false);
bool changed = false;
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
if (!selectedObject.HasComponent<MetaCoreMeshRendererComponent>()) {
return;
@ -1178,13 +1378,29 @@ void MetaCoreForEachSelectedGameObject(MetaCoreEditorContext& editorContext, TAc
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.resize(materialSlotIndex + 1);
}
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids[materialSlotIndex] = materialGuid;
MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer(
selectedObject.GetComponent<MetaCoreMeshRendererComponent>(),
*modelDocument,
resolvedMaterial->GeneratedIndex
);
if (resolvedMaterial.has_value() &&
resolvedMaterial->GeneratedKind == "material" &&
modelDocument.has_value() &&
resolvedMaterial->GeneratedIndex < modelDocument->GeneratedMaterialAssets.size()) {
MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer(
selectedObject.GetComponent<MetaCoreMeshRendererComponent>(),
*modelDocument,
resolvedMaterial->GeneratedIndex
);
} else {
MetaCoreSyncMaterialDocumentPreviewToMeshRenderer(
assetDatabaseService.get(),
assetEditingService.get(),
selectedObject.GetComponent<MetaCoreMeshRendererComponent>(),
*materialAsset
);
}
changed = true;
});
return true;
if (changed) {
editorContext.GetScene().IncrementRevision();
}
return changed;
}
[[nodiscard]] bool MetaCoreReapplyMaterialResourceToSelectedObjects(
@ -1218,6 +1434,57 @@ void MetaCoreClearMaterialSlotOnSelectedObjects(
});
}
[[nodiscard]] std::filesystem::path MetaCoreBuildUniqueMaterialPath(MetaCoreIAssetDatabaseService& assetDatabaseService) {
if (!assetDatabaseService.HasProject()) {
return {};
}
const std::filesystem::path relativeDirectory = std::filesystem::path("Assets") / "Materials";
for (int suffix = 0; suffix < 1000; ++suffix) {
const std::string name = suffix == 0 ? "Material" : ("Material " + std::to_string(suffix));
const std::filesystem::path relativePath = relativeDirectory / (name + ".mcmaterial.json");
if (!std::filesystem::exists(assetDatabaseService.GetProjectDescriptor().RootPath / relativePath)) {
return relativePath;
}
}
return relativeDirectory / "Material 1000.mcmaterial.json";
}
[[nodiscard]] bool MetaCoreCreateAndBindMaterialToSelectedObjects(
MetaCoreEditorContext& editorContext,
std::size_t materialSlotIndex
) {
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
return false;
}
const std::filesystem::path materialPath = MetaCoreBuildUniqueMaterialPath(*assetDatabaseService);
if (materialPath.empty() || !assetDatabaseService->CreateMaterialAsset(materialPath)) {
return false;
}
const auto materialRecord = assetDatabaseService->FindAssetByRelativePath(materialPath);
if (!materialRecord.has_value() || !materialRecord->Guid.IsValid()) {
return false;
}
if (!MetaCoreApplyDraggedMaterialToSelectedObjects(editorContext, materialSlotIndex, materialRecord->Guid)) {
return false;
}
editorContext.SelectAsset(MetaCoreSelectedAssetState{
materialRecord->Guid,
materialRecord->RelativePath,
materialRecord->Type,
materialRecord->StorageKind
});
editorContext.ClearSelectedAssetSubId();
editorContext.ClearSelection();
return true;
}
[[nodiscard]] std::optional<MetaCoreAssetGuid> MetaCoreResolveDefaultMaterialForMeshRendererSlot(
MetaCoreEditorContext& editorContext,
const MetaCoreMeshRendererComponent& meshRenderer,
@ -2308,6 +2575,31 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
ImGui::EndTable();
}
if (ImGui::Button("添加材质槽")) {
const std::size_t newSlotIndex = meshRenderer.MaterialAssetGuids.size();
MetaCoreTrackComponentInspectorEdit(editorContext, "添加材质槽", false);
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
if (selectedObject.HasComponent<MetaCoreMeshRendererComponent>()) {
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.resize(newSlotIndex + 1);
}
});
}
ImGui::SameLine();
std::size_t createSlotIndex = meshRenderer.MaterialAssetGuids.size();
for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) {
if (!meshRenderer.MaterialAssetGuids[materialIndex].IsValid()) {
createSlotIndex = materialIndex;
break;
}
}
if (ImGui::Button("新建材质并绑定")) {
if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, createSlotIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败");
}
}
// 槽位列表循环
const auto materialChoices = MetaCoreCollectMaterialAssetChoices(editorContext);
for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) {
@ -2412,6 +2704,14 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
if (ImGui::Selectable("<None>", !materialGuid.IsValid())) {
materialGuid = MetaCoreAssetGuid{};
}
if (ImGui::Selectable("新建材质并绑定", false)) {
if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败");
}
}
ImGui::Separator();
for (const auto& choice : materialChoices) {
bool isSelected = (choice.AssetGuid == materialGuid);
if (ImGui::Selectable(choice.Label.c_str(), isSelected)) {
@ -2436,6 +2736,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
materialGuid = MetaCoreAssetGuid{};
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已清空材质槽位");
}
if (ImGui::MenuItem("新建材质并绑定")) {
if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败");
}
}
if (ImGui::MenuItem("恢复模型默认材质 (Restore Default)")) {
if (MetaCoreRestoreDefaultMaterialSlotOnSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已恢复模型默认材质槽位");
@ -2480,6 +2787,21 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
});
editorContext.SetSelectedAssetSubId("material:" + materialGuid.ToString());
editorContext.ClearSelection();
} else {
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
const auto materialRecord = assetDatabaseService != nullptr
? assetDatabaseService->FindAssetByGuid(materialGuid)
: std::nullopt;
if (materialRecord.has_value()) {
editorContext.SelectAsset(MetaCoreSelectedAssetState{
materialRecord->Guid,
materialRecord->RelativePath,
materialRecord->Type,
materialRecord->StorageKind
});
editorContext.ClearSelectedAssetSubId();
editorContext.ClearSelection();
}
}
}
}
@ -2720,10 +3042,20 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
drawList->AddCircleFilled(highlightCenter, 0.8F, IM_COL32(255, 255, 255, 255));
if (isPanelOpen) {
if (!resolvedMaterial.has_value() ||
resolvedMaterial->GeneratedKind != "material" ||
!materialDocument.has_value()) {
ImGui::TextDisabled("未找到对应材质资源");
if (!materialDocument.has_value()) {
ImGui::TextColored(ImVec4(0.95F, 0.38F, 0.32F, 1.0F), "未找到对应材质资源");
ImGui::TextDisabled("可以清空槽位,或新建一个材质并绑定到当前槽位。");
if (ImGui::Button(("新建材质并绑定##MissingMaterial_" + std::to_string(materialIndex)).c_str())) {
if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败");
}
}
ImGui::SameLine();
if (ImGui::Button(("清空槽位##MissingMaterial_" + std::to_string(materialIndex)).c_str())) {
MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex);
}
} else {
MetaCoreMaterialAssetDocument materialAsset = *materialDocument;
bool modified = false;
@ -2791,9 +3123,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
if (modified) {
if (assetEditingService->SaveMaterialAsset(materialGuid, materialAsset)) {
if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid);
modelDocument.has_value()) {
MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument);
if (resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") {
if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid);
modelDocument.has_value()) {
MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument);
}
} else {
MetaCoreApplyMaterialPreviewToSceneUsers(editorContext, materialGuid, materialAsset);
}
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已保存材质资源修改");
} else {
@ -5609,7 +5945,7 @@ bool MetaCoreBuiltinAssetEditingService::SaveMaterialAsset(
}
}
if (PackageService_ != nullptr && assetRecord.has_value()) {
if (PackageService_ != nullptr) {
const auto resolved = MetaCoreResolveGeneratedAssetDocument(
*AssetDatabaseService_,
*PackageService_,
@ -5621,13 +5957,17 @@ bool MetaCoreBuiltinAssetEditingService::SaveMaterialAsset(
MetaCoreModelAssetDocument editableDocument = resolved->Document;
editableDocument.GeneratedMaterialAssets[resolved->GeneratedIndex] = document;
editableDocument.GeneratedMaterialAssets[resolved->GeneratedIndex].AssetGuid = materialGuid;
return MetaCoreWriteImportedGltfAssetDocument(
const bool saved = MetaCoreWriteImportedGltfAssetDocument(
*AssetDatabaseService_,
*PackageService_,
*ReflectionRegistry_,
resolved->AssetRecord,
editableDocument
);
if (saved) {
ModelCache_[resolved->AssetRecord.Guid] = editableDocument;
}
return saved;
}
}
}

View File

@ -1274,89 +1274,177 @@ void MetaCoreApplyMaterialPreviewToScene(
return;
}
const auto normalizeModelPath = [](std::string path) {
if (path.empty()) {
return std::string{};
}
std::replace(path.begin(), path.end(), '\\', '/');
return std::filesystem::path(path).lexically_normal().generic_string();
};
const auto materialGuidListIsEmpty = [](const std::vector<MetaCoreAssetGuid>& materialGuids) {
return std::all_of(materialGuids.begin(), materialGuids.end(), [](const MetaCoreAssetGuid& guid) {
return !guid.IsValid();
});
};
const auto findMaterialIndexByGuid = [&](const MetaCoreAssetGuid& materialGuid) -> std::optional<std::size_t> {
if (!materialGuid.IsValid()) {
return std::nullopt;
}
for (std::size_t index = 0; index < document.GeneratedMaterialAssets.size(); ++index) {
if (document.GeneratedMaterialAssets[index].AssetGuid == materialGuid) {
return index;
}
}
return std::nullopt;
};
const auto resolveFirstMaterialIndexForMeshRenderer = [&](const MetaCoreMeshRendererComponent& meshRenderer) -> std::optional<std::size_t> {
if (meshRenderer.ModelNodeIndex < 0 ||
static_cast<std::size_t>(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) {
return std::nullopt;
}
const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast<std::size_t>(meshRenderer.ModelNodeIndex)];
if (node.MeshIndex < 0 || static_cast<std::size_t>(node.MeshIndex) >= document.Meshes.size()) {
return std::nullopt;
}
for (const std::int32_t materialIndex : document.Meshes[static_cast<std::size_t>(node.MeshIndex)].MaterialSlots) {
if (materialIndex >= 0 && static_cast<std::size_t>(materialIndex) < document.GeneratedMaterialAssets.size()) {
return static_cast<std::size_t>(materialIndex);
}
}
return std::nullopt;
};
const auto resolveSceneObjectModelPath = [&](MetaCoreGameObject object) {
MetaCoreGameObject current = object;
while (current) {
if (current.HasComponent<MetaCoreMeshRendererComponent>()) {
const auto& meshRenderer = current.GetComponent<MetaCoreMeshRendererComponent>();
if (!meshRenderer.SourceModelPath.empty()) {
return normalizeModelPath(meshRenderer.SourceModelPath);
}
}
if (current.HasComponent<MetaCoreModelRootTag>()) {
const auto& tag = current.GetComponent<MetaCoreModelRootTag>();
if (!tag.SourceModelPath.empty()) {
return normalizeModelPath(tag.SourceModelPath);
}
}
if (current.GetParentId() == 0) {
break;
}
current = editorContext.GetScene().FindGameObject(current.GetParentId());
}
return std::string{};
};
const auto repairLegacyBinding = [&](MetaCoreMeshRendererComponent& meshRenderer, const MetaCoreAssetGuid& modelGuid) {
if (meshRenderer.SourceModelPath.empty()) {
meshRenderer.SourceModelPath = document.SourcePath.generic_string();
}
if (!meshRenderer.SourceModelAssetGuid.IsValid() && modelGuid.IsValid()) {
meshRenderer.SourceModelAssetGuid = modelGuid;
}
if (!materialGuidListIsEmpty(meshRenderer.MaterialAssetGuids) ||
meshRenderer.ModelNodeIndex < 0 ||
static_cast<std::size_t>(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) {
return;
}
const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast<std::size_t>(meshRenderer.ModelNodeIndex)];
if (node.MeshIndex < 0 || static_cast<std::size_t>(node.MeshIndex) >= document.Meshes.size()) {
return;
}
meshRenderer.MaterialAssetGuids.clear();
for (const std::int32_t materialIndex : document.Meshes[static_cast<std::size_t>(node.MeshIndex)].MaterialSlots) {
if (materialIndex >= 0 && static_cast<std::size_t>(materialIndex) < document.GeneratedMaterialAssets.size()) {
meshRenderer.MaterialAssetGuids.push_back(document.GeneratedMaterialAssets[static_cast<std::size_t>(materialIndex)].AssetGuid);
} else {
meshRenderer.MaterialAssetGuids.push_back(MetaCoreAssetGuid{});
}
}
};
const auto applyMaterialIndex = [&](MetaCoreMeshRendererComponent& meshRenderer, std::size_t materialIndex) {
if (materialIndex >= document.GeneratedMaterialAssets.size()) {
return;
}
const MetaCoreMaterialAssetDocument& materialDocument = document.GeneratedMaterialAssets[materialIndex];
const MetaCoreAssetGuid materialGuid = materialDocument.AssetGuid;
meshRenderer.BaseColor = materialDocument.BaseColor;
meshRenderer.BaseColorTextureGuid = materialDocument.BaseColorTexture;
meshRenderer.BaseColorTexturePath =
materialDocument.BaseColorTexture.IsValid() && texturePaths.contains(materialDocument.BaseColorTexture)
? texturePaths[materialDocument.BaseColorTexture]
: std::string{};
meshRenderer.MetallicRoughnessTextureGuid = materialDocument.MetallicRoughnessTexture;
meshRenderer.MetallicRoughnessTexturePath =
materialDocument.MetallicRoughnessTexture.IsValid() && texturePaths.contains(materialDocument.MetallicRoughnessTexture)
? texturePaths[materialDocument.MetallicRoughnessTexture]
: std::string{};
meshRenderer.DoubleSided = doubleSidedFlags.contains(materialGuid) ? doubleSidedFlags[materialGuid] : true;
meshRenderer.Metallic = metallicValues.contains(materialGuid) ? metallicValues[materialGuid] : 0.0F;
meshRenderer.Roughness = roughnessValues.contains(materialGuid) ? roughnessValues[materialGuid] : 1.0F;
meshRenderer.EmissiveColor = emissiveColors.contains(materialGuid) ? emissiveColors[materialGuid] : glm::vec3(0.0F, 0.0F, 0.0F);
const MetaCoreMaterialAlphaMode alphaMode = alphaModes.contains(materialGuid) ? alphaModes[materialGuid] : MetaCoreMaterialAlphaMode::Opaque;
meshRenderer.AlphaMode =
alphaMode == MetaCoreMaterialAlphaMode::Mask
? MetaCoreMeshAlphaMode::Mask
: (alphaMode == MetaCoreMaterialAlphaMode::Blend ? MetaCoreMeshAlphaMode::Blend : MetaCoreMeshAlphaMode::Opaque);
meshRenderer.AlphaCutoff = alphaCutoffs.contains(materialGuid) ? alphaCutoffs[materialGuid] : 0.5F;
meshRenderer.NormalTextureGuid = materialDocument.NormalTexture;
meshRenderer.NormalTexturePath =
materialDocument.NormalTexture.IsValid() && texturePaths.contains(materialDocument.NormalTexture)
? texturePaths[materialDocument.NormalTexture]
: std::string{};
meshRenderer.EmissiveTextureGuid = materialDocument.EmissiveTexture;
meshRenderer.EmissiveTexturePath =
materialDocument.EmissiveTexture.IsValid() && texturePaths.contains(materialDocument.EmissiveTexture)
? texturePaths[materialDocument.EmissiveTexture]
: std::string{};
meshRenderer.AoTextureGuid = materialDocument.AoTexture;
meshRenderer.AoTexturePath =
materialDocument.AoTexture.IsValid() && texturePaths.contains(materialDocument.AoTexture)
? texturePaths[materialDocument.AoTexture]
: std::string{};
};
const std::string documentSourcePath = normalizeModelPath(document.SourcePath.generic_string());
const MetaCoreAssetGuid modelGuid = documentSourcePath.empty()
? MetaCoreAssetGuid{}
: MetaCoreAssetRegistry::Get().ResolvePathToGuid(documentSourcePath);
bool changed = false;
for (MetaCoreGameObject& gameObject : editorContext.GetScene().GetGameObjects()) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
continue;
}
for (const MetaCoreAssetGuid& materialGuid : gameObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids) {
const auto iterator = materialBaseColors.find(materialGuid);
if (iterator != materialBaseColors.end()) {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor = iterator->second;
const auto materialDocumentIterator = std::find_if(
document.GeneratedMaterialAssets.begin(),
document.GeneratedMaterialAssets.end(),
[&](const MetaCoreMaterialAssetDocument& materialAsset) {
return materialAsset.AssetGuid == materialGuid;
}
);
if (materialDocumentIterator != document.GeneratedMaterialAssets.end() &&
materialDocumentIterator->BaseColorTexture.IsValid()) {
const auto textureIterator = texturePaths.find(materialDocumentIterator->BaseColorTexture);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTexturePath =
textureIterator != texturePaths.end() ? textureIterator->second : std::string{};
} else {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTexturePath.clear();
}
if (materialDocumentIterator != document.GeneratedMaterialAssets.end() &&
materialDocumentIterator->MetallicRoughnessTexture.IsValid()) {
const auto textureIterator = texturePaths.find(materialDocumentIterator->MetallicRoughnessTexture);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTexturePath =
textureIterator != texturePaths.end() ? textureIterator->second : std::string{};
} else {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTexturePath.clear();
}
const auto doubleSidedIterator = doubleSidedFlags.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().DoubleSided =
doubleSidedIterator != doubleSidedFlags.end() ? doubleSidedIterator->second : true;
const auto metallicIterator = metallicValues.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().Metallic =
metallicIterator != metallicValues.end() ? metallicIterator->second : 0.0F;
const auto roughnessIterator = roughnessValues.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().Roughness =
roughnessIterator != roughnessValues.end() ? roughnessIterator->second : 1.0F;
const auto emissiveColorIterator = emissiveColors.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveColor =
emissiveColorIterator != emissiveColors.end() ? emissiveColorIterator->second : glm::vec3(0.0F, 0.0F, 0.0F);
const auto alphaModeIterator = alphaModes.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaMode =
alphaModeIterator != alphaModes.end()
? (alphaModeIterator->second == MetaCoreMaterialAlphaMode::Mask
? MetaCoreMeshAlphaMode::Mask
: (alphaModeIterator->second == MetaCoreMaterialAlphaMode::Blend
? MetaCoreMeshAlphaMode::Blend
: MetaCoreMeshAlphaMode::Opaque))
: MetaCoreMeshAlphaMode::Opaque;
const auto alphaCutoffIterator = alphaCutoffs.find(materialGuid);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaCutoff =
alphaCutoffIterator != alphaCutoffs.end() ? alphaCutoffIterator->second : 0.5F;
if (materialDocumentIterator != document.GeneratedMaterialAssets.end() &&
materialDocumentIterator->NormalTexture.IsValid()) {
const auto textureIterator = texturePaths.find(materialDocumentIterator->NormalTexture);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTexturePath =
textureIterator != texturePaths.end() ? textureIterator->second : std::string{};
} else {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTexturePath.clear();
}
if (materialDocumentIterator != document.GeneratedMaterialAssets.end() &&
materialDocumentIterator->EmissiveTexture.IsValid()) {
const auto textureIterator = texturePaths.find(materialDocumentIterator->EmissiveTexture);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTexturePath =
textureIterator != texturePaths.end() ? textureIterator->second : std::string{};
} else {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTexturePath.clear();
}
if (materialDocumentIterator != document.GeneratedMaterialAssets.end() &&
materialDocumentIterator->AoTexture.IsValid()) {
const auto textureIterator = texturePaths.find(materialDocumentIterator->AoTexture);
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AoTexturePath =
textureIterator != texturePaths.end() ? textureIterator->second : std::string{};
} else {
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AoTexturePath.clear();
}
auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
std::optional<std::size_t> materialIndex;
for (const MetaCoreAssetGuid& materialGuid : meshRenderer.MaterialAssetGuids) {
materialIndex = findMaterialIndexByGuid(materialGuid);
if (materialIndex.has_value()) {
break;
}
}
const bool sameModelByGuid =
modelGuid.IsValid() &&
meshRenderer.SourceModelAssetGuid.IsValid() &&
meshRenderer.SourceModelAssetGuid == modelGuid;
const bool sameModelByPath = !documentSourcePath.empty() && resolveSceneObjectModelPath(gameObject) == documentSourcePath;
const bool sameModel = sameModelByGuid || sameModelByPath;
if (!materialIndex.has_value() && sameModel && materialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) {
materialIndex = resolveFirstMaterialIndexForMeshRenderer(meshRenderer);
}
if (!materialIndex.has_value()) {
continue;
}
if (sameModel) {
repairLegacyBinding(meshRenderer, modelGuid);
}
applyMaterialIndex(meshRenderer, *materialIndex);
changed = true;
}
if (changed) {
editorContext.GetScene().IncrementRevision();
}
}
@ -2686,6 +2774,472 @@ void DrawReferencedObjectList(MetaCoreEditorContext& editorContext, const std::v
}
}
[[nodiscard]] const char* MetaCoreMaterialShaderModelLabel(MetaCoreMaterialShaderModel shaderModel) {
switch (shaderModel) {
case MetaCoreMaterialShaderModel::PbrMetalRough:
return "PBR 金属粗糙度";
default:
return "未知";
}
}
[[nodiscard]] const char* MetaCoreMaterialAlphaModeLabel(MetaCoreMaterialAlphaMode alphaMode) {
switch (alphaMode) {
case MetaCoreMaterialAlphaMode::Opaque:
return "不透明";
case MetaCoreMaterialAlphaMode::Mask:
return "镂空裁剪";
case MetaCoreMaterialAlphaMode::Blend:
return "透明混合";
default:
return "未知";
}
}
[[nodiscard]] ImU32 MetaCoreMaterialColorToU32(const glm::vec3& color, int alpha = 255) {
const auto toByte = [](float value) {
return static_cast<int>(std::clamp(value, 0.0F, 1.0F) * 255.0F + 0.5F);
};
return IM_COL32(toByte(color.r), toByte(color.g), toByte(color.b), std::clamp(alpha, 0, 255));
}
void MetaCoreApplyMaterialDocumentToMeshRenderer(
MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIAssetEditingService* assetEditingService,
MetaCoreMeshRendererComponent& meshRenderer,
const MetaCoreMaterialAssetDocument& materialDocument
) {
const auto texturePathForGuid = [&](const MetaCoreAssetGuid& textureGuid) -> std::string {
if (!textureGuid.IsValid()) {
return {};
}
const auto textureRecord = assetDatabaseService.FindAssetByGuid(textureGuid);
if (textureRecord.has_value()) {
return textureRecord->RelativePath.generic_string();
}
if (assetEditingService != nullptr) {
const auto textureDocument = assetEditingService->LoadTextureAsset(textureGuid);
if (textureDocument.has_value() && !textureDocument->SourcePath.empty()) {
return textureDocument->SourcePath.generic_string();
}
}
return {};
};
meshRenderer.BaseColor = materialDocument.BaseColor;
meshRenderer.Metallic = materialDocument.Metallic;
meshRenderer.Roughness = materialDocument.Roughness;
meshRenderer.DoubleSided = materialDocument.DoubleSided;
meshRenderer.EmissiveColor = materialDocument.EmissiveColor;
meshRenderer.AlphaCutoff = materialDocument.AlphaCutoff;
meshRenderer.AlphaMode =
materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Mask
? MetaCoreMeshAlphaMode::Mask
: (materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Blend
? MetaCoreMeshAlphaMode::Blend
: MetaCoreMeshAlphaMode::Opaque);
meshRenderer.BaseColorTextureGuid = materialDocument.BaseColorTexture;
meshRenderer.BaseColorTexturePath = texturePathForGuid(materialDocument.BaseColorTexture);
meshRenderer.NormalTextureGuid = materialDocument.NormalTexture;
meshRenderer.NormalTexturePath = texturePathForGuid(materialDocument.NormalTexture);
meshRenderer.MetallicRoughnessTextureGuid = materialDocument.MetallicRoughnessTexture;
meshRenderer.MetallicRoughnessTexturePath = texturePathForGuid(materialDocument.MetallicRoughnessTexture);
meshRenderer.EmissiveTextureGuid = materialDocument.EmissiveTexture;
meshRenderer.EmissiveTexturePath = texturePathForGuid(materialDocument.EmissiveTexture);
meshRenderer.AoTextureGuid = materialDocument.AoTexture;
meshRenderer.AoTexturePath = texturePathForGuid(materialDocument.AoTexture);
}
void MetaCoreApplyStandaloneMaterialPreviewToScene(
MetaCoreEditorContext& editorContext,
MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreMaterialAssetDocument& materialDocument
) {
if (!materialDocument.AssetGuid.IsValid()) {
return;
}
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
bool changed = false;
for (MetaCoreGameObject& gameObject : editorContext.GetScene().GetGameObjects()) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
continue;
}
const auto& materialGuids = gameObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids;
if (std::find(materialGuids.begin(), materialGuids.end(), materialDocument.AssetGuid) == materialGuids.end()) {
continue;
}
MetaCoreApplyMaterialDocumentToMeshRenderer(
assetDatabaseService,
assetEditingService.get(),
gameObject.GetComponent<MetaCoreMeshRendererComponent>(),
materialDocument
);
changed = true;
}
if (changed) {
editorContext.GetScene().IncrementRevision();
}
}
struct MetaCoreMaterialTextureChoice {
MetaCoreAssetGuid AssetGuid{};
std::string Label{};
};
struct MetaCoreMaterialTextureBindingState {
std::string DisplayName = "无贴图";
std::string DetailText{};
bool Missing = false;
std::optional<MetaCoreAssetRecord> LocateAsset{};
std::string LocateSubId{};
};
[[nodiscard]] std::vector<MetaCoreMaterialTextureChoice> MetaCoreCollectMaterialTextureChoices(
MetaCoreIAssetDatabaseService& assetDatabaseService
) {
std::vector<MetaCoreMaterialTextureChoice> choices;
choices.reserve(assetDatabaseService.GetAssetRegistry().size());
for (const MetaCoreAssetRecord& record : assetDatabaseService.GetAssetRegistry()) {
if (record.Type != "texture" || !record.Guid.IsValid()) {
continue;
}
std::string label = record.RelativePath.filename().string();
if (!record.RelativePath.parent_path().empty()) {
label += " | " + record.RelativePath.parent_path().generic_string();
}
choices.push_back(MetaCoreMaterialTextureChoice{record.Guid, std::move(label)});
}
std::sort(choices.begin(), choices.end(), [](const auto& lhs, const auto& rhs) {
return lhs.Label < rhs.Label;
});
return choices;
}
[[nodiscard]] MetaCoreMaterialTextureBindingState MetaCoreResolveMaterialTextureBinding(
MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIAssetEditingService* assetEditingService,
const MetaCoreAssetGuid& textureGuid
) {
MetaCoreMaterialTextureBindingState state;
if (!textureGuid.IsValid()) {
return state;
}
if (const auto record = assetDatabaseService.FindAssetByGuid(textureGuid); record.has_value()) {
state.DisplayName = record->RelativePath.filename().string();
state.DetailText = record->RelativePath.generic_string();
state.LocateAsset = *record;
return state;
}
if (assetEditingService != nullptr) {
const auto resolved = assetEditingService->ResolveGeneratedAsset(textureGuid);
const auto textureDocument = assetEditingService->LoadTextureAsset(textureGuid);
if (resolved.has_value() && resolved->GeneratedKind == "texture" && textureDocument.has_value()) {
state.DisplayName = !textureDocument->Name.empty()
? textureDocument->Name
: (!textureDocument->SourcePath.empty() ? textureDocument->SourcePath.filename().string() : std::string("生成贴图"));
state.DetailText = "生成贴图: " + resolved->SourceAsset.RelativePath.generic_string();
if (!textureDocument->SourcePath.empty()) {
state.DetailText += " / " + textureDocument->SourcePath.generic_string();
}
state.LocateAsset = resolved->SourceAsset;
state.LocateSubId = MetaCoreBuildGeneratedResourceSubId("texture", textureGuid);
return state;
}
if (textureDocument.has_value()) {
state.DisplayName = !textureDocument->Name.empty()
? textureDocument->Name
: (!textureDocument->SourcePath.empty() ? textureDocument->SourcePath.filename().string() : std::string("贴图资源"));
state.DetailText = textureDocument->SourcePath.generic_string();
return state;
}
}
state.DisplayName = "贴图缺失";
state.Missing = true;
return state;
}
void MetaCoreDrawMaterialPreviewCard(const MetaCoreMaterialAssetDocument& materialDocument) {
const float cardSize = std::min(std::max(ImGui::GetContentRegionAvail().x, 180.0F), 260.0F);
const ImVec2 canvasMin = ImGui::GetCursorScreenPos();
const ImVec2 canvasSize(cardSize, cardSize + 72.0F);
const ImVec2 canvasMax(canvasMin.x + canvasSize.x, canvasMin.y + canvasSize.y);
ImGui::InvisibleButton("##MaterialPreviewCard", canvasSize);
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(canvasMin, canvasMax, IM_COL32(28, 31, 38, 255), 12.0F);
drawList->AddRect(canvasMin, canvasMax, IM_COL32(255, 255, 255, 28), 12.0F);
const ImVec2 previewMin(canvasMin.x + 14.0F, canvasMin.y + 14.0F);
const ImVec2 previewMax(canvasMax.x - 14.0F, canvasMin.y + cardSize - 6.0F);
const float checker = 14.0F;
for (float y = previewMin.y; y < previewMax.y; y += checker) {
for (float x = previewMin.x; x < previewMax.x; x += checker) {
const bool even = (static_cast<int>((x - previewMin.x) / checker) + static_cast<int>((y - previewMin.y) / checker)) % 2 == 0;
drawList->AddRectFilled(
ImVec2(x, y),
ImVec2(std::min(x + checker, previewMax.x), std::min(y + checker, previewMax.y)),
even ? IM_COL32(55, 58, 66, 255) : IM_COL32(39, 42, 50, 255)
);
}
}
drawList->AddRectFilled(previewMin, previewMax, MetaCoreMaterialColorToU32(materialDocument.BaseColor, 238), 10.0F);
const ImVec2 sphereCenter(
(previewMin.x + previewMax.x) * 0.5F,
(previewMin.y + previewMax.y) * 0.5F
);
const float radius = std::min(previewMax.x - previewMin.x, previewMax.y - previewMin.y) * 0.31F;
drawList->AddCircleFilled(sphereCenter, radius, MetaCoreMaterialColorToU32(materialDocument.BaseColor), 48);
drawList->AddCircleFilled(
ImVec2(sphereCenter.x + radius * 0.20F, sphereCenter.y + radius * 0.22F),
radius * 0.78F,
IM_COL32(0, 0, 0, static_cast<int>(50.0F + materialDocument.Roughness * 80.0F)),
48
);
const float highlightRadius = std::max(3.0F, radius * (0.24F - materialDocument.Roughness * 0.14F));
const int highlightAlpha = static_cast<int>(95.0F + materialDocument.Metallic * 115.0F + (1.0F - materialDocument.Roughness) * 40.0F);
drawList->AddCircleFilled(
ImVec2(sphereCenter.x - radius * 0.34F, sphereCenter.y - radius * 0.36F),
highlightRadius,
IM_COL32(255, 255, 255, std::clamp(highlightAlpha, 80, 245)),
24
);
const float emissiveStrength = std::max({materialDocument.EmissiveColor.r, materialDocument.EmissiveColor.g, materialDocument.EmissiveColor.b});
if (emissiveStrength > 0.001F) {
drawList->AddCircle(
sphereCenter,
radius + 5.0F,
MetaCoreMaterialColorToU32(materialDocument.EmissiveColor, 180),
48,
3.0F
);
}
drawList->AddRect(previewMin, previewMax, IM_COL32(255, 255, 255, 42), 10.0F);
const float infoY = previewMax.y + 12.0F;
drawList->AddText(ImVec2(canvasMin.x + 16.0F, infoY), IM_COL32(225, 230, 238, 255), "材质预览");
const std::string info =
"金属 " + std::to_string(static_cast<int>(materialDocument.Metallic * 100.0F + 0.5F)) +
"% 粗糙 " + std::to_string(static_cast<int>(materialDocument.Roughness * 100.0F + 0.5F)) + "%";
drawList->AddText(ImVec2(canvasMin.x + 16.0F, infoY + ImGui::GetTextLineHeight() + 4.0F), IM_COL32(151, 160, 174, 255), info.c_str());
}
[[nodiscard]] bool MetaCoreDrawMaterialTextureSlot(
MetaCoreEditorContext& editorContext,
MetaCoreIAssetDatabaseService& assetDatabaseService,
const MetaCoreIAssetEditingService* assetEditingService,
const char* label,
const char* id,
MetaCoreAssetGuid& textureGuid,
const std::vector<MetaCoreMaterialTextureChoice>& textureChoices
) {
bool changed = false;
ImGui::PushID(id);
if (ImGui::BeginTable("TextureSlot", 2, ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 118.0F);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(label);
ImGui::TableNextColumn();
const MetaCoreMaterialTextureBindingState binding =
MetaCoreResolveMaterialTextureBinding(assetDatabaseService, assetEditingService, textureGuid);
ImGui::PushStyleColor(ImGuiCol_Button, binding.Missing ? ImVec4(0.34F, 0.13F, 0.12F, 1.0F) : ImVec4(0.12F, 0.13F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, binding.Missing ? ImVec4(0.44F, 0.18F, 0.16F, 1.0F) : ImVec4(0.18F, 0.20F, 0.25F, 1.0F));
const bool openPicker =
ImGui::Button((binding.DisplayName + "##TexturePickerButton").c_str(), ImVec2(std::max(ImGui::GetContentRegionAvail().x - 98.0F, 80.0F), 0.0F));
ImGui::PopStyleColor(2);
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MetaCoreProjectAssetDragDropPayloadType);
payload != nullptr && payload->DataSize == sizeof(MetaCoreProjectAssetDragDropPayload)) {
const auto* assetPayload = static_cast<const MetaCoreProjectAssetDragDropPayload*>(payload->Data);
if (assetPayload != nullptr &&
!assetPayload->IsDirectory &&
assetPayload->AssetGuid.IsValid() &&
std::strcmp(assetPayload->AssetType, "texture") == 0) {
textureGuid = assetPayload->AssetGuid;
changed = true;
}
}
ImGui::EndDragDropTarget();
}
if (openPicker) {
ImGui::OpenPopup("TexturePickerPopup");
}
if (ImGui::BeginPopup("TexturePickerPopup")) {
if (ImGui::Selectable("无贴图", !textureGuid.IsValid())) {
textureGuid = MetaCoreAssetGuid{};
changed = true;
}
for (const auto& choice : textureChoices) {
const bool selected = choice.AssetGuid == textureGuid;
if (ImGui::Selectable(choice.Label.c_str(), selected)) {
textureGuid = choice.AssetGuid;
changed = true;
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::SmallButton("清空")) {
if (textureGuid.IsValid()) {
textureGuid = MetaCoreAssetGuid{};
changed = true;
}
}
ImGui::SameLine();
const bool canLocate = binding.LocateAsset.has_value();
ImGui::BeginDisabled(!canLocate);
if (ImGui::SmallButton("定位")) {
if (binding.LocateAsset.has_value()) {
const MetaCoreAssetRecord& textureRecord = *binding.LocateAsset;
editorContext.SelectAsset(MetaCoreSelectedAssetState{
textureRecord.Guid,
textureRecord.RelativePath,
textureRecord.Type,
textureRecord.StorageKind
});
if (binding.LocateSubId.empty()) {
editorContext.ClearSelectedAssetSubId();
} else {
editorContext.SetSelectedAssetSubId(binding.LocateSubId);
}
editorContext.ClearSelection();
}
}
ImGui::EndDisabled();
if (binding.Missing) {
ImGui::TextColored(ImVec4(0.95F, 0.38F, 0.32F, 1.0F), "贴图缺失: %s", textureGuid.ToString().c_str());
} else if (!binding.DetailText.empty()) {
ImGui::TextDisabled("%s", binding.DetailText.c_str());
}
ImGui::EndTable();
}
ImGui::PopID();
return changed;
}
[[nodiscard]] bool MetaCoreDrawMaterialVisualEditor(
MetaCoreEditorContext& editorContext,
MetaCoreIAssetDatabaseService& assetDatabaseService,
MetaCoreMaterialAssetDocument& materialDocument,
const char* sourceLabel,
const std::filesystem::path& displayPath,
std::string_view idSuffix
) {
bool changed = false;
const std::string suffix(idSuffix);
const auto textureChoices = MetaCoreCollectMaterialTextureChoices(assetDatabaseService);
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
ImGui::PushID(suffix.c_str());
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F));
ImGui::TextUnformatted(materialDocument.Name.empty() ? "<未命名材质>" : materialDocument.Name.c_str());
ImGui::SameLine();
ImGui::TextDisabled("%s", sourceLabel);
ImGui::TextDisabled("GUID: %s", materialDocument.AssetGuid.ToString().c_str());
if (!displayPath.empty()) {
ImGui::TextDisabled("路径: %s", displayPath.generic_string().c_str());
}
ImGui::Separator();
const float availableWidth = ImGui::GetContentRegionAvail().x;
const bool useTwoColumns = availableWidth >= 520.0F;
if (useTwoColumns) {
ImGui::BeginTable("MaterialVisualLayout", 2, ImGuiTableFlags_SizingStretchProp);
ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch, 0.62F);
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.38F);
ImGui::TableNextRow();
ImGui::TableNextColumn();
}
if (ImGui::CollapsingHeader("基础参数", ImGuiTreeNodeFlags_DefaultOpen)) {
std::array<char, 256> nameBuffer{};
std::snprintf(nameBuffer.data(), nameBuffer.size(), "%s", materialDocument.Name.c_str());
ImGui::SetNextItemWidth(-1.0F);
if (ImGui::InputText("名称", nameBuffer.data(), nameBuffer.size())) {
materialDocument.Name = nameBuffer.data();
changed = true;
}
const char* shaderItems[] = {"PBR 金属粗糙度"};
int shaderIndex = 0;
ImGui::BeginDisabled();
ImGui::Combo("着色模型", &shaderIndex, shaderItems, IM_ARRAYSIZE(shaderItems));
ImGui::EndDisabled();
float baseColor[3] = {materialDocument.BaseColor.r, materialDocument.BaseColor.g, materialDocument.BaseColor.b};
if (ImGui::ColorEdit3("基础颜色", baseColor)) {
materialDocument.BaseColor = {baseColor[0], baseColor[1], baseColor[2]};
changed = true;
}
if (ImGui::SliderFloat("金属度", &materialDocument.Metallic, 0.0F, 1.0F, "%.3f")) {
changed = true;
}
if (ImGui::SliderFloat("粗糙度", &materialDocument.Roughness, 0.0F, 1.0F, "%.3f")) {
changed = true;
}
float emissiveColor[3] = {materialDocument.EmissiveColor.r, materialDocument.EmissiveColor.g, materialDocument.EmissiveColor.b};
if (ImGui::ColorEdit3("发光颜色", emissiveColor)) {
materialDocument.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]};
changed = true;
}
}
if (ImGui::CollapsingHeader("贴图", ImGuiTreeNodeFlags_DefaultOpen)) {
changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "基础颜色", "BaseColorTexture", materialDocument.BaseColorTexture, textureChoices);
changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "法线", "NormalTexture", materialDocument.NormalTexture, textureChoices);
changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "金属粗糙度", "MetallicRoughnessTexture", materialDocument.MetallicRoughnessTexture, textureChoices);
changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "遮蔽 AO", "AoTexture", materialDocument.AoTexture, textureChoices);
changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "发光", "EmissiveTexture", materialDocument.EmissiveTexture, textureChoices);
}
if (ImGui::CollapsingHeader("透明与渲染选项", ImGuiTreeNodeFlags_DefaultOpen)) {
const char* alphaItems[] = {"不透明", "镂空裁剪", "透明混合"};
int alphaIndex = static_cast<int>(materialDocument.AlphaMode);
if (ImGui::Combo("透明模式", &alphaIndex, alphaItems, IM_ARRAYSIZE(alphaItems))) {
materialDocument.AlphaMode = static_cast<MetaCoreMaterialAlphaMode>(std::clamp(alphaIndex, 0, 2));
changed = true;
}
if (materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Mask) {
if (ImGui::SliderFloat("裁剪阈值", &materialDocument.AlphaCutoff, 0.0F, 1.0F, "%.3f")) {
changed = true;
}
}
if (ImGui::Checkbox("双面渲染", &materialDocument.DoubleSided)) {
changed = true;
}
}
if (ImGui::CollapsingHeader("高级信息")) {
ImGui::TextDisabled("着色模型: %s", MetaCoreMaterialShaderModelLabel(materialDocument.ShaderModel));
ImGui::TextDisabled("透明模式: %s", MetaCoreMaterialAlphaModeLabel(materialDocument.AlphaMode));
ImGui::TextDisabled("StableImportKey: %s", materialDocument.StableImportKey.empty() ? "<无>" : materialDocument.StableImportKey.c_str());
}
if (useTwoColumns) {
ImGui::TableNextColumn();
MetaCoreDrawMaterialPreviewCard(materialDocument);
ImGui::EndTable();
} else {
ImGui::Separator();
MetaCoreDrawMaterialPreviewCard(materialDocument);
}
ImGui::PopStyleVar();
ImGui::PopID();
return changed;
}
void DrawGeneratedMaterialDetails(
MetaCoreEditorContext& editorContext,
const MetaCoreMaterialAssetDocument& selectedMaterial
@ -2707,84 +3261,29 @@ void DrawGeneratedMaterialDetails(
MetaCoreMaterialAssetDocument materialAsset = *loadedMaterial;
ImGui::Separator();
ImGui::TextDisabled("MATERIAL RESOURCE");
ImGui::Text("Name: %s", materialAsset.Name.empty() ? "<Unnamed>" : materialAsset.Name.c_str());
ImGui::TextDisabled("Guid: %s", materialAsset.AssetGuid.ToString().c_str());
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabaseService == nullptr) {
ImGui::TextDisabled("资产数据库不可用。");
return;
}
const auto references = FindSceneObjectsUsingMaterial(editorContext, materialAsset.AssetGuid);
ImGui::Text("Scene References: %d", static_cast<int>(references.size()));
ImGui::TextDisabled("场景引用: %d", static_cast<int>(references.size()));
if (!references.empty()) {
if (ImGui::Button("Highlight Scene Objects##SelectedMaterial")) {
if (ImGui::Button("高亮引用对象##SelectedMaterial")) {
editorContext.SetSelection(references, references.front());
}
DrawReferencedObjectList(editorContext, references, "SelectedMaterialRefs");
}
bool modified = false;
float baseColor[3] = {materialAsset.BaseColor.x, materialAsset.BaseColor.y, materialAsset.BaseColor.z};
if (ImGui::ColorEdit3("Base Color##SelectedMaterial", baseColor)) {
materialAsset.BaseColor = {baseColor[0], baseColor[1], baseColor[2]};
modified = true;
}
if (ImGui::SliderFloat("Metallic##SelectedMaterial", &materialAsset.Metallic, 0.0F, 1.0F)) {
modified = true;
}
if (ImGui::SliderFloat("Roughness##SelectedMaterial", &materialAsset.Roughness, 0.0F, 1.0F)) {
modified = true;
}
if (ImGui::Checkbox("Double Sided##SelectedMaterial", &materialAsset.DoubleSided)) {
modified = true;
}
const char* alphaModeItems[] = {"Opaque", "Mask", "Blend"};
int alphaModeIndex = static_cast<int>(materialAsset.AlphaMode);
if (ImGui::Combo("Alpha Mode##SelectedMaterial", &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) {
materialAsset.AlphaMode = static_cast<MetaCoreMaterialAlphaMode>(alphaModeIndex);
modified = true;
}
if (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask) {
if (ImGui::SliderFloat("Alpha Cutoff##SelectedMaterial", &materialAsset.AlphaCutoff, 0.0F, 1.0F)) {
modified = true;
}
}
const auto textureChoices = MetaCoreCollectTextureAssetChoices(editorContext);
MetaCoreAssetGuid baseColorTexture = materialAsset.BaseColorTexture;
if (MetaCoreDrawGeneratedAssetCombo("Base Color Texture##SelectedMaterial", textureChoices, baseColorTexture)) {
materialAsset.BaseColorTexture = baseColorTexture;
modified = true;
}
DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "BaseColor", materialAsset.BaseColorTexture);
MetaCoreAssetGuid normalTexture = materialAsset.NormalTexture;
if (MetaCoreDrawGeneratedAssetCombo("Normal Texture##SelectedMaterial", textureChoices, normalTexture)) {
materialAsset.NormalTexture = normalTexture;
modified = true;
}
DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "Normal", materialAsset.NormalTexture);
MetaCoreAssetGuid metalRoughTexture = materialAsset.MetallicRoughnessTexture;
if (MetaCoreDrawGeneratedAssetCombo("MetalRough Texture##SelectedMaterial", textureChoices, metalRoughTexture)) {
materialAsset.MetallicRoughnessTexture = metalRoughTexture;
modified = true;
}
DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "MetalRough", materialAsset.MetallicRoughnessTexture);
MetaCoreAssetGuid emissiveTexture = materialAsset.EmissiveTexture;
if (MetaCoreDrawGeneratedAssetCombo("Emissive Texture##SelectedMaterial", textureChoices, emissiveTexture)) {
materialAsset.EmissiveTexture = emissiveTexture;
modified = true;
}
DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "Emissive", materialAsset.EmissiveTexture);
MetaCoreAssetGuid aoTexture = materialAsset.AoTexture;
if (MetaCoreDrawGeneratedAssetCombo("AO Texture##SelectedMaterial", textureChoices, aoTexture)) {
materialAsset.AoTexture = aoTexture;
modified = true;
}
DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "AO", materialAsset.AoTexture);
float emissiveColor[3] = {materialAsset.EmissiveColor.x, materialAsset.EmissiveColor.y, materialAsset.EmissiveColor.z};
if (ImGui::ColorEdit3("Emissive Color##SelectedMaterial", emissiveColor)) {
materialAsset.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]};
modified = true;
}
const bool modified = MetaCoreDrawMaterialVisualEditor(
editorContext,
*assetDatabaseService,
materialAsset,
"模型生成材质",
resolvedMaterial->SourceAsset.RelativePath,
"GeneratedMaterial"
);
if (modified) {
if (assetEditingService->SaveMaterialAsset(materialAsset.AssetGuid, materialAsset)) {
@ -2792,7 +3291,7 @@ void DrawGeneratedMaterialDetails(
modelDocument.has_value()) {
MetaCoreApplyMaterialPreviewToScene(editorContext, *modelDocument);
}
ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "保存材质资源修改");
ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "自动保存材质资源");
} else {
ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质资源保存失败");
}
@ -3912,107 +4411,41 @@ void DrawMaterialAssetDetails(
MetaCoreIAssetDatabaseService& assetDatabaseService,
MetaCoreIReflectionRegistry& reflectionRegistry
) {
(void)reflectionRegistry;
const MetaCoreSelectedAssetState& selectedAsset = editorContext.GetSelectedAsset();
const auto assetRecord = assetDatabaseService.FindAssetByGuid(selectedAsset.Guid);
if (!assetRecord.has_value()) return;
const std::filesystem::path absolutePath = assetDatabaseService.GetProjectDescriptor().RootPath / assetRecord->RelativePath;
auto materialDocOpt = MetaCoreSceneSerializer::LoadMaterialFromJson(absolutePath, reflectionRegistry.GetTypeRegistry());
if (!materialDocOpt.has_value()) {
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "加载材质失败!");
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
if (assetEditingService == nullptr) {
ImGui::TextColored(ImVec4(1.0F, 0.4F, 0.4F, 1.0F), "材质编辑服务不可用。");
return;
}
MetaCoreMaterialAssetDocument materialDoc = std::move(*materialDocOpt);
auto materialDocOpt = assetEditingService->LoadMaterialAsset(selectedAsset.Guid);
if (!materialDocOpt.has_value()) {
ImGui::TextColored(ImVec4(1.0F, 0.4F, 0.4F, 1.0F), "加载材质失败。");
return;
}
MetaCoreMaterialAssetDocument materialDoc = *materialDocOpt;
if (!materialDoc.AssetGuid.IsValid()) {
materialDoc.AssetGuid = selectedAsset.Guid;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 4.0F));
if (ImGui::BeginChild("MaterialInspector", ImVec2(0, 0), true, 0)) {
ImGui::TextDisabled("MATERIAL ASSET");
ImGui::Separator();
ImGui::Spacing();
bool changed = false;
float color[3] = { materialDoc.BaseColor.r, materialDoc.BaseColor.g, materialDoc.BaseColor.b };
if (ImGui::ColorEdit3("基础颜色 (Base Color)", color)) {
materialDoc.BaseColor = glm::vec3(color[0], color[1], color[2]);
changed = true;
}
float metallic = materialDoc.Metallic;
if (ImGui::SliderFloat("金属度 (Metallic)", &metallic, 0.0f, 1.0f)) {
materialDoc.Metallic = metallic;
changed = true;
}
float roughness = materialDoc.Roughness;
if (ImGui::SliderFloat("粗糙度 (Roughness)", &roughness, 0.0f, 1.0f)) {
materialDoc.Roughness = roughness;
changed = true;
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Text("贴图绑定 (Textures)");
auto drawTextureCombo = [&](const char* label, MetaCoreAssetGuid& currentGuid) {
std::string previewName = "None";
if (currentGuid.IsValid()) {
const auto record = assetDatabaseService.FindAssetByGuid(currentGuid);
if (record.has_value()) {
previewName = record->RelativePath.filename().string();
} else {
previewName = "Missing (" + currentGuid.ToString() + ")";
}
}
if (ImGui::BeginCombo(label, previewName.c_str())) {
if (ImGui::Selectable("None", !currentGuid.IsValid())) {
if (currentGuid.IsValid()) {
currentGuid = MetaCoreAssetGuid{};
changed = true;
}
}
for (const auto& a : assetDatabaseService.GetAssetRegistry()) {
if (a.Type == "texture") {
const bool selected = (currentGuid == a.Guid);
const std::string name = a.RelativePath.filename().string();
if (ImGui::Selectable(name.c_str(), selected)) {
if (currentGuid != a.Guid) {
currentGuid = a.Guid;
changed = true;
}
}
}
}
ImGui::EndCombo();
}
};
drawTextureCombo("基础颜色贴图 (Base Color Texture)", materialDoc.BaseColorTexture);
drawTextureCombo("法线贴图 (Normal Texture)", materialDoc.NormalTexture);
drawTextureCombo("金属粗糙度贴图 (Metallic Roughness)", materialDoc.MetallicRoughnessTexture);
drawTextureCombo("遮蔽贴图 (Occlusion/Ao Texture)", materialDoc.AoTexture);
const bool changed = MetaCoreDrawMaterialVisualEditor(
editorContext,
assetDatabaseService,
materialDoc,
"独立材质",
assetRecord->RelativePath,
"StandaloneMaterial"
);
if (changed) {
if (MetaCoreSceneSerializer::SaveMaterialToJson(absolutePath, materialDoc, reflectionRegistry.GetTypeRegistry())) {
const std::filesystem::path metaPath = assetDatabaseService.GetProjectDescriptor().RootPath / MetaCoreBuildMetaPath(assetRecord->RelativePath);
const std::uint64_t newHash = MetaCoreHashFile(absolutePath).value_or(0);
std::error_code ec;
if (std::filesystem::exists(metaPath, ec)) {
try {
std::ifstream input(metaPath);
nlohmann::json json;
input >> json;
input.close();
json["source_hash"] = newHash;
std::ofstream output(metaPath);
output << json.dump(4);
output.close();
} catch (...) {}
}
assetDatabaseService.Refresh();
if (assetEditingService->SaveMaterialAsset(selectedAsset.Guid, materialDoc)) {
MetaCoreApplyStandaloneMaterialPreviewToScene(editorContext, assetDatabaseService, materialDoc);
ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已自动保存材质资源");
} else {
ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质资源保存失败");
}
}
}

View File

@ -5,6 +5,7 @@
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreRender/MetaCoreImGuiHelper.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "stb/stb_image.h"
#include <imgui.h>
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
@ -21,7 +22,9 @@
#include <filament/RenderTarget.h>
#include <filament/LightManager.h>
#include <filament/RenderableManager.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/TextureSampler.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
@ -306,6 +309,7 @@ public:
}
}
AssetDuplicatedMaterials_.clear();
DestroyMaterialTextureCache();
for (auto& [id, entity] : SceneLightEntities_) {
if (Scene_) {
@ -391,9 +395,121 @@ public:
}
void SetProjectRootPath(const std::filesystem::path& projectRootPath) {
if (ProjectRootPath_ != projectRootPath) {
DestroyMaterialTextureCache();
}
ProjectRootPath_ = projectRootPath;
}
void DestroyMaterialTextureCache() {
if (Engine_) {
Engine_->flushAndWait();
for (auto& [_, texture] : MaterialTextureCache_) {
if (texture != nullptr) {
Engine_->destroy(texture);
}
}
}
MaterialTextureCache_.clear();
}
[[nodiscard]] std::filesystem::path ResolveMaterialTexturePath(
const std::string& texturePath,
const std::string& sourceModelPath
) const {
if (texturePath.empty()) {
return {};
}
const std::filesystem::path rawPath(texturePath);
if (rawPath.is_absolute()) {
return std::filesystem::exists(rawPath) ? rawPath : std::filesystem::path{};
}
const std::filesystem::path projectRelativePath = ProjectRootPath_ / rawPath;
if (std::filesystem::exists(projectRelativePath)) {
return projectRelativePath;
}
if (!sourceModelPath.empty()) {
const std::filesystem::path modelRelativePath(sourceModelPath);
const std::filesystem::path modelDirectory = modelRelativePath.parent_path();
if (!modelDirectory.empty()) {
const std::filesystem::path modelRelativeTexturePath = ProjectRootPath_ / modelDirectory / rawPath;
if (std::filesystem::exists(modelRelativeTexturePath)) {
return modelRelativeTexturePath;
}
}
}
const std::filesystem::path assetsRelativePath = ProjectRootPath_ / "Assets" / rawPath;
if (std::filesystem::exists(assetsRelativePath)) {
return assetsRelativePath;
}
return {};
}
[[nodiscard]] filament::Texture* LoadMaterialTexture(
const std::string& texturePath,
const std::string& sourceModelPath,
bool srgb
) {
if (Engine_ == nullptr || texturePath.empty()) {
return nullptr;
}
const std::filesystem::path absolutePath = ResolveMaterialTexturePath(texturePath, sourceModelPath);
if (absolutePath.empty()) {
return nullptr;
}
const std::string cacheKey = absolutePath.lexically_normal().generic_string() + (srgb ? "|srgb" : "|linear");
if (const auto cacheIterator = MaterialTextureCache_.find(cacheKey);
cacheIterator != MaterialTextureCache_.end()) {
return cacheIterator->second;
}
int width = 0;
int height = 0;
int channels = 0;
unsigned char* pixels = stbi_load(absolutePath.string().c_str(), &width, &height, &channels, 4);
if (pixels == nullptr || width <= 0 || height <= 0) {
if (pixels != nullptr) {
stbi_image_free(pixels);
}
MetaCoreFilamentDebugLog("Material texture load failed path=\"" + absolutePath.generic_string() + "\"");
return nullptr;
}
const std::size_t byteSize = static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4U;
filament::Texture::PixelBufferDescriptor pixelBuffer(
pixels,
byteSize,
filament::Texture::Format::RGBA,
filament::Texture::Type::UBYTE,
[](void* buffer, size_t, void*) {
stbi_image_free(buffer);
}
);
filament::Texture* texture = filament::Texture::Builder()
.width(static_cast<std::uint32_t>(width))
.height(static_cast<std::uint32_t>(height))
.levels(1)
.sampler(filament::Texture::Sampler::SAMPLER_2D)
.format(srgb ? filament::Texture::InternalFormat::SRGB8_A8 : filament::Texture::InternalFormat::RGBA8)
.build(*Engine_);
if (texture == nullptr) {
return nullptr;
}
texture->setImage(*Engine_, 0, std::move(pixelBuffer));
MaterialTextureCache_[cacheKey] = texture;
return texture;
}
void ParseModelHierarchyToEcs(MetaCoreScene& scene, MetaCoreGameObject& parentObject, filament::gltfio::FilamentAsset* asset) {
if (!asset) return;
@ -1015,36 +1131,124 @@ public:
}
}
// 5. 同步所有存活网格的材质属性,例如 BaseColor
const auto setFloatParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, float value) {
const filament::Material* material = materialInstance.getMaterial();
if (material != nullptr && material->hasParameter(name)) {
materialInstance.setParameter(name, value);
}
};
const auto setFloat3ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float3 value) {
const filament::Material* material = materialInstance.getMaterial();
if (material != nullptr && material->hasParameter(name)) {
materialInstance.setParameter(name, value);
}
};
const auto setFloat4ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float4 value) {
const filament::Material* material = materialInstance.getMaterial();
if (material != nullptr && material->hasParameter(name)) {
materialInstance.setParameter(name, value);
}
};
const auto setTextureParameterIfPresent = [](
filament::MaterialInstance& materialInstance,
const char* name,
filament::Texture* texture,
const filament::TextureSampler& sampler
) -> bool {
const filament::Material* material = materialInstance.getMaterial();
if (material == nullptr || !material->hasParameter(name)) {
return false;
}
materialInstance.setParameter(name, texture, sampler);
return true;
};
// 5. 同步所有存活网格的材质预览属性。MaterialInstance 参数必须在渲染前更新。
auto& rm = Engine_->getRenderableManager();
const filament::TextureSampler materialSampler(
filament::TextureSampler::MinFilter::LINEAR,
filament::TextureSampler::MagFilter::LINEAR,
filament::TextureSampler::WrapMode::REPEAT
);
for (const auto& renderable : snapshot.Renderables) {
auto itEntity = ObjectToFilamentEntity_.find(renderable.ObjectId);
if (itEntity == ObjectToFilamentEntity_.end()) {
continue;
}
utils::Entity entity = itEntity->second.second;
if (!entity) {
std::vector<utils::Entity> materialTargetEntities;
CollectRenderableDescendants(itEntity->second.first, itEntity->second.second, materialTargetEntities);
if (materialTargetEntities.empty()) {
continue;
}
auto instance = rm.getInstance(entity);
if (!instance) {
continue;
}
filament::Texture* baseColorTexture = LoadMaterialTexture(
renderable.BaseColorTexturePath,
renderable.SourceModelPath,
true
);
filament::Texture* metallicRoughnessTexture = LoadMaterialTexture(
renderable.MetallicRoughnessTexturePath,
renderable.SourceModelPath,
false
);
filament::Texture* normalTexture = LoadMaterialTexture(
renderable.NormalTexturePath,
renderable.SourceModelPath,
false
);
filament::Texture* emissiveTexture = LoadMaterialTexture(
renderable.EmissiveTexturePath,
renderable.SourceModelPath,
true
);
filament::Texture* aoTexture = LoadMaterialTexture(
renderable.AoTexturePath,
renderable.SourceModelPath,
false
);
// 遍历所有 primitive获取材质实例并设置其 baseColorFactor 属性
size_t primCount = rm.getPrimitiveCount(instance);
for (size_t primIndex = 0; primIndex < primCount; ++primIndex) {
filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex);
if (matInst) {
// BaseColor 包含 RGB以 float4 传递Alpha 设为 1.0
filament::math::float4 baseColorVec{
renderable.BaseColor.x,
renderable.BaseColor.y,
renderable.BaseColor.z,
1.0f
};
matInst->setParameter("baseColorFactor", baseColorVec);
for (utils::Entity materialTargetEntity : materialTargetEntities) {
const auto instance = rm.getInstance(materialTargetEntity);
if (!instance) {
continue;
}
// 遍历所有 primitive获取材质实例并设置材质预览属性。
const size_t primCount = rm.getPrimitiveCount(instance);
for (size_t primIndex = 0; primIndex < primCount; ++primIndex) {
filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex);
if (matInst) {
filament::math::float4 baseColorVec{
renderable.BaseColor.x,
renderable.BaseColor.y,
renderable.BaseColor.z,
1.0f
};
setFloat4ParameterIfPresent(*matInst, "baseColorFactor", baseColorVec);
setFloatParameterIfPresent(*matInst, "metallicFactor", renderable.Metallic);
setFloatParameterIfPresent(*matInst, "roughnessFactor", renderable.Roughness);
setFloat3ParameterIfPresent(
*matInst,
"emissiveFactor",
filament::math::float3{
renderable.EmissiveColor.x,
renderable.EmissiveColor.y,
renderable.EmissiveColor.z
}
);
if (renderable.AlphaMode == MetaCoreMeshAlphaMode::Mask) {
setFloatParameterIfPresent(*matInst, "alphaCutoff", renderable.AlphaCutoff);
}
setTextureParameterIfPresent(*matInst, "baseColorMap", baseColorTexture, materialSampler);
setTextureParameterIfPresent(*matInst, "metallicRoughnessMap", metallicRoughnessTexture, materialSampler);
setTextureParameterIfPresent(*matInst, "normalMap", normalTexture, materialSampler);
setTextureParameterIfPresent(*matInst, "emissiveMap", emissiveTexture, materialSampler);
if (!setTextureParameterIfPresent(*matInst, "occlusionMap", aoTexture, materialSampler)) {
if (!setTextureParameterIfPresent(*matInst, "aoMap", aoTexture, materialSampler)) {
setTextureParameterIfPresent(*matInst, "ambientOcclusionMap", aoTexture, materialSampler);
}
}
}
}
}
}
@ -1420,6 +1624,60 @@ private:
return parentToChildren;
}
void CollectRenderableDescendants(
filament::gltfio::FilamentAsset* asset,
utils::Entity rootEntity,
std::vector<utils::Entity>& outEntities
) const {
if (!Engine_ || rootEntity.isNull()) {
return;
}
auto& rm = Engine_->getRenderableManager();
auto& tm = Engine_->getTransformManager();
const auto addRenderable = [&](utils::Entity entity) {
if (entity.isNull() || !rm.getInstance(entity)) {
return;
}
if (std::find(outEntities.begin(), outEntities.end(), entity) == outEntities.end()) {
outEntities.push_back(entity);
}
};
addRenderable(rootEntity);
if (asset == nullptr) {
return;
}
const utils::Entity* entities = asset->getEntities();
const size_t entityCount = asset->getEntityCount();
for (size_t i = 0; i < entityCount; ++i) {
utils::Entity candidate = entities[i];
if (candidate.isNull() || candidate == rootEntity) {
continue;
}
utils::Entity current = candidate;
bool isDescendant = false;
while (!current.isNull()) {
if (current == rootEntity) {
isDescendant = true;
break;
}
const auto transformInstance = tm.getInstance(current);
if (!transformInstance) {
break;
}
current = tm.getParent(transformInstance);
}
if (isDescendant) {
addRenderable(candidate);
}
}
}
static filament::math::float3 ToFilamentFloat3(const glm::vec3& value) {
return filament::math::float3{ value.x, value.y, value.z };
}
@ -1565,6 +1823,7 @@ private:
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> HostRootSourceModelGuids_;
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> InvalidatedModelGuids_;
std::unordered_map<MetaCoreId, std::vector<filament::MaterialInstance*>> AssetDuplicatedMaterials_;
std::unordered_map<std::string, filament::Texture*> MaterialTextureCache_;
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;

View File

@ -180,7 +180,18 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
hostRoot.GetId(),
hostRoot.GetName(),
hostRoot.HasComponent<MetaCoreModelRootTag>(),
meshRenderer.BaseColor
meshRenderer.BaseColor,
meshRenderer.Metallic,
meshRenderer.Roughness,
meshRenderer.EmissiveColor,
meshRenderer.AlphaMode,
meshRenderer.AlphaCutoff,
meshRenderer.DoubleSided,
meshRenderer.BaseColorTexturePath,
meshRenderer.MetallicRoughnessTexturePath,
meshRenderer.NormalTexturePath,
meshRenderer.EmissiveTexturePath,
meshRenderer.AoTexturePath
});
}

View File

@ -36,6 +36,17 @@ struct MetaCoreRenderSyncRenderable {
std::string HostRootName{};
bool HostRootHasModelRootTag = false;
glm::vec3 BaseColor{1.0F, 1.0F, 1.0F};
float Metallic = 0.0F;
float Roughness = 1.0F;
glm::vec3 EmissiveColor{0.0F, 0.0F, 0.0F};
MetaCoreMeshAlphaMode AlphaMode = MetaCoreMeshAlphaMode::Opaque;
float AlphaCutoff = 0.5F;
bool DoubleSided = true;
std::string BaseColorTexturePath{};
std::string MetallicRoughnessTexturePath{};
std::string NormalTexturePath{};
std::string EmissiveTexturePath{};
std::string AoTexturePath{};
};
struct MetaCoreRenderSyncCamera {

View File

@ -652,6 +652,44 @@ void MetaCoreTestProjectAssetDatabaseOperations() {
MetaCoreExpect(materialRecord.has_value(), "材质应进入资产数据库");
MetaCoreExpect(materialRecord->Guid.IsValid(), "材质 GUID 应有效");
MetaCoreExpect(materialRecord->Type == "material", "材质资产类型应正确");
const auto assetEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetEditingService>();
MetaCoreExpect(assetEditingService != nullptr, "应解析到 AssetEditingService");
const std::filesystem::path materialMetaPath = tempProjectRoot / (materialPath.generic_string() + ".mcmeta");
nlohmann::json oldMaterialMeta;
{
std::ifstream input(materialMetaPath);
input >> oldMaterialMeta;
}
const std::uint64_t oldMaterialHash = oldMaterialMeta.value("source_hash", 0ULL);
auto editableMaterial = assetEditingService->LoadMaterialAsset(materialRecord->Guid);
MetaCoreExpect(editableMaterial.has_value(), "AssetEditingService 应能加载独立材质");
editableMaterial->Name = "PanelMaterialEdited";
editableMaterial->BaseColor = glm::vec3(0.12F, 0.34F, 0.56F);
editableMaterial->Metallic = 0.42F;
editableMaterial->Roughness = 0.68F;
editableMaterial->DoubleSided = true;
editableMaterial->AlphaMode = MetaCore::MetaCoreMaterialAlphaMode::Mask;
editableMaterial->AlphaCutoff = 0.31F;
editableMaterial->EmissiveColor = glm::vec3(0.03F, 0.04F, 0.05F);
MetaCoreExpect(assetEditingService->SaveMaterialAsset(materialRecord->Guid, *editableMaterial), "AssetEditingService 应能保存独立材质");
const auto reloadedMaterial = assetEditingService->LoadMaterialAsset(materialRecord->Guid);
MetaCoreExpect(reloadedMaterial.has_value(), "保存后应能重新加载独立材质");
MetaCoreExpect(reloadedMaterial->Name == "PanelMaterialEdited", "材质名称应 round-trip");
MetaCoreExpectVec3Near(reloadedMaterial->BaseColor, glm::vec3(0.12F, 0.34F, 0.56F), "材质 BaseColor 应 round-trip");
MetaCoreExpect(std::abs(reloadedMaterial->Metallic - 0.42F) < 0.0001F, "材质 Metallic 应 round-trip");
MetaCoreExpect(std::abs(reloadedMaterial->Roughness - 0.68F) < 0.0001F, "材质 Roughness 应 round-trip");
MetaCoreExpect(reloadedMaterial->DoubleSided, "材质 DoubleSided 应 round-trip");
MetaCoreExpect(reloadedMaterial->AlphaMode == MetaCore::MetaCoreMaterialAlphaMode::Mask, "材质 AlphaMode 应 round-trip");
MetaCoreExpect(std::abs(reloadedMaterial->AlphaCutoff - 0.31F) < 0.0001F, "材质 AlphaCutoff 应 round-trip");
nlohmann::json newMaterialMeta;
{
std::ifstream input(materialMetaPath);
input >> newMaterialMeta;
}
const std::uint64_t newMaterialHash = newMaterialMeta.value("source_hash", 0ULL);
MetaCoreExpect(newMaterialHash != 0ULL, "保存材质后 mcmeta source_hash 应有效");
MetaCoreExpect(newMaterialHash != oldMaterialHash, "保存材质后 mcmeta source_hash 应更新");
MetaCoreExpect(newMaterialHash == MetaCore::MetaCoreHashFile(tempProjectRoot / materialPath).value_or(0), "mcmeta source_hash 应匹配材质文件 hash");
const std::filesystem::path scenePath = std::filesystem::path("Scenes") / "PanelScene.mcscene.json";
MetaCoreExpect(assetDatabase->CreateEmptySceneAsset(scenePath, true), "应能创建空场景资源");
@ -1268,6 +1306,17 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
rootMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset;
rootMesh.SourceModelPath = "Assets/Models/SyncRoot.glb";
rootMesh.BaseColor = glm::vec3(0.5F, 0.6F, 0.7F);
rootMesh.Metallic = 0.35F;
rootMesh.Roughness = 0.45F;
rootMesh.EmissiveColor = glm::vec3(0.05F, 0.06F, 0.07F);
rootMesh.AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Mask;
rootMesh.AlphaCutoff = 0.42F;
rootMesh.DoubleSided = false;
rootMesh.BaseColorTexturePath = "Assets/Textures/SyncBaseColor.png";
rootMesh.MetallicRoughnessTexturePath = "Assets/Textures/SyncMetallicRoughness.png";
rootMesh.NormalTexturePath = "Assets/Textures/SyncNormal.png";
rootMesh.EmissiveTexturePath = "Assets/Textures/SyncEmissive.png";
rootMesh.AoTexturePath = "Assets/Textures/SyncAo.png";
MetaCore::MetaCoreGameObject child = scene.CreateGameObject("RenderChild", root.GetId());
child.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(4.0F, 5.0F, 6.0F);
@ -1300,6 +1349,17 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
if (renderable.ObjectId == root.GetId()) {
MetaCoreExpect(renderable.Visible == true, "RenderSync 根对象 Visible 应为 true");
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.5F, 0.6F, 0.7F), "RenderSync 根对象 BaseColor 应正确");
MetaCoreExpect(std::abs(renderable.Metallic - 0.35F) < 0.0001F, "RenderSync 根对象 Metallic 应正确");
MetaCoreExpect(std::abs(renderable.Roughness - 0.45F) < 0.0001F, "RenderSync 根对象 Roughness 应正确");
MetaCoreExpectVec3Near(renderable.EmissiveColor, glm::vec3(0.05F, 0.06F, 0.07F), "RenderSync 根对象 EmissiveColor 应正确");
MetaCoreExpect(renderable.AlphaMode == MetaCore::MetaCoreMeshAlphaMode::Mask, "RenderSync 根对象 AlphaMode 应正确");
MetaCoreExpect(std::abs(renderable.AlphaCutoff - 0.42F) < 0.0001F, "RenderSync 根对象 AlphaCutoff 应正确");
MetaCoreExpect(renderable.DoubleSided == false, "RenderSync 根对象 DoubleSided 应正确");
MetaCoreExpect(renderable.BaseColorTexturePath == "Assets/Textures/SyncBaseColor.png", "RenderSync 根对象 BaseColor 贴图路径应正确");
MetaCoreExpect(renderable.MetallicRoughnessTexturePath == "Assets/Textures/SyncMetallicRoughness.png", "RenderSync 根对象金属粗糙贴图路径应正确");
MetaCoreExpect(renderable.NormalTexturePath == "Assets/Textures/SyncNormal.png", "RenderSync 根对象法线贴图路径应正确");
MetaCoreExpect(renderable.EmissiveTexturePath == "Assets/Textures/SyncEmissive.png", "RenderSync 根对象自发光贴图路径应正确");
MetaCoreExpect(renderable.AoTexturePath == "Assets/Textures/SyncAo.png", "RenderSync 根对象 AO 贴图路径应正确");
} else if (renderable.ObjectId == child.GetId()) {
MetaCoreExpect(renderable.Visible == false, "RenderSync 子对象 Visible 应为 false");
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.1F, 0.2F, 0.3F), "RenderSync 子对象 BaseColor 应正确");
@ -1903,10 +1963,12 @@ void MetaCoreTestInstantiateMultiNodeModelAssetIntoScene() {
const auto sceneEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreISceneEditingService>();
const auto packageService = moduleRegistry.ResolveService<MetaCore::MetaCoreIPackageService>();
const auto reflectionRegistry = moduleRegistry.ResolveService<MetaCore::MetaCoreIReflectionRegistry>();
const auto assetEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetEditingService>();
MetaCoreExpect(assetDatabase != nullptr, "应解析到 AssetDatabaseService");
MetaCoreExpect(sceneEditingService != nullptr, "应解析到 SceneEditingService");
MetaCoreExpect(packageService != nullptr, "应解析到 PackageService");
MetaCoreExpect(reflectionRegistry != nullptr, "应解析到 ReflectionRegistry");
MetaCoreExpect(assetEditingService != nullptr, "应解析到 AssetEditingService");
const auto modelRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Assembly.gltf");
MetaCoreExpect(modelRecord.has_value(), "应能找到导入后的多节点模型资源");
@ -1985,6 +2047,36 @@ void MetaCoreTestInstantiateMultiNodeModelAssetIntoScene() {
packageService->WritePackage(tempProjectRoot / modelRecord->PackagePath, std::move(package)),
"应能写入多节点模型资源包"
);
MetaCoreExpect(assetDatabase->Refresh(), "写入模型包后应能刷新资产数据库");
{
MetaCore::MetaCoreMaterialAssetDocument generatedMaterial = materialAsset;
const MetaCore::MetaCoreAssetGuid generatedMaterialGuid = generatedMaterial.AssetGuid;
generatedMaterial.Name = "GeneratedMaterialEdited";
generatedMaterial.BaseColor = glm::vec3(0.8F, 0.2F, 0.1F);
generatedMaterial.Metallic = 0.77F;
generatedMaterial.Roughness = 0.22F;
generatedMaterial.DoubleSided = true;
MetaCoreExpect(assetEditingService->SaveMaterialAsset(generatedMaterialGuid, generatedMaterial), "应能保存模型生成材质");
const auto reloadedGeneratedMaterial = assetEditingService->LoadMaterialAsset(generatedMaterialGuid);
MetaCoreExpect(reloadedGeneratedMaterial.has_value(), "保存后应能重新加载模型生成材质");
MetaCoreExpect(reloadedGeneratedMaterial->AssetGuid == generatedMaterialGuid, "保存模型生成材质应保持 GUID 不变");
MetaCoreExpect(reloadedGeneratedMaterial->Name == "GeneratedMaterialEdited", "模型生成材质名称应写回");
MetaCoreExpectVec3Near(reloadedGeneratedMaterial->BaseColor, glm::vec3(0.8F, 0.2F, 0.1F), "模型生成材质 BaseColor 应写回");
MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Metallic - 0.77F) < 0.0001F, "模型生成材质 Metallic 应写回");
MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Roughness - 0.22F) < 0.0001F, "模型生成材质 Roughness 应写回");
const auto reloadedModelAfterMaterialEdit = assetEditingService->LoadModelAsset(modelRecord->Guid);
MetaCoreExpect(reloadedModelAfterMaterialEdit.has_value(), "保存生成材质后应能重新加载源模型");
const auto generatedMaterialIterator = std::find_if(
reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.begin(),
reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.end(),
[&](const MetaCore::MetaCoreMaterialAssetDocument& material) {
return material.AssetGuid == generatedMaterialGuid;
}
);
MetaCoreExpect(generatedMaterialIterator != reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.end(), "源模型中应保留已编辑的生成材质");
MetaCoreExpect(generatedMaterialIterator->Name == "GeneratedMaterialEdited", "源模型生成材质文档应更新");
}
const auto instantiatedRootId = sceneEditingService->InstantiateModelAsset(editorContext, modelRecord->Guid, std::nullopt);
MetaCoreExpect(instantiatedRootId.has_value(), "应能将多节点模型实例化到场景");