diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h index dd198a7179..d9ae8099da 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ { @@ -23,7 +24,7 @@ namespace AZ using MaterialAssignmentLodIndex = AZ::u64; //! MaterialAssignmentId is used to address available and overridable material slots on a model. - //! The LOD and one of the model's original material asset IDs are used as coordinates that identify + //! The LOD and one of the model's original material slot IDs are used as coordinates that identify //! a specific material slot or a set of slots matching either. struct MaterialAssignmentId final { @@ -33,41 +34,39 @@ namespace AZ MaterialAssignmentId() = default; - MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, const AZ::Data::AssetId& materialAssetId); + MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Create an ID that maps to all material slots, regardless of asset ID or LOD, effectively applying to an entire model. + //! Create an ID that maps to all material slots, regardless of slot ID or LOD, effectively applying to an entire model. static MaterialAssignmentId CreateDefault(); - //! Create an ID that maps to all material slots with a corresponding asset ID, regardless of LOD. - static MaterialAssignmentId CreateFromAssetOnly(AZ::Data::AssetId materialAssetId); + //! Create an ID that maps to all material slots with a corresponding slot ID, regardless of LOD. + static MaterialAssignmentId CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Create an ID that maps to a specific material slot with a corresponding asset ID and LOD. - static MaterialAssignmentId CreateFromLodAndAsset(MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId); + //! Create an ID that maps to a specific material slot with a corresponding stable ID and LOD. + static MaterialAssignmentId CreateFromLodAndStableId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Returns true if the asset ID and LOD are invalid + //! Returns true if the slot stable ID and LOD are invalid, meaning this assignment applies to the entire model. bool IsDefault() const; - //! Returns true if the asset ID is valid and LOD is invalid - bool IsAssetOnly() const; + //! Returns true if the slot stable ID is valid and LOD is invalid, meaning this assignment applies to every LOD. + bool IsSlotIdOnly() const; - //! Returns true if the asset ID and LOD are both valid - bool IsLodAndAsset() const; + //! Returns true if the slot stable ID and LOD are both valid, meaning this assignment applies to a single material slot on a specific LOD. + bool IsLodAndSlotId() const; - //! Creates a string composed of the asset path and LOD + //! Creates a string describing all the details of the assignment ID AZStd::string ToString() const; - //! Creates a hash composed of the asset ID sub ID and LOD + //! Creates a hash composed of all elements of the assignment ID size_t GetHash() const; - //! Returns true if both asset ID sub IDs and LODs match bool operator==(const MaterialAssignmentId& rhs) const; - - //! Returns true if both asset ID sub IDs and LODs do not match bool operator!=(const MaterialAssignmentId& rhs) const; static constexpr MaterialAssignmentLodIndex NonLodIndex = -1; + MaterialAssignmentLodIndex m_lodIndex = NonLodIndex; - AZ::Data::AssetId m_materialAssetId = AZ::Data::AssetId(); + RPI::ModelMaterialSlot::StableId m_materialSlotStableId = RPI::ModelMaterialSlot::InvalidStableId; }; } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ec48d57d1a..9ae4f65304 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -123,7 +123,7 @@ namespace AZ } const MaterialAssignment& assetAssignment = - GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromAssetOnly(id.m_materialAssetId)); + GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromStableIdOnly(id.m_materialSlotStableId)); if (assetAssignment.m_materialInstance.get()) { return assetAssignment; @@ -152,11 +152,11 @@ namespace AZ { if (mesh.m_material) { - const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromAssetOnly(mesh.m_material->GetAssetId()); + const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromStableIdOnly(mesh.m_materialSlotStableId); materials[generalId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material); const MaterialAssignmentId specificId = - MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId()); + MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, mesh.m_materialSlotStableId); materials[specificId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp index 71dea0596f..8b6b0be237 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp @@ -19,9 +19,9 @@ namespace AZ if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("lodIndex", &MaterialAssignmentId::m_lodIndex) - ->Field("materialAssetId", &MaterialAssignmentId::m_materialAssetId) + ->Field("materialSlotStableId", &MaterialAssignmentId::m_materialSlotStableId) ; } @@ -33,75 +33,72 @@ namespace AZ ->Attribute(AZ::Script::Attributes::Module, "render") ->Constructor() ->Constructor() - ->Constructor() + ->Constructor() ->Method("IsDefault", &MaterialAssignmentId::IsDefault) - ->Method("IsAssetOnly", &MaterialAssignmentId::IsAssetOnly) - ->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndAsset) + ->Method("IsAssetOnly", &MaterialAssignmentId::IsSlotIdOnly) // Included for compatibility. Use "IsSlotIdOnly" instead. + ->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndSlotId) // Included for compatibility. Use "IsLodAndSlotId" instead. + ->Method("IsSlotIdOnly", &MaterialAssignmentId::IsSlotIdOnly) + ->Method("IsLodAndSlotId", &MaterialAssignmentId::IsLodAndSlotId) ->Method("ToString", &MaterialAssignmentId::ToString) ->Property("lodIndex", BehaviorValueProperty(&MaterialAssignmentId::m_lodIndex)) - ->Property("materialAssetId", BehaviorValueProperty(&MaterialAssignmentId::m_materialAssetId)) + ->Property("materialSlotStableId", BehaviorValueProperty(&MaterialAssignmentId::m_materialSlotStableId)) ; } } - MaterialAssignmentId::MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, const AZ::Data::AssetId& materialAssetId) + MaterialAssignmentId::MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId) : m_lodIndex(lodIndex) - , m_materialAssetId(materialAssetId) + , m_materialSlotStableId(materialSlotStableId) { } MaterialAssignmentId MaterialAssignmentId::CreateDefault() { - return MaterialAssignmentId(NonLodIndex, AZ::Data::AssetId()); + return MaterialAssignmentId(NonLodIndex, RPI::ModelMaterialSlot::InvalidStableId); } - MaterialAssignmentId MaterialAssignmentId::CreateFromAssetOnly(AZ::Data::AssetId materialAssetId) + MaterialAssignmentId MaterialAssignmentId::CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId) { - return MaterialAssignmentId(NonLodIndex, materialAssetId); + return MaterialAssignmentId(NonLodIndex, materialSlotStableId); } - MaterialAssignmentId MaterialAssignmentId::CreateFromLodAndAsset( - MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId) + MaterialAssignmentId MaterialAssignmentId::CreateFromLodAndStableId( + MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId) { - return MaterialAssignmentId(lodIndex, materialAssetId); + return MaterialAssignmentId(lodIndex, materialSlotStableId); } bool MaterialAssignmentId::IsDefault() const { - return m_lodIndex == NonLodIndex && !m_materialAssetId.IsValid(); + return m_lodIndex == NonLodIndex && m_materialSlotStableId == RPI::ModelMaterialSlot::InvalidStableId; } - bool MaterialAssignmentId::IsAssetOnly() const + bool MaterialAssignmentId::IsSlotIdOnly() const { - return m_lodIndex == NonLodIndex && m_materialAssetId.IsValid(); + return m_lodIndex == NonLodIndex && m_materialSlotStableId != RPI::ModelMaterialSlot::InvalidStableId; } - bool MaterialAssignmentId::IsLodAndAsset() const + bool MaterialAssignmentId::IsLodAndSlotId() const { - return m_lodIndex != NonLodIndex && m_materialAssetId.IsValid(); + return m_lodIndex != NonLodIndex && m_materialSlotStableId != RPI::ModelMaterialSlot::InvalidStableId; } AZStd::string MaterialAssignmentId::ToString() const { - AZStd::string assetPathString; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_materialAssetId); - AZ::StringFunc::Path::StripPath(assetPathString); - AZ::StringFunc::Path::StripExtension(assetPathString); - return AZStd::string::format("%s:%llu", assetPathString.c_str(), m_lodIndex); + return AZStd::string::format("%u:%llu", m_materialSlotStableId, m_lodIndex); } size_t MaterialAssignmentId::GetHash() const { size_t seed = 0; AZStd::hash_combine(seed, m_lodIndex); - AZStd::hash_combine(seed, m_materialAssetId.m_subId); + AZStd::hash_combine(seed, m_materialSlotStableId); return seed; } bool MaterialAssignmentId::operator==(const MaterialAssignmentId& rhs) const { - return m_lodIndex == rhs.m_lodIndex && m_materialAssetId.m_subId == rhs.m_materialAssetId.m_subId; + return m_lodIndex == rhs.m_lodIndex && m_materialSlotStableId == rhs.m_materialSlotStableId; } bool MaterialAssignmentId::operator!=(const MaterialAssignmentId& rhs) const diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index c6362f3a5d..6e031aa853 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -589,18 +589,6 @@ namespace AZ { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - auto modelAsset = model->GetModelAsset(); - for (const auto& modelLodAsset : modelAsset->GetLodAssets()) - { - for (const auto& mesh : modelLodAsset->GetMeshes()) - { - if (mesh.GetMaterialAsset().GetStatus() != Data::AssetData::AssetStatus::Ready) - { - - } - } - } - m_model = model; const size_t modelLodCount = m_model->GetLodCount(); m_drawPacketListsByLod.resize(modelLodCount); @@ -644,10 +632,12 @@ namespace AZ for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex) { - Data::Instance material = modelLod.GetMeshes()[meshIndex].m_material; + const RPI::ModelLod::Mesh& mesh = modelLod.GetMeshes()[meshIndex]; + + Data::Instance material = mesh.m_material; // Determine if there is a material override specified for this sub mesh - const MaterialAssignmentId materialAssignmentId(modelLodIndex, material ? material->GetAssetId() : AZ::Data::AssetId()); + const MaterialAssignmentId materialAssignmentId(modelLodIndex, mesh.m_materialSlotStableId); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId); if (materialAssignment.m_materialInstance.get()) { @@ -790,7 +780,7 @@ namespace AZ // retrieve the material Data::Instance material = mesh.m_material; - const MaterialAssignmentId materialAssignmentId(rayTracingLod, material ? material->GetAssetId() : AZ::Data::AssetId()); + const MaterialAssignmentId materialAssignmentId(rayTracingLod, mesh.m_materialSlotStableId); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId); if (materialAssignment.m_materialInstance.get()) { diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp index 155b751272..640e30d0f6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp @@ -639,8 +639,13 @@ namespace AZ Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); + + // Create a separate material slot for each sub-mesh + AZ::RPI::ModelMaterialSlot materialSlot; + materialSlot.m_stableId = i; + materialSlot.m_defaultMaterialAsset = lod.m_subMeshProperties[i].m_material; - modelLodCreator.SetMeshMaterialAsset(lod.m_subMeshProperties[i].m_material); + modelLodCreator.SetMeshMaterialSlot(materialSlot); modelLodCreator.EndMesh(); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h index dcc4813b3d..36892d0027 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h @@ -72,6 +72,8 @@ namespace AZ RHI::IndexBufferView m_indexBufferView; StreamInfoList m_streamInfo; + + ModelMaterialSlot::StableId m_materialSlotStableId = ModelMaterialSlot::InvalidStableId; //! The default material assigned to the mesh by the asset. Data::Instance m_material; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h index b7414f73a2..891aec04b3 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h @@ -49,6 +49,9 @@ namespace AZ //! Returns the model-space axis aligned bounding box const AZ::Aabb& GetAabb() const; + + //! Returns the list of all ModelMaterialSlot's for the model, across all LODs. + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const; //! Returns the number of Lods in the model size_t GetLodCount() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h index 4e86b0e367..61e2ebeb05 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -84,8 +85,9 @@ namespace AZ //! Returns the number of indices in this mesh uint32_t GetIndexCount() const; - //! Returns the reference to material asset used by this mesh - const Data::Asset & GetMaterialAsset() const; + //! Returns the index of the material slot used by this mesh. + //! This indexes into the ModelLodAsset's material slot list. + size_t GetMaterialSlotIndex() const; //! Returns the name of this mesh const AZ::Name& GetName() const; @@ -124,7 +126,9 @@ namespace AZ AZ::Name m_name; AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); - Data::Asset m_materialAsset{ Data::AssetLoadBehavior::PreLoad }; + // Identifies the material that is used by this mesh. + // References material slot in the ModelLodAsset that owns this mesh; see ModelLodAsset::GetMaterialSlot(). + size_t m_materialSlotIndex = 0; // Both the buffer in m_indexBufferAssetView and the buffers in m_streamBufferInfo // may point to either unique buffers for the mesh or to consolidated @@ -143,11 +147,21 @@ namespace AZ //! Returns the model-space axis-aligned bounding box of all meshes in the lod const AZ::Aabb& GetAabb() const; + + //! Returns an array view into the collection of material slots available to this lod + AZStd::array_view GetMaterialSlots() const; + + //! Returns a specific material slot by index, with error checking. + //! The index can be retrieved from Mesh::GetMaterialSlotIndex(). + const ModelMaterialSlot& GetMaterialSlot(size_t slotIndex) const; + + //! Find a material slot with the given stableId, or returns null if it isn't found. + const ModelMaterialSlot* FindMaterialSlot(uint32_t stableId) const; private: AZStd::vector m_meshes; AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); - + // These buffers owned by the lod are the consolidated super buffers. // Meshes may either have views into these buffers or they may own // their own buffers. @@ -155,6 +169,13 @@ namespace AZ Data::Asset m_indexBuffer; AZStd::vector> m_streamBuffers; + // Lists all of the material slots that are used by this LOD. + // Note the same slot can appear in multiple LODs in the model, so that LODs don't have to refer back to the model asset. + AZStd::vector m_materialSlots; + + // A default ModelMaterialSlot to be returned upon error conditions. + ModelMaterialSlot m_fallbackSlot; + void AddMesh(const Mesh& mesh); void SetReady(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h index c3291e5c73..5672b49f68 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include @@ -45,9 +46,10 @@ namespace AZ //! Begin and BeginMesh must be called first. void SetMeshAabb(AZ::Aabb&& aabb); - //! Sets the material asset for the current SubMesh. + //! Sets the material slot data for the current SubMesh. + //! Adds a new material slot to the ModelLodAsset if it doesn't already exist. //! Begin and BeginMesh must be called first - void SetMeshMaterialAsset(const Data::Asset& materialAsset); + void SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot); //! Sets the given BufferAssetView to the current SubMesh as the index buffer. //! Begin and BeginMesh must be called first diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h new file mode 100644 index 0000000000..27137459b7 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AZ +{ + class ReflectContext; + + namespace RPI + { + //! Use by model assets to identify a logical material slot. + //! Each slot has a unique ID, a name, and a default material. Each mesh in model will reference a single ModelMaterialSlot. + //! Other classes like MeshFeatureProcessor and MaterialComponent can override the material associated with individual slots + //! to alter the default appearance of the mesh. + struct ModelMaterialSlot + { + AZ_TYPE_INFO(ModelMaterialSlot, "{0E88A62A-D83D-4C1B-8DE7-CE972B8124B5}"); + + static void Reflect(AZ::ReflectContext* context); + + using StableId = uint32_t; + static const StableId InvalidStableId = -1; + + //! This ID must have a consistent value when the asset is reprocessed by the asset pipeline, and must be unique within the ModelLodAsset. + //! In practice, this set using the MaterialUid from SceneAPI. See ModelAssetBuilderComponent::CreateMesh. + StableId m_stableId = InvalidStableId; + + Name m_displayName; //!< The name of the slot as displayed to the user in UI. (Using Name instead of string for fast copies) + + Data::Asset m_defaultMaterialAsset{ Data::AssetLoadBehavior::PreLoad }; //!< The material that will be applied to this slot by default. + }; + + using ModelMaterialSlotMap = AZStd::unordered_map; + + } //namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp index aa2ad37647..608ee17d95 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp @@ -109,7 +109,7 @@ namespace AZ if (auto* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(27); // [ATOM-15658] + ->Version(29); // (updated to separate material slot ID from default material asset) } } @@ -1806,8 +1806,12 @@ namespace AZ auto iter = materialAssetsByUid.find(meshView.m_materialUid); if (iter != materialAssetsByUid.end()) { - const Data::Asset& materialAsset = iter->second.m_asset; - lodAssetCreator.SetMeshMaterialAsset(materialAsset); + ModelMaterialSlot materialSlot; + materialSlot.m_stableId = meshView.m_materialUid; + materialSlot.m_displayName = iter->second.m_name; + materialSlot.m_defaultMaterialAsset = iter->second.m_asset; + + lodAssetCreator.SetMeshMaterialSlot(materialSlot); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp index b68930e4a3..2dfee9e2f1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp @@ -100,10 +100,13 @@ namespace AZ } } - auto& materialAsset = mesh.GetMaterialAsset(); - if (materialAsset.IsReady()) + const ModelMaterialSlot& materialSlot = lodAsset.GetMaterialSlot(mesh.GetMaterialSlotIndex()); + + meshInstance.m_materialSlotStableId = materialSlot.m_stableId; + + if (materialSlot.m_defaultMaterialAsset.IsReady()) { - meshInstance.m_material = Material::FindOrCreate(materialAsset); + meshInstance.m_material = Material::FindOrCreate(materialSlot.m_defaultMaterialAsset); } m_meshes.emplace_back(AZStd::move(meshInstance)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp index 610f183eb1..b9781843cf 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp @@ -24,6 +24,7 @@ namespace AZ { ModelLodAsset::Reflect(context); ModelAsset::Reflect(context); + ModelMaterialSlot::Reflect(context); MorphTargetMetaAsset::Reflect(context); SkinMetaAsset::Reflect(context); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp index 083de10d47..486faafbdb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp @@ -56,6 +56,30 @@ namespace AZ { return m_aabb; } + + RPI::ModelMaterialSlotMap ModelAsset::GetModelMaterialSlots() const + { + RPI::ModelMaterialSlotMap slotMap; + + for (const Data::Asset& lod : GetLodAssets()) + { + for (const AZ::RPI::ModelMaterialSlot& materialSlot : lod->GetMaterialSlots()) + { + auto iter = slotMap.find(materialSlot.m_stableId); + if (iter == slotMap.end()) + { + slotMap.emplace(materialSlot.m_stableId, materialSlot); + } + else + { + AZ_Assert(materialSlot.m_displayName == iter->second.m_displayName && materialSlot.m_defaultMaterialAsset.GetId() == iter->second.m_defaultMaterialAsset.GetId(), + "Multiple LODs have mismatched data for the same material slot."); + } + } + } + + return slotMap; + } size_t ModelAsset::GetLodCount() const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp index 190e4c5315..4811f6a1db 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp @@ -23,24 +23,25 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("Meshes", &ModelLodAsset::m_meshes) ->Field("Aabb", &ModelLodAsset::m_aabb) + ->Field("MaterialSlots", &ModelLodAsset::m_materialSlots) ; } Mesh::Reflect(context); } - + void ModelLodAsset::Mesh::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) - ->Field("Material", &ModelLodAsset::Mesh::m_materialAsset) + ->Version(1) ->Field("Name", &ModelLodAsset::Mesh::m_name) ->Field("AABB", &ModelLodAsset::Mesh::m_aabb) + ->Field("MaterialSlotIndex", &ModelLodAsset::Mesh::m_materialSlotIndex) ->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView) ->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo) ; @@ -75,9 +76,9 @@ namespace AZ return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount; } - const Data::Asset & ModelLodAsset::Mesh::GetMaterialAsset() const + size_t ModelLodAsset::Mesh::GetMaterialSlotIndex() const { - return m_materialAsset; + return m_materialSlotIndex; } const AZ::Name& ModelLodAsset::Mesh::GetName() const @@ -118,6 +119,41 @@ namespace AZ { return m_aabb; } + + AZStd::array_view ModelLodAsset::GetMaterialSlots() const + { + return m_materialSlots; + } + + const ModelMaterialSlot& ModelLodAsset::GetMaterialSlot(size_t slotIndex) const + { + if (slotIndex < m_materialSlots.size()) + { + return m_materialSlots[slotIndex]; + } + else + { + AZ_Error("ModelAsset", false, "Material slot index %zu out of range. ModelAsset has %zu slots.", slotIndex, m_materialSlots.size()); + return m_fallbackSlot; + } + } + + const ModelMaterialSlot* ModelLodAsset::FindMaterialSlot(uint32_t stableId) const + { + auto iter = AZStd::find_if(m_materialSlots.begin(), m_materialSlots.end(), [&stableId](const ModelMaterialSlot& existingMaterialSlot) + { + return existingMaterialSlot.m_stableId == stableId; + }); + + if (iter == m_materialSlots.end()) + { + return nullptr; + } + else + { + return iter; + } + } const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp index 6f17a5c590..5a066d2517 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp @@ -60,12 +60,32 @@ namespace AZ m_currentMesh.m_aabb = AZStd::move(aabb); } } - - void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset& materialAsset) + + void ModelLodAssetCreator::SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot) { - if (ValidateIsMeshReady()) + auto iter = AZStd::find_if(m_asset->m_materialSlots.begin(), m_asset->m_materialSlots.end(), [&materialSlot](const ModelMaterialSlot& existingMaterialSlot) + { + return existingMaterialSlot.m_stableId == materialSlot.m_stableId; + }); + + if (iter == m_asset->m_materialSlots.end()) { - m_currentMesh.m_materialAsset = materialAsset; + m_currentMesh.m_materialSlotIndex = m_asset->m_materialSlots.size(); + m_asset->m_materialSlots.push_back(materialSlot); + } + else + { + if (materialSlot.m_displayName != iter->m_displayName) + { + ReportWarning("Material slot %u was already added with a different name.", materialSlot.m_stableId); + } + + if (materialSlot.m_defaultMaterialAsset != iter->m_defaultMaterialAsset) + { + ReportWarning("Material slot %u was already added with a different MaterialAsset.", materialSlot.m_stableId); + } + + *iter = materialSlot; } } @@ -288,7 +308,9 @@ namespace AZ creator.SetMeshName(sourceMesh.GetName()); AZ::Aabb aabb = sourceMesh.GetAabb(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(sourceMesh.GetMaterialAsset()); + + const ModelMaterialSlot& materialSlot = sourceAsset->GetMaterialSlot(sourceMesh.GetMaterialSlotIndex()); + creator.SetMeshMaterialSlot(materialSlot); // Mesh index buffer view const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp new file mode 100644 index 0000000000..61f3ebbe3a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + void ModelMaterialSlot::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("StableId", &ModelMaterialSlot::m_stableId) + ->Field("DisplayName", &ModelMaterialSlot::m_displayName) + ->Field("DefaultMaterialAsset", &ModelMaterialSlot::m_defaultMaterialAsset) + ; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index c049837045..49c7231fed 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -22,6 +22,7 @@ set(FILES Include/Atom/RPI.Reflect/Model/ModelKdTree.h Include/Atom/RPI.Reflect/Model/ModelLodAsset.h Include/Atom/RPI.Reflect/Model/ModelLodIndex.h + Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h Include/Atom/RPI.Reflect/Model/MorphTargetDelta.h @@ -106,6 +107,7 @@ set(FILES Source/RPI.Reflect/Model/ModelLodAsset.cpp Source/RPI.Reflect/Model/ModelAssetCreator.cpp Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp + Source/RPI.Reflect/Model/ModelMaterialSlot.cpp Source/RPI.Reflect/Model/MorphTargetDelta.cpp Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp Source/RPI.Reflect/Model/MorphTargetMetaAssetCreator.cpp diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 134bf6db47..4072684987 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -69,6 +69,9 @@ namespace AZ : public ComponentBus { public: + //! Returns the list of all ModelMaterialSlot's for the model, across all LODs. + virtual RPI::ModelMaterialSlotMap GetModelMaterialSlots() const = 0; + virtual MaterialAssignmentMap GetMaterialAssignments() const = 0; virtual AZStd::unordered_set GetModelUvNames() const = 0; }; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index d7dc4335b3..f1767a8b1d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -44,57 +44,8 @@ namespace AZ if (classElement.GetVersion() < 3) { - // The default material was changed from an asset to an EditorMaterialComponentSlot and old data must be converted - constexpr AZ::u32 defaultMaterialAssetDataCrc = AZ_CRC("defaultMaterialAsset", 0x736fc071); - - Data::Asset oldDefaultMaterialData; - if (!classElement.GetChildData(defaultMaterialAssetDataCrc, oldDefaultMaterialData)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get defaultMaterialAsset element"); - return false; - } - - if (!classElement.RemoveElementByName(defaultMaterialAssetDataCrc)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove defaultMaterialAsset element"); - return false; - } - - EditorMaterialComponentSlot newDefaultMaterialData; - newDefaultMaterialData.m_id = DefaultMaterialAssignmentId; - newDefaultMaterialData.m_materialAsset = oldDefaultMaterialData; - classElement.AddElementWithData(context, "defaultMaterialSlot", newDefaultMaterialData); - - // Slots now support and display the default material asset when empty - // The old placeholder assignments are irrelevant and must be cleared - constexpr AZ::u32 materialSlotsByLodDataCrc = AZ_CRC("materialSlotsByLod", 0xb1498db6); - - EditorMaterialComponentSlotsByLodContainer lodSlotData; - if (!classElement.GetChildData(materialSlotsByLodDataCrc, lodSlotData)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get materialSlotsByLod element"); - return false; - } - - if (!classElement.RemoveElementByName(materialSlotsByLodDataCrc)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove materialSlotsByLod element"); - return false; - } - - // Find and clear all slots that are assigned to the slot's default value - for (auto& lodSlots : lodSlotData) - { - for (auto& slot : lodSlots) - { - if (slot.m_materialAsset.GetId() == slot.m_id.m_materialAssetId) - { - slot.m_materialAsset = {}; - } - } - } - - classElement.AddElementWithData(context, "materialSlotsByLod", lodSlotData); + AZ_Error("EditorMaterialComponent", false, "Material Component version < 3 is no longer supported"); + return false; } if (classElement.GetVersion() < 4) @@ -238,7 +189,7 @@ namespace AZ for (auto& materialSlotPair : GetMaterialSlots()) { EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsAssetOnly()) + if (materialSlot->m_id.IsSlotIdOnly()) { materialSlot->Clear(); } @@ -251,7 +202,7 @@ namespace AZ for (auto& materialSlotPair : GetMaterialSlots()) { EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsLodAndAsset()) + if (materialSlot->m_id.IsLodAndSlotId()) { materialSlot->Clear(); } @@ -318,6 +269,9 @@ namespace AZ // Build the controller configuration from the editor configuration MaterialComponentConfig config = m_controller.GetConfiguration(); config.m_materials.clear(); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); for (const auto& materialSlotPair : GetMaterialSlots()) { @@ -340,10 +294,15 @@ namespace AZ } else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty()) { - MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; - materialAssignment.m_materialAsset.Create(materialSlot->m_id.m_materialAssetId); - materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; - materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; + auto materialSlotIter = modelMaterialSlots.find(materialSlot->m_id.m_materialSlotStableId); + + if (materialSlotIter != modelMaterialSlots.end()) + { + MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; + materialAssignment.m_materialAsset = materialSlotIter->second.m_defaultMaterialAsset; + materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; + materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; + } } } @@ -362,6 +321,9 @@ namespace AZ // Get the known material assignment slots from the associated model or other source MaterialAssignmentMap materialsFromSource; MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); // Generate the table of editable materials using the source data to define number of groups, elements, and initial values for (const auto& materialPair : materialsFromSource) @@ -385,6 +347,29 @@ namespace AZ OnConfigurationChanged(); }; + const char* UnknownSlotName = ""; + + // If this is the default material assignment ID then it represents the default slot which is not contained in any other group + if (slot.m_id == DefaultMaterialAssignmentId) + { + slot.m_label = "Default Material"; + } + else + { + auto slotIter = modelMaterialSlots.find(slot.m_id.m_materialSlotStableId); + if (slotIter != modelMaterialSlots.end()) + { + const Name& displayName = slotIter->second.m_displayName; + slot.m_label = !displayName.IsEmpty() ? displayName.GetStringView() : UnknownSlotName; + + slot.m_defaultMaterialAsset = slotIter->second.m_defaultMaterialAsset; + } + else + { + slot.m_label = UnknownSlotName; + } + } + // if material is present in controller configuration, assign its data const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); slot.m_materialAsset = materialFromController.m_materialAsset; @@ -400,13 +385,13 @@ namespace AZ continue; } - if (slot.m_id.IsAssetOnly()) + if (slot.m_id.IsSlotIdOnly()) { m_materialSlots.push_back(slot); continue; } - if (slot.m_id.IsLodAndAsset()) + if (slot.m_id.IsLodAndSlotId()) { // Resize the containers to fit all elements m_materialSlotsByLod.resize(AZ::GetMax(m_materialSlotsByLod.size(), aznumeric_cast(slot.m_id.m_lodIndex + 1))); @@ -452,17 +437,19 @@ namespace AZ { AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); // First generating a unique set of all material asset IDs that will be used for source data generation AZStd::unordered_set assetIds; - auto materialSlots = GetMaterialSlots(); - for (auto& materialSlotPair : materialSlots) + for (auto& materialSlot : modelMaterialSlots) { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.m_materialAssetId.IsValid()) + Data::AssetId defaultMaterialAssetId = materialSlot.second.m_defaultMaterialAsset.GetId(); + if (defaultMaterialAssetId.IsValid()) { - assetIds.insert(materialSlot->m_id.m_materialAssetId); + assetIds.insert(defaultMaterialAssetId); } } @@ -472,7 +459,7 @@ namespace AZ for (const AZ::Data::AssetId& assetId : assetIds) { EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_assetId = assetId; + exportItem.m_originalAssetId = assetId; exportItems.push_back(exportItem); } @@ -489,12 +476,23 @@ namespace AZ const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0); if (assetIdOutcome) { - for (auto& materialSlotPair : materialSlots) + for (auto& materialSlotPair : GetMaterialSlots()) { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot && materialSlot->m_id.m_materialAssetId == exportItem.m_assetId) + EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second; + + if (editorMaterialSlot) { - materialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); + // Only update the slot of it was originally empty, having no override material. + // We need to check whether replaced material corresponds to this slot's default material. + if (!editorMaterialSlot->m_materialAsset.GetId().IsValid()) + { + auto materialSlot = modelMaterialSlots.find(editorMaterialSlot->m_id.m_materialSlotStableId); + if (materialSlot != modelMaterialSlots.end() && + materialSlot->second.m_defaultMaterialAsset.GetId() == exportItem.m_originalAssetId) + { + editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); + } + } } } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index bde500bdc5..1e6b19f616 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp @@ -132,7 +132,7 @@ namespace AZ int row = 0; for (ExportItem& exportItem : exportItems) { - QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_assetId).c_str()); + QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_originalAssetId).c_str()); // Configuring initial settings based on whether or not the target file already exists exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); @@ -147,7 +147,7 @@ namespace AZ // Create a check box for toggling the enabled state of this item QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget); materialSlotCheckBox->setChecked(exportItem.m_enabled); - materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_assetId).c_str()); + materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_originalAssetId).c_str()); tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox); // Create a file picker widget for selecting the save path for the exported material @@ -256,7 +256,7 @@ namespace AZ } EditorMaterialComponentUtil::MaterialEditData editData; - if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData)) + if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_originalAssetId, editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data."); return false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h index 00bef9ee66..289bde0c75 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h @@ -19,10 +19,10 @@ namespace AZ { namespace EditorMaterialComponentExporter { - // Attemts to generate a display label for a material slot by parsing its file name + //! Attemts to generate a display label for a material slot by parsing its file name AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId); - // Generates a destination file path for exporting material source data + //! Generates a destination file path for exporting material source data AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId); struct ExportItem @@ -30,16 +30,17 @@ namespace AZ bool m_enabled = true; bool m_exists = false; bool m_overwrite = false; - AZ::Data::AssetId m_assetId; + AZ::Data::AssetId m_originalAssetId; //!< AssetId of the original built-in material, which will be exported. AZStd::string m_exportPath; }; using ExportItemsContainer = AZStd::vector; - // Generates and opens a dialog for configuring material data export paths and actions + //! Generates and opens a dialog for configuring material data export paths and actions. + //! Note this will not modify the m_originalAssetId field in each ExportItem. bool OpenExportDialog(ExportItemsContainer& exportItems); - // Attemts to construct and save material source data from a product asset + //! Attemts to construct and save material source data from a product asset bool ExportMaterialSourceData(const ExportItem& exportItem); } // namespace EditorMaterialComponentExporter } // namespace Render diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index d341bbb290..c8a2024b39 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -20,7 +20,7 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include -#include +#include #include AZ_POP_DISABLE_WARNING @@ -48,7 +48,7 @@ namespace AZ return false; } - const MaterialAssignmentId newId(oldId.first, oldId.second); + const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId); classElement.AddElementWithData(context, "id", newId); } @@ -83,6 +83,7 @@ namespace AZ ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) + ->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -121,21 +122,12 @@ namespace AZ AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const { - return m_id.m_materialAssetId; + return m_defaultMaterialAsset.GetId(); } AZStd::string EditorMaterialComponentSlot::GetLabel() const { - // Generate the label for the material slot based on the assignment ID - // If this is the default material assignment ID then it represents the default slot which is not contained in any other group - if (m_id == DefaultMaterialAssignmentId) - { - return "Default Material"; - } - - // Otherwise the label can be generated by parsing the source file name associated with the asset ID - const AZStd::string& label = EditorMaterialComponentExporter::GetLabelByAssetId(m_id.m_materialAssetId); - return !label.empty() ? label : ""; + return m_label; } bool EditorMaterialComponentSlot::HasSourceData() const @@ -183,27 +175,22 @@ namespace AZ OnMaterialChanged(); } - void EditorMaterialComponentSlot::SetDefaultAsset() + void EditorMaterialComponentSlot::ResetToDefaultAsset() { - m_materialAsset = {}; + m_materialAsset = m_defaultMaterialAsset; m_propertyOverrides = {}; m_matModUvOverrides = {}; - if (m_id.m_materialAssetId.IsValid()) - { - // If no material is assigned to this slot, assign the default material from the slot id to edit its properties - m_materialAsset.Create(m_id.m_materialAssetId); - } OnMaterialChanged(); } void EditorMaterialComponentSlot::OpenMaterialExporter() { // Because we are generating a source material from this specific slot there is only one entry - // But we still need to allow the user to reconfigure it using the dialogue + // But we still need to allow the user to reconfigure it using the dialog EditorMaterialComponentExporter::ExportItemsContainer exportItems; { EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_assetId = m_id.m_materialAssetId; + exportItem.m_originalAssetId = m_defaultMaterialAsset.GetId(); exportItems.push_back(exportItem); } @@ -275,7 +262,7 @@ namespace AZ QAction* action = nullptr; action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); }); - action->setEnabled(m_id.m_materialAssetId.IsValid()); + action->setEnabled(m_defaultMaterialAsset.GetId().IsValid()); menu.addSeparator(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h index e8b4f46854..79fddf2611 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h @@ -34,7 +34,7 @@ namespace AZ AZStd::string GetLabel() const; bool HasSourceData() const; void OpenMaterialEditor() const; - void SetDefaultAsset(); + void ResetToDefaultAsset(); void Clear(); void ClearOverrides(); void OpenMaterialExporter(); @@ -42,7 +42,9 @@ namespace AZ void OpenUvNameMapInspector(); MaterialAssignmentId m_id; + AZStd::string m_label; Data::Asset m_materialAsset; + Data::Asset m_defaultMaterialAsset; MaterialPropertyOverrideMap m_propertyOverrides; AZStd::function m_materialChangedCallback; AZStd::function m_propertyChangedCallback; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp index a0adcd3187..f7b7177e29 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp @@ -44,7 +44,7 @@ namespace AZ for (const auto& oldPair : oldMaterials) { const DeprecatedMaterialAssignmentId& oldId = oldPair.first; - const MaterialAssignmentId newId(oldId.first, oldId.second); + const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId); newMaterials[newId] = oldPair.second; } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index 29bd9a839b..a2a3022e91 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -251,6 +251,19 @@ namespace AZ m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transformInterface->GetWorldTM(), m_cachedNonUniformScale); } } + + RPI::ModelMaterialSlotMap MeshComponentController::GetModelMaterialSlots() const + { + Data::Asset modelAsset = GetModelAsset(); + if (modelAsset.IsReady()) + { + return modelAsset->GetModelMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index 80b483452f..78c2e0797f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -111,6 +111,7 @@ namespace AZ void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; // MaterialReceiverRequestBus::Handler overrides ... + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp index 3143191135..ae617f910f 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -96,12 +96,10 @@ namespace AZ skinnedSubMesh.m_vertexCount = aznumeric_cast(subMeshVertexCount); lodVertexCount += aznumeric_cast(subMeshVertexCount); - // The default material id used by a sub-mesh is the guid of the source scene file plus the subId which is a unique material ID from the scene API - AZ::u32 subId = modelMesh.GetMaterialAsset().GetId().m_subId; - AZ::Data::AssetId materialId{ actorAssetId.m_guid, subId }; - + skinnedSubMesh.m_material = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()).m_defaultMaterialAsset; // Queue the material asset - the ModelLod seems to handle delayed material loads - skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid(), skinnedSubMesh.m_material.GetAutoLoadBehavior()); + skinnedSubMesh.m_material.QueueLoad(); + subMeshes.push_back(skinnedSubMesh); } else diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index 4c6045d7ce..6697162c18 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -307,6 +307,19 @@ namespace AZ m_meshFeatureProcessor = nullptr; m_skinnedMeshFeatureProcessor = nullptr; } + + RPI::ModelMaterialSlotMap AtomActorInstance::GetModelMaterialSlots() const + { + Data::Asset modelAsset = GetModelAsset(); + if (modelAsset.IsReady()) + { + return modelAsset->GetModelMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const { diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index c854fed3c1..e7a4047012 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -120,6 +120,7 @@ namespace AZ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MaterialReceiverRequestBus::Handler overrides... + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp index 3f089a8639..5a9b8191ed 100644 --- a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp +++ b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp @@ -116,7 +116,10 @@ namespace WhiteBox // set the default material if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath(TexturedMaterialPath.data())) { - modelLodCreator.SetMeshMaterialAsset(materialAsset); + AZ::RPI::ModelMaterialSlot materialSlot; + materialSlot.m_stableId = 0; + materialSlot.m_defaultMaterialAsset = materialAsset; + modelLodCreator.SetMeshMaterialSlot(materialSlot); } else {