diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h index 907b1a1740..987e78ae0f 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h @@ -66,6 +66,6 @@ namespace AZ //! Find an assignment id corresponding to the lod and label substring filters MaterialAssignmentId FindMaterialAssignmentIdInModel( - const Data::Instance model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); + const Data::Instance& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); } // namespace Render } // namespace AZ 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..e58a8397db 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,51 +24,50 @@ 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 { AZ_RTTI(AZ::Render::MaterialAssignmentId, "{EB603581-4654-4C17-B6DE-AE61E79EDA97}"); AZ_CLASS_ALLOCATOR(AZ::Render::MaterialAssignmentId, SystemAllocator, 0); static void Reflect(ReflectContext* context); + static bool ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement); 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/Include/Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h index 5d333e070e..c52bed8cf2 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h @@ -45,7 +45,7 @@ namespace AZ uint32_t m_vertexOffset = 0; uint32_t m_vertexCount = 0; Aabb m_aabb = Aabb::CreateNull(); - Data::Asset m_material; + AZ::RPI::ModelMaterialSlot m_materialSlot; }; //! Buffer views for a specific sub-mesh that are not modified during skinning and thus are shared by all instances of the same skinned mesh diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index 074d5c39e8..4d437be244 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -33,8 +33,7 @@ namespace AZ serializeContext->Class() ->Version(1) ->Field("MaterialAsset", &MaterialAssignment::m_materialAsset) - ->Field("PropertyOverrides", &MaterialAssignment::m_propertyOverrides) - ; + ->Field("PropertyOverrides", &MaterialAssignment::m_propertyOverrides); } if (auto behaviorContext = azrtti_cast(context)) @@ -50,8 +49,7 @@ namespace AZ ->Constructor&, const Data::Instance&>() ->Method("ToString", &MaterialAssignment::ToString) ->Property("materialAsset", BehaviorValueProperty(&MaterialAssignment::m_materialAsset)) - ->Property("propertyOverrides", BehaviorValueProperty(&MaterialAssignment::m_propertyOverrides)) - ; + ->Property("propertyOverrides", BehaviorValueProperty(&MaterialAssignment::m_propertyOverrides)); behaviorContext->ConstantProperty("DefaultMaterialAssignment", BehaviorConstant(DefaultMaterialAssignment)) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) @@ -67,7 +65,6 @@ namespace AZ ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Category, "render") ->Attribute(AZ::Script::Attributes::Module, "render"); - } } @@ -123,7 +120,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 +149,12 @@ 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); } } @@ -168,38 +166,36 @@ namespace AZ } MaterialAssignmentId FindMaterialAssignmentIdInLod( - const Data::Instance& lod, const MaterialAssignmentLodIndex lodIndex, const AZStd::string& labelFilter) + const Data::Instance& model, + const Data::Instance& lod, + const MaterialAssignmentLodIndex lodIndex, + const AZStd::string& labelFilter) { for (const AZ::RPI::ModelLod::Mesh& mesh : lod->GetMeshes()) { - if (mesh.m_material && mesh.m_material->GetAssetId().IsValid()) + const AZ::RPI::ModelMaterialSlot& slot = model->GetModelAsset()->FindMaterialSlot(mesh.m_materialSlotStableId); + if (AZ::StringFunc::Contains(slot.m_displayName.GetCStr(), labelFilter, true)) { - AZ::Data::AssetInfo assetInfo; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, mesh.m_material->GetAssetId()); - if (assetInfo.m_assetId.IsValid() && AZ::StringFunc::Contains(assetInfo.m_relativePath, labelFilter, true)) - { - return MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId()); - } + return MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, mesh.m_materialSlotStableId); } } return MaterialAssignmentId(); } MaterialAssignmentId FindMaterialAssignmentIdInModel( - const Data::Instance model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter) + const Data::Instance& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter) { if (model && !labelFilter.empty()) { if (lodFilter < model->GetLodCount()) { - return FindMaterialAssignmentIdInLod(model->GetLods()[lodFilter], lodFilter, labelFilter); + return FindMaterialAssignmentIdInLod(model, model->GetLods()[lodFilter], lodFilter, labelFilter); } for (size_t lodIndex = 0; lodIndex < model->GetLodCount(); ++lodIndex) { const MaterialAssignmentId result = - FindMaterialAssignmentIdInLod(model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter); + FindMaterialAssignmentIdInLod(model, model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter); if (!result.IsDefault()) { return result; diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp index 71dea0596f..b8a03af6d1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp @@ -14,14 +14,46 @@ namespace AZ { namespace Render { + bool MaterialAssignmentId::ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) + { + if (classElement.GetVersion() < 2) + { + constexpr AZ::u32 materialAssetIdCrc = AZ_CRC("materialAssetId"); + + AZ::Data::AssetId materialAssetId; + if (!classElement.GetChildData(materialAssetIdCrc, materialAssetId)) + { + AZ_Error("AZ::Render::MaterialAssignmentId::ConvertVersion", false, "Failed to get AssetId element"); + return false; + } + + if (!classElement.RemoveElementByName(materialAssetIdCrc)) + { + AZ_Error("AZ::Render::MaterialAssignmentId::ConvertVersion", false, "Failed to remove deprecated element materialAssetId"); + // No need to early-return, the object will still load successfully, it will just report more errors about the unrecognized element. + } + + if (materialAssetId.IsValid()) + { + classElement.AddElementWithData(context, "materialSlotStableId", materialAssetId.m_subId); + } + else + { + classElement.AddElementWithData(context, "materialSlotStableId", RPI::ModelMaterialSlot::InvalidStableId); + } + } + + return true; + } + void MaterialAssignmentId::Reflect(ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2, &MaterialAssignmentId::ConvertVersion) ->Field("lodIndex", &MaterialAssignmentId::m_lodIndex) - ->Field("materialAssetId", &MaterialAssignmentId::m_materialAssetId) + ->Field("materialSlotStableId", &MaterialAssignmentId::m_materialSlotStableId) ; } @@ -33,75 +65,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..8e2c6f2e9b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -485,20 +485,12 @@ namespace AZ AZ_Error("MeshDataInstance::MeshLoader", false, "Invalid model asset Id."); return; } - - // Check if the model is in the instance database and skip the loading process in this case. - // The model asset id is used as instance id to indicate that it is a static and shared. - Data::Instance model = Data::InstanceDatabase::Instance().Find(Data::InstanceId::CreateFromAssetId(m_modelAsset.GetId())); - if (model) + + if (!m_modelAsset.IsReady()) { - // In case the mesh asset requires instancing (e.g. when containing a cloth buffer), the model will always be cloned and there will not be a - // model instance with the asset id as instance id as searched above. - m_parent->Init(model); - m_modelChangedEvent.Signal(AZStd::move(model)); - return; + m_modelAsset.QueueLoad(); } - m_modelAsset.QueueLoad(); Data::AssetBus::Handler::BusConnect(modelAsset.GetId()); } @@ -589,18 +581,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 +624,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 +772,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..de2c52d1c8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp @@ -640,7 +640,8 @@ namespace AZ Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); - modelLodCreator.SetMeshMaterialAsset(lod.m_subMeshProperties[i].m_material); + modelCreator.AddMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot); + modelLodCreator.SetMeshMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot.m_stableId); modelLodCreator.EndMesh(); } diff --git a/Gems/Atom/RPI/Assets/Materials/Default.materialtype b/Gems/Atom/RPI/Assets/Materials/Default.materialtype deleted file mode 100644 index e8e6ff24eb..0000000000 --- a/Gems/Atom/RPI/Assets/Materials/Default.materialtype +++ /dev/null @@ -1,90 +0,0 @@ -{ - "description": "A simple default base material used primarily for imported model files like FBX.", - "propertyLayout": { - "version": 1, - "properties": { - "general": [ - { - "id": "DiffuseColor", - "type": "color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "shaderInput", - "id": "m_diffuseColor" - } - }, - { - "id": "DiffuseMap", - "type": "image", - "defaultValue": "", - "connection": { - "type": "shaderInput", - "id": "m_diffuseMap" - } - }, - { - "id": "UseDiffuseMap", - "type": "bool", - "defaultValue": false, - "connection": { - "type": "shaderOption", - "id": "o_useDiffuseMap" - } - }, - { - "id": "SpecularColor", - "type": "color", - "defaultValue": [ 0.0, 0.0, 0.0 ], - "connection": { - "type": "shaderInput", - "id": "m_specularColor" - } - }, - { - "id": "SpecularMap", - "type": "image", - "defaultValue": "", - "connection": { - "type": "shaderInput", - "id": "m_specularMap" - } - }, - { - "id": "UseSpecularMap", - "type": "bool", - "defaultValue": false, - "connection": { - "type": "shaderOption", - "id": "o_useSpecularMap" - } - }, - { - "id": "NormalMap", - "type": "image", - "defaultValue": "", - "connection": { - "type": "shaderInput", - "id": "m_normalMap" - } - }, - { - "id": "UseNormalMap", - "type": "bool", - "defaultValue": false, - "connection": { - "type": "shaderOption", - "id": "o_useNormalMap" - } - } - ] - } - }, - "shaders": [ - { - "file": "DefaultMaterial.shader" - }, - { - "file": "DefaultMaterial_DepthPass.shader" - } - ] -} diff --git a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.azsl b/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.azsl deleted file mode 100644 index 6640ed3fca..0000000000 --- a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.azsl +++ /dev/null @@ -1,127 +0,0 @@ - -/* - * 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 -#include -#include - -ShaderResourceGroup MaterialSrg : SRG_PerMaterial -{ - float4 m_diffuseColor; - float3 m_specularColor; - - Texture2D m_diffuseMap; - Texture2D m_normalMap; - Texture2D m_specularMap; - - Sampler m_sampler - { - MaxAnisotropy = 16; - AddressU = Wrap; - AddressV = Wrap; - AddressW = Wrap; - }; -} - -option bool o_useDiffuseMap = false; -option bool o_useSpecularMap = false; -option bool o_useNormalMap = false; - -struct VertexInput -{ - float3 m_position : POSITION; - float3 m_normal : NORMAL; - float4 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; - float2 m_uv : UV0; -}; - -struct VertexOutput -{ - float4 m_position : SV_Position; - float3 m_normal : NORMAL; - float3 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; - float2 m_uv : UV0; - float3 m_positionToCamera : VIEW; -}; - -VertexOutput MainVS(VertexInput input) -{ - const float4x4 objectToWorldMatrix = ObjectSrg::GetWorldMatrix(); - - VertexOutput output; - float3 worldPosition = mul(objectToWorldMatrix, float4(input.m_position,1)).xyz; - output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0)); - - output.m_uv = input.m_uv; - - output.m_positionToCamera = ViewSrg::m_worldPosition - worldPosition; - - float3x3 objectToWorldMatrixIT = ObjectSrg::GetWorldMatrixInverseTranspose(); - - ConstructTBN(input.m_normal, input.m_tangent, input.m_bitangent, objectToWorldMatrix, objectToWorldMatrixIT, output.m_normal, output.m_tangent, output.m_bitangent); - - return output; -} - -struct PixelOutput -{ - float4 m_color : SV_Target0; -}; - -PixelOutput MainPS(VertexOutput input) -{ - PixelOutput output; - - // Very rough placeholder lighting - static const float3 lightDir = normalize(float3(1,1,1)); - - float4 baseColor = MaterialSrg::m_diffuseColor; - if (o_useDiffuseMap) - { - baseColor *= MaterialSrg::m_diffuseMap.Sample(MaterialSrg::m_sampler, input.m_uv); - } - - float3 specular = MaterialSrg::m_specularColor; - if (o_useSpecularMap) - { - specular *= MaterialSrg::m_specularMap.Sample(MaterialSrg::m_sampler, input.m_uv).rgb; - } - - float3 normal; - if (o_useNormalMap) - { - float4 sampledValue = MaterialSrg::m_normalMap.Sample(MaterialSrg::m_sampler, input.m_uv); - normal = GetWorldSpaceNormal(sampledValue.xy, input.m_normal, input.m_tangent, input.m_bitangent); - } - else - { - normal = normalize(input.m_normal); - } - - float3 viewDir = normalize(input.m_positionToCamera); - float3 H = normalize(lightDir + viewDir); - float NdotH = max(0.001, dot(normal, H)); - float NdotL = saturate(dot(normal, lightDir)); - - float3 diffuse = NdotL * baseColor.xyz; - - specular = pow(NdotH, 5.0) * specular; - - // Combined - float3 result = diffuse + specular + float3(0.1, 0.1, 0.1) * baseColor.xyz; - - output.m_color = float4(result.xyz, baseColor.a); - - return output; -} diff --git a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.shader b/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.shader deleted file mode 100644 index ceea480f43..0000000000 --- a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial.shader +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Source" : "DefaultMaterial.azsl", - - "DepthStencilState" : { - "Depth" : { "Enable" : true, "CompareFunc" : "Equal" } - }, - - "DrawList" : "forward", - - "ProgramSettings": - { - "EntryPoints": - [ - { - "name": "MainVS", - "type": "Vertex" - }, - { - "name": "MainPS", - "type": "Fragment" - } - ] - } -} - - diff --git a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.azsl b/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.azsl deleted file mode 100644 index 961fde7568..0000000000 --- a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.azsl +++ /dev/null @@ -1,33 +0,0 @@ - -/* - * 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 - -struct VertexInput -{ - float3 m_position : POSITION; -}; - -struct VertexOutput -{ - float4 m_position : SV_Position; -}; - -VertexOutput MainVS(VertexInput input) -{ - const float4x4 objectToWorldMatrix = ObjectSrg::GetWorldMatrix(); - - VertexOutput output; - float3 worldPosition = mul(objectToWorldMatrix, float4(input.m_position,1)).xyz; - output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0)); - return output; -} diff --git a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.shader b/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.shader deleted file mode 100644 index 19ba1d5d9d..0000000000 --- a/Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.shader +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Source" : "DefaultMaterial_DepthPass.azsl", - - "DepthStencilState" : { - "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } - }, - - "DrawList" : "depth", - - "ProgramSettings": - { - "EntryPoints": - [ - { - "name": "MainVS", - "type": "Vertex" - } - ] - } -} diff --git a/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake b/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake index 8f85259f7d..9e89427a70 100644 --- a/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake +++ b/Gems/Atom/RPI/Assets/atom_rpi_asset_files.cmake @@ -7,11 +7,6 @@ # set(FILES - Materials/Default.materialtype - Materials/DefaultMaterial.azsl - Materials/DefaultMaterial.shader - Materials/DefaultMaterial_DepthPass.azsl - Materials/DefaultMaterial_DepthPass.shader Shader/DecomposeMsImage.azsl Shader/DecomposeMsImage.shader Shader/ImagePreview.azsl diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h index 32880a221c..a19c985f2d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h @@ -90,8 +90,8 @@ namespace AZ private: Model() = default; - static Data::Instance CreateInternal(ModelAsset& modelAsset); - RHI::ResultCode Init(ModelAsset& modelAsset); + static Data::Instance CreateInternal(const Data::Asset& modelAsset); + RHI::ResultCode Init(const Data::Asset& modelAsset); AZStd::fixed_vector, ModelLodAsset::LodCountMax> m_lods; Data::Asset m_modelAsset; 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..0d0304a04e 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 @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -72,6 +73,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; @@ -82,7 +85,7 @@ namespace AZ AZ_INSTANCE_DATA(ModelLod, "{3C796FC9-2067-4E0F-A660-269F8254D1D5}"); AZ_CLASS_ALLOCATOR(ModelLod, AZ::SystemAllocator, 0); - static Data::Instance FindOrCreate(const Data::Asset& lodAsset); + static Data::Instance FindOrCreate(const Data::Asset& lodAsset, const Data::Asset& modelAsset); ~ModelLod() = default; @@ -122,8 +125,8 @@ namespace AZ private: ModelLod() = default; - static Data::Instance CreateInternal(ModelLodAsset& lodAsset); - RHI::ResultCode Init(ModelLodAsset& lodAsset); + static Data::Instance CreateInternal(const Data::Asset& lodAsset, const AZStd::any* modelAssetAny); + RHI::ResultCode Init(const Data::Asset& lodAsset, const Data::Asset& modelAsset); bool SetMeshInstanceData( const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo, 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..dbcfc69d56 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,12 @@ 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. + const ModelMaterialSlotMap& GetMaterialSlots() const; + + //! Find a material slot with the given stableId, or returns an invalid slot if it isn't found. + const ModelMaterialSlot& FindMaterialSlot(uint32_t stableId) const; //! Returns the number of Lods in the model size_t GetLodCount() const; @@ -97,6 +103,13 @@ namespace AZ volatile mutable bool m_isKdTreeCalculationRunning = false; mutable AZStd::mutex m_kdTreeLock; mutable AZStd::optional m_modelTriangleCount; + + // 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. + ModelMaterialSlotMap m_materialSlots; + + // A default ModelMaterialSlot to be returned upon error conditions. + ModelMaterialSlot m_fallbackSlot; AZStd::size_t CalculateTriangleCount() const; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h index 0b8cb678dc..d87ae1c57e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h @@ -29,6 +29,10 @@ namespace AZ //! Assigns a name to the model void SetName(AZStd::string_view name); + + //! Adds a new material slot to the asset. + //! If a slot with the same stable ID already exists, it will be replaced. + void AddMaterialSlot(const ModelMaterialSlot& materialSlot); //! Adds a Lod to the model. void AddLodAsset(Data::Asset&& lodAsset); 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..8c8edddfe1 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 ID of the material slot used by this mesh. + //! This maps into the ModelAsset's material slot list. + ModelMaterialSlot::StableId GetMaterialSlotId() 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 slot that is used by this mesh. + // References material slot in the ModelAsset that owns this mesh; see ModelAsset::FindMaterialSlot(). + ModelMaterialSlot::StableId m_materialSlotId = ModelMaterialSlot::InvalidStableId; // Both the buffer in m_indexBufferAssetView and the buffers in m_streamBufferInfo // may point to either unique buffers for the mesh or to consolidated @@ -147,7 +151,7 @@ namespace AZ 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. 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..776347cb2b 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,9 @@ namespace AZ //! Begin and BeginMesh must be called first. void SetMeshAabb(AZ::Aabb&& aabb); - //! Sets the material asset for the current SubMesh. + //! Sets the ID of the model's material slot that this mesh uses. //! Begin and BeginMesh must be called first - void SetMeshMaterialAsset(const Data::Asset& materialAsset); + void SetMeshMaterialSlot(ModelMaterialSlot::StableId id); //! 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..dd46aca6cd --- /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; + + //! 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/MaterialAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp index c55eb947a1..7187cb391a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp @@ -91,7 +91,7 @@ namespace AZ if (auto* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(14); // [ATOM-13410] + ->Version(16); // Optional material conversion } } 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..7100b2cd48 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) } } @@ -367,6 +367,9 @@ namespace AZ MorphTargetMetaAssetCreator morphTargetMetaCreator; morphTargetMetaCreator.Begin(MorphTargetMetaAsset::ConstructAssetId(modelAssetId, modelAssetName)); + + ModelAssetCreator modelAssetCreator; + modelAssetCreator.Begin(modelAssetId); uint32_t lodIndex = 0; for (const SourceMeshContentList& sourceMeshContentList : sourceMeshContentListsByLod) @@ -429,7 +432,7 @@ namespace AZ for (const ProductMeshView& meshView : lodMeshViews) { - if (!CreateMesh(meshView, indexBuffer, streamBuffers, lodAssetCreator, context.m_materialsByUid)) + if (!CreateMesh(meshView, indexBuffer, streamBuffers, modelAssetCreator, lodAssetCreator, context.m_materialsByUid)) { return AZ::SceneAPI::Events::ProcessingResult::Failure; } @@ -469,10 +472,6 @@ namespace AZ } sourceMeshContentListsByLod.clear(); - // Build the final asset structure - ModelAssetCreator modelAssetCreator; - modelAssetCreator.Begin(modelAssetId); - // Finalize all LOD assets for (auto& lodAsset : lodAssets) { @@ -1796,6 +1795,7 @@ namespace AZ const ProductMeshView& meshView, const BufferAssetView& lodIndexBuffer, const AZStd::vector& lodStreamBuffers, + ModelAssetCreator& modelAssetCreator, ModelLodAssetCreator& lodAssetCreator, const MaterialAssetsByUid& materialAssetsByUid) { @@ -1806,8 +1806,13 @@ 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; + + modelAssetCreator.AddMaterialSlot(materialSlot); + lodAssetCreator.SetMeshMaterialSlot(materialSlot.m_stableId); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h index 79dec962df..843f756df6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h @@ -42,6 +42,7 @@ namespace AZ using SkinData = AZ::SceneAPI::DataTypes::ISkinWeightData; class Stream; + class ModelAssetCreator; class ModelLodAssetCreator; class BufferAssetCreator; struct PackedCompressedMorphTargetDelta; @@ -294,6 +295,7 @@ namespace AZ const ProductMeshView& meshView, const BufferAssetView& lodIndexBuffer, const AZStd::vector& lodStreamBuffers, + ModelAssetCreator& modelAssetCreator, ModelLodAssetCreator& lodAssetCreator, const MaterialAssetsByUid& materialAssetsByUid); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp index e684c4832a..32fe297c57 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp @@ -40,7 +40,7 @@ namespace AZ return m_lods; } - Data::Instance Model::CreateInternal(ModelAsset& modelAsset) + Data::Instance Model::CreateInternal(const Data::Asset& modelAsset) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); Data::Instance model = aznew Model(); @@ -54,15 +54,15 @@ namespace AZ return nullptr; } - RHI::ResultCode Model::Init(ModelAsset& modelAsset) + RHI::ResultCode Model::Init(const Data::Asset& modelAsset) { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - m_lods.resize(modelAsset.GetLodAssets().size()); + m_lods.resize(modelAsset->GetLodAssets().size()); for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex) { - const Data::Asset& lodAsset = modelAsset.GetLodAssets()[lodIndex]; + const Data::Asset& lodAsset = modelAsset->GetLodAssets()[lodIndex]; if (!lodAsset) { @@ -70,7 +70,7 @@ namespace AZ return RHI::ResultCode::Fail; } - Data::Instance lodInstance = ModelLod::FindOrCreate(lodAsset); + Data::Instance lodInstance = ModelLod::FindOrCreate(lodAsset, modelAsset); if (lodInstance == nullptr) { return RHI::ResultCode::Fail; @@ -98,7 +98,7 @@ namespace AZ m_lods[lodIndex] = AZStd::move(lodInstance); } - m_modelAsset = { &modelAsset, AZ::Data::AssetLoadBehavior::PreLoad }; + m_modelAsset = modelAsset; m_isUploadPending = true; return RHI::ResultCode::Success; } 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..dc39200a65 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp @@ -19,11 +19,14 @@ namespace AZ { namespace RPI { - Data::Instance ModelLod::FindOrCreate(const Data::Asset& lodAsset) + Data::Instance ModelLod::FindOrCreate(const Data::Asset& lodAsset, const Data::Asset& modelAsset) { + AZStd::any modelAssetAny{&modelAsset}; + return Data::InstanceDatabase::Instance().FindOrCreate( Data::InstanceId::CreateFromAssetId(lodAsset.GetId()), - lodAsset); + lodAsset, + &modelAssetAny); } AZStd::array_view ModelLod::GetMeshes() const @@ -31,10 +34,13 @@ namespace AZ return m_meshes; } - Data::Instance ModelLod::CreateInternal(ModelLodAsset& lodAsset) + Data::Instance ModelLod::CreateInternal(const Data::Asset& lodAsset, const AZStd::any* modelAssetAny) { + AZ_Assert(modelAssetAny != nullptr, "Invalid model asset param"); + auto modelAsset = AZStd::any_cast*>(*modelAssetAny); + Data::Instance lod = aznew ModelLod(); - const RHI::ResultCode resultCode = lod->Init(lodAsset); + const RHI::ResultCode resultCode = lod->Init(lodAsset, *modelAsset); if (resultCode == RHI::ResultCode::Success) { @@ -44,11 +50,11 @@ namespace AZ return nullptr; } - RHI::ResultCode ModelLod::Init(ModelLodAsset& lodAsset) + RHI::ResultCode ModelLod::Init(const Data::Asset& lodAsset, const Data::Asset& modelAsset) { AZ_TRACE_METHOD(); - for (const ModelLodAsset::Mesh& mesh : lodAsset.GetMeshes()) + for (const ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes()) { Mesh meshInstance; @@ -100,10 +106,13 @@ namespace AZ } } - auto& materialAsset = mesh.GetMaterialAsset(); - if (materialAsset.IsReady()) + const ModelMaterialSlot& materialSlot = modelAsset->FindMaterialSlot(mesh.GetMaterialSlotId()); + + 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..1ed81f8e16 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); } @@ -40,9 +41,9 @@ namespace AZ { //Create Lod Database AZ::Data::InstanceHandler lodInstanceHandler; - lodInstanceHandler.m_createFunction = [](Data::AssetData* modelLodAsset) + lodInstanceHandler.m_createFunctionWithParam = [](Data::AssetData* modelLodAsset, const AZStd::any* modelAsset) { - return ModelLod::CreateInternal(*(azrtti_cast(modelLodAsset))); + return ModelLod::CreateInternal(Data::Asset{modelLodAsset, AZ::Data::AssetLoadBehavior::PreLoad}, modelAsset); }; Data::InstanceDatabase::Create(azrtti_typeid(), lodInstanceHandler); @@ -50,7 +51,7 @@ namespace AZ AZ::Data::InstanceHandler modelInstanceHandler; modelInstanceHandler.m_createFunction = [](Data::AssetData* modelAsset) { - return Model::CreateInternal(*(azrtti_cast(modelAsset))); + return Model::CreateInternal(Data::Asset{modelAsset, AZ::Data::AssetLoadBehavior::PreLoad}); }; Data::InstanceDatabase::Create(azrtti_typeid(), modelInstanceHandler); } 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..275b056514 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp @@ -29,9 +29,10 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("Name", &ModelAsset::m_name) ->Field("Aabb", &ModelAsset::m_aabb) + ->Field("MaterialSlots", &ModelAsset::m_materialSlots) ->Field("LodAssets", &ModelAsset::m_lodAssets) ; } @@ -56,6 +57,25 @@ namespace AZ { return m_aabb; } + + const ModelMaterialSlotMap& ModelAsset::GetMaterialSlots() const + { + return m_materialSlots; + } + + const ModelMaterialSlot& ModelAsset::FindMaterialSlot(uint32_t stableId) const + { + auto iter = m_materialSlots.find(stableId); + + if (iter == m_materialSlots.end()) + { + return m_fallbackSlot; + } + else + { + return iter->second; + } + } size_t ModelAsset::GetLodCount() const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAssetCreator.cpp index 0c7a12fa8b..b35c9e44fe 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAssetCreator.cpp @@ -29,6 +29,33 @@ namespace AZ m_asset->m_name = name; } } + + void ModelAssetCreator::AddMaterialSlot(const ModelMaterialSlot& materialSlot) + { + if (ValidateIsReady()) + { + auto iter = m_asset->m_materialSlots.find(materialSlot.m_stableId); + + if (iter == m_asset->m_materialSlots.end()) + { + m_asset->m_materialSlots[materialSlot.m_stableId] = materialSlot; + } + else + { + if (materialSlot.m_displayName != iter->second.m_displayName) + { + ReportWarning("Material slot %u was already added with a different name.", materialSlot.m_stableId); + } + + if (materialSlot.m_defaultMaterialAsset != iter->second.m_defaultMaterialAsset) + { + ReportWarning("Material slot %u was already added with a different default MaterialAsset.", materialSlot.m_stableId); + } + + iter->second = materialSlot; + } + } + } void ModelAssetCreator::AddLodAsset(Data::Asset&& lodAsset) { 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..ccf0d49b46 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp @@ -31,16 +31,16 @@ namespace AZ 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("MaterialSlotId", &ModelLodAsset::Mesh::m_materialSlotId) ->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView) ->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo) ; @@ -75,9 +75,9 @@ namespace AZ return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount; } - const Data::Asset & ModelLodAsset::Mesh::GetMaterialAsset() const + ModelMaterialSlot::StableId ModelLodAsset::Mesh::GetMaterialSlotId() const { - return m_materialAsset; + return m_materialSlotId; } const AZ::Name& ModelLodAsset::Mesh::GetName() const @@ -118,7 +118,7 @@ namespace AZ { return m_aabb; } - + const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const { const AZStd::array_view& streamBufferList = GetStreamBufferInfoList(); 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..f94116db70 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp @@ -60,13 +60,15 @@ namespace AZ m_currentMesh.m_aabb = AZStd::move(aabb); } } - - void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset& materialAsset) + + void ModelLodAssetCreator::SetMeshMaterialSlot(ModelMaterialSlot::StableId id) { - if (ValidateIsMeshReady()) + if (!ValidateIsMeshReady()) { - m_currentMesh.m_materialAsset = materialAsset; + return; } + + m_currentMesh.m_materialSlotId = id; } void ModelLodAssetCreator::SetMeshIndexBuffer(const BufferAssetView& bufferAssetView) @@ -288,7 +290,8 @@ namespace AZ creator.SetMeshName(sourceMesh.GetName()); AZ::Aabb aabb = sourceMesh.GetAabb(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(sourceMesh.GetMaterialAsset()); + + creator.SetMeshMaterialSlot(sourceMesh.GetMaterialSlotId()); // 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..0900949625 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp @@ -0,0 +1,34 @@ +/* + * 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 + { + // Normally this would be defined in the header file and substituted by the compiler, but for + // some reason clang doesn't accept it. + const ModelMaterialSlot::StableId ModelMaterialSlot::InvalidStableId = -1; + + 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/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index cdf0d0166d..368fb2c3c6 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -91,7 +92,7 @@ namespace UnitTest AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); uint32_t m_indexCount = 0; uint32_t m_vertexCount = 0; - AZ::Data::Asset m_material; + AZ::RPI::ModelMaterialSlot::StableId m_materialSlotId = AZ::RPI::ModelMaterialSlot::InvalidStableId; }; struct ExpectedLod @@ -106,6 +107,18 @@ namespace UnitTest AZStd::vector m_lods; }; + void SetUp() override + { + RPITestFixture::SetUp(); + + auto assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0); + auto typeId = AZ::AzTypeInfo::Uuid(); + m_materialAsset = AZ::Data::Asset(assetId, typeId, ""); + + // Some tests attempt to serialize-in the model asset, which should not attempt to actually load this dummy asset reference. + m_materialAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehaviorNamespace::NoLoad); + } + AZ::RHI::ShaderSemantic GetPositionSemantic() const { return AZ::RHI::ShaderSemantic(AZ::Name("POSITION")); @@ -136,6 +149,7 @@ namespace UnitTest return true; } + //! This function assumes the model has "sharedMeshCount + separateMeshCount" unique material slots, with incremental IDs starting at 0. AZ::Data::Asset BuildTestLod(const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedLod& expectedLod) { using namespace AZ; @@ -148,6 +162,8 @@ namespace UnitTest const uint32_t indexCount = 36; const uint32_t vertexCount = 36; + RPI::ModelMaterialSlot::StableId materialSlotId = 0; + if(sharedMeshCount > 0) { const uint32_t sharedIndexCount = indexCount * sharedMeshCount; @@ -164,7 +180,7 @@ namespace UnitTest ExpectedMesh expectedMesh; expectedMesh.m_indexCount = indexCount; expectedMesh.m_vertexCount = vertexCount; - expectedMesh.m_material = m_materialAsset; + expectedMesh.m_materialSlotId = i; RHI::BufferViewDescriptor indexBufferViewDescriptor = RHI::BufferViewDescriptor::CreateStructured(i * indexCount, indexCount, sizeof(uint32_t)); @@ -180,7 +196,7 @@ namespace UnitTest creator.BeginMesh(); Aabb aabb = expectedMesh.m_aabb; creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(materialSlotId++); creator.SetMeshIndexBuffer({ sharedIndexBuffer, indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { sharedPositionBuffer, vertexBufferViewDescriptor }); creator.EndMesh(); @@ -195,7 +211,7 @@ namespace UnitTest ExpectedMesh expectedMesh; expectedMesh.m_indexCount = indexCount; expectedMesh.m_vertexCount = vertexCount; - expectedMesh.m_material = m_materialAsset; + expectedMesh.m_materialSlotId = sharedMeshCount + i; RHI::BufferViewDescriptor indexBufferViewDescriptor = RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t)); @@ -213,7 +229,7 @@ namespace UnitTest creator.BeginMesh(); Aabb aabb = expectedMesh.m_aabb; creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(materialSlotId++); creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { positonBuffer, positionBufferViewDescriptor }); @@ -239,6 +255,15 @@ namespace UnitTest creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom())); creator.SetName("TestModel"); + + for (RPI::ModelMaterialSlot::StableId materialSlotId = 0; materialSlotId < sharedMeshCount + separateMeshCount; ++materialSlotId) + { + RPI::ModelMaterialSlot slot; + slot.m_defaultMaterialAsset = m_materialAsset; + slot.m_displayName = AZStd::string::format("Slot%d", materialSlotId); + slot.m_stableId = materialSlotId; + creator.AddMaterialSlot(slot); + } for (uint32_t i = 0; i < lodCount; ++i) { @@ -263,7 +288,7 @@ namespace UnitTest EXPECT_TRUE(mesh.GetAabb() == expectedMesh.m_aabb); EXPECT_TRUE(mesh.GetIndexCount() == expectedMesh.m_indexCount); EXPECT_TRUE(mesh.GetVertexCount() == expectedMesh.m_vertexCount); - EXPECT_TRUE(mesh.GetMaterialAsset() == expectedMesh.m_material); + EXPECT_TRUE(mesh.GetMaterialSlotId() == expectedMesh.m_materialSlotId); } void ValidateLodAsset(const AZ::RPI::ModelLodAsset* lodAsset, const ExpectedLod& expectedLod) @@ -299,9 +324,7 @@ namespace UnitTest } const uint32_t m_manyMesh = 100; // Not too much to hold up the tests but enough to stress them - AZ::Data::Asset m_materialAsset = - AZ::Data::Asset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0), - AZ::AzTypeInfo::Uuid(), ""); + AZ::Data::Asset m_materialAsset; }; @@ -687,11 +710,11 @@ namespace UnitTest } } - // Tests that if we try to set the material id on a mesh + // Tests that if we try to set the material slot on a mesh // without calling Begin or BeginMesh that it fails // as expected. Also tests the case that Begin *is* // called but BeginMesh is not. - TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh) + TEST_F(ModelTests, SetMaterialSlotNoBeginNoBeginMesh) { using namespace AZ; @@ -699,7 +722,7 @@ namespace UnitTest { ErrorMessageFinder messageFinder("Begin() was not called"); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); } creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom())); @@ -707,7 +730,7 @@ namespace UnitTest //This should still fail even if we call Begin but not BeginMesh { ErrorMessageFinder messageFinder("BeginMesh() was not called"); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); } } @@ -827,7 +850,7 @@ namespace UnitTest creator.BeginMesh(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor }); @@ -842,7 +865,7 @@ namespace UnitTest ErrorMessageFinder messageFinder("BeginMesh() was not called", 5); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor }); @@ -885,7 +908,7 @@ namespace UnitTest creator.BeginMesh(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor }); @@ -907,7 +930,7 @@ namespace UnitTest creator.BeginMesh(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(m_materialAsset); + creator.SetMeshMaterialSlot(0); creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor }); creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor }); @@ -1019,10 +1042,7 @@ namespace UnitTest lodCreator.BeginMesh(); lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({-1.0f, -1.0f, -0.5f}, {1.0f, 1.0f, 0.5f})); - lodCreator.SetMeshMaterialAsset( - AZ::Data::Asset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0), - AZ::AzTypeInfo::Uuid(), "") - ); + lodCreator.SetMeshMaterialSlot(AZ::Sfmt::GetInstance().Rand32()); { AZ::Data::Asset indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t)); 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 6b637a67c5..234ea99066 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -74,6 +74,10 @@ namespace AZ //! Get material assignment id matching lod and label substring virtual MaterialAssignmentId FindMaterialAssignmentId( const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0; + + //! 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..a4e058561e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -17,6 +17,7 @@ #include #include #include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include @@ -44,64 +45,15 @@ 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) { classElement.AddElementWithData(context, "materialSlotsByLodEnabled", true); } - + return true; } @@ -238,7 +190,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 +203,7 @@ namespace AZ for (auto& materialSlotPair : GetMaterialSlots()) { EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsLodAndAsset()) + if (materialSlot->m_id.IsLodAndSlotId()) { materialSlot->Clear(); } @@ -318,7 +270,7 @@ namespace AZ // Build the controller configuration from the editor configuration MaterialComponentConfig config = m_controller.GetConfiguration(); config.m_materials.clear(); - + for (const auto& materialSlotPair : GetMaterialSlots()) { const EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; @@ -341,7 +293,7 @@ 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_materialAsset = materialSlot->m_defaultMaterialAsset; materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; } @@ -362,6 +314,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,9 +340,33 @@ 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; + slot.m_propertyOverrides = materialFromController.m_propertyOverrides; slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides; @@ -400,13 +379,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,27 +431,26 @@ namespace AZ { AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); - + // First generating a unique set of all material asset IDs that will be used for source data generation - AZStd::unordered_set assetIds; + AZStd::unordered_map assetIdMap; auto materialSlots = GetMaterialSlots(); for (auto& materialSlotPair : materialSlots) { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.m_materialAssetId.IsValid()) + Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId(); + if (defaultMaterialAssetId.IsValid()) { - assetIds.insert(materialSlot->m_id.m_materialAssetId); + assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel(); } } // Convert the unique set of asset IDs into export items that can be configured in the dialog // The order should not matter because the table in the dialog can sort itself for a specific row EditorMaterialComponentExporter::ExportItemsContainer exportItems; - for (const AZ::Data::AssetId& assetId : assetIds) + for (auto assetIdInfo : assetIdMap) { - EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_assetId = assetId; + EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second}; exportItems.push_back(exportItem); } @@ -486,15 +464,20 @@ namespace AZ continue; } - const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0); + const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); if (assetIdOutcome) { for (auto& materialSlotPair : materialSlots) { - 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()); + // We need to check whether replaced material corresponds to this slot's default material. + if (editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) + { + editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); + } } } } @@ -582,3 +565,4 @@ namespace AZ } } // namespace Render } // namespace AZ + diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index bde500bdc5..32a36392a4 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp @@ -37,47 +37,7 @@ namespace AZ { namespace EditorMaterialComponentExporter { - AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId) - { - AZStd::string label; - if (assetId.IsValid()) - { - // Material assets that are exported through the scene pipeline have their filenames generated by adding - // the DCC material name as a prefix and a unique number to the end of the source file name. - // Rather than storing the DCC material name inside of the material asset we can reproduce it by removing - // the prefix and suffix from the product file name. - - // We need the material product path as the initial string that will be stripped down - const AZStd::string& productPath = AZ::RPI::AssetUtils::GetProductPathByAssetId(assetId); - if (!productPath.empty() && AzFramework::StringFunc::Path::GetFileName(productPath.c_str(), label)) - { - // If there is a source file, typically an FBX or other model file, we must get its filename to remove the prefix from the label - AZStd::string prefix; - const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId); - if (!sourcePath.empty() && AZ::StringFunc::Path::GetFileName(sourcePath.c_str(), prefix)) - { - if (!prefix.empty() && prefix.size() < label.size()) - { - if (AZ::StringFunc::StartsWith(label, prefix, false)) - { - // All of the product filename's tokens are separated by underscores so we must also remove the first underscore after the prefix - label = label.substr(prefix.size() + 1); - } - } - } - - // We can remove the numeric suffix by stripping the label of everything after the last underscore - const auto iter = label.find_last_of("_"); - if (iter != AZStd::string::npos) - { - label = label.substr(0, iter); - } - } - } - return label; - } - - AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId) + AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName) { AZStd::string exportPath; if (assetId.IsValid()) @@ -85,7 +45,7 @@ namespace AZ exportPath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId); AZ::StringFunc::Path::StripExtension(exportPath); exportPath += "_"; - exportPath += GetLabelByAssetId(assetId); + exportPath += materialSlotName; exportPath += "."; exportPath += AZ::RPI::MaterialSourceData::Extension; AZ::StringFunc::Path::Normalize(exportPath); @@ -132,12 +92,12 @@ namespace AZ int row = 0; for (ExportItem& exportItem : exportItems) { - QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_assetId).c_str()); + QFileInfo fileInfo(GetExportPathByAssetId(exportItem.GetOriginalAssetId(), exportItem.GetMaterialSlotName()).c_str()); // Configuring initial settings based on whether or not the target file already exists - exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); - exportItem.m_exists = fileInfo.exists(); - exportItem.m_overwrite = false; + exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData()); + exportItem.SetExists(fileInfo.exists()); + exportItem.SetOverwrite(false); // Populate the table with data for every column tableWidget->setItem(row, MaterialSlotColumn, new QTableWidgetItem()); @@ -146,23 +106,23 @@ 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->setChecked(exportItem.GetEnabled()); + materialSlotCheckBox->setText(exportItem.GetMaterialSlotName().c_str()); tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox); // Create a file picker widget for selecting the save path for the exported material AzQtComponents::BrowseEdit* materialFileWidget = new AzQtComponents::BrowseEdit(tableWidget); materialFileWidget->setLineEditReadOnly(true); materialFileWidget->setClearButtonEnabled(false); - materialFileWidget->setEnabled(exportItem.m_enabled); + materialFileWidget->setEnabled(exportItem.GetEnabled()); materialFileWidget->setText(fileInfo.fileName()); tableWidget->setCellWidget(row, MaterialFileColumn, materialFileWidget); // Create a check box for toggling the overwrite state of this item QWidget* overwriteCheckBoxContainer = new QWidget(tableWidget); QCheckBox* overwriteCheckBox = new QCheckBox(overwriteCheckBoxContainer); - overwriteCheckBox->setChecked(exportItem.m_overwrite); - overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); + overwriteCheckBox->setChecked(exportItem.GetOverwrite()); + overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists()); overwriteCheckBoxContainer->setLayout(new QHBoxLayout(overwriteCheckBoxContainer)); overwriteCheckBoxContainer->layout()->addWidget(overwriteCheckBox); @@ -173,21 +133,21 @@ namespace AZ // Whenever the selection is updated, automatically apply the change to the export item QObject::connect(materialSlotCheckBox, &QCheckBox::stateChanged, materialSlotCheckBox, [&exportItem, materialFileWidget, materialSlotCheckBox, overwriteCheckBox]([[maybe_unused]] int state) { - exportItem.m_enabled = materialSlotCheckBox->isChecked(); - materialFileWidget->setEnabled(exportItem.m_enabled); - overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); + exportItem.SetEnabled(materialSlotCheckBox->isChecked()); + materialFileWidget->setEnabled(exportItem.GetEnabled()); + overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists()); }); // Whenever the overwrite check box is updated, automatically apply the change to the export item QObject::connect(overwriteCheckBox, &QCheckBox::stateChanged, overwriteCheckBox, [&exportItem, overwriteCheckBox]([[maybe_unused]] int state) { - exportItem.m_overwrite = overwriteCheckBox->isChecked(); + exportItem.SetOverwrite(overwriteCheckBox->isChecked()); }); // Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&dialog, &exportItem, materialFileWidget, overwriteCheckBox]() { QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog, QString("Select Material Filename"), - exportItem.m_exportPath.c_str(), + exportItem.GetExportPath().c_str(), QString("Material (*.material)"), nullptr, QFileDialog::DontConfirmOverwrite); @@ -195,14 +155,14 @@ namespace AZ // Only update the export data if a valid path and filename was selected if (!fileInfo.absoluteFilePath().isEmpty()) { - exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); - exportItem.m_exists = fileInfo.exists(); - exportItem.m_overwrite = fileInfo.exists(); + exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData()); + exportItem.SetExists(fileInfo.exists()); + exportItem.SetOverwrite(fileInfo.exists()); // Update the controls to display the new state materialFileWidget->setText(fileInfo.fileName()); - overwriteCheckBox->setChecked(exportItem.m_overwrite); - overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); + overwriteCheckBox->setChecked(exportItem.GetOverwrite()); + overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists()); } }); @@ -245,24 +205,24 @@ namespace AZ bool ExportMaterialSourceData(const ExportItem& exportItem) { - if (!exportItem.m_enabled || exportItem.m_exportPath.empty()) + if (!exportItem.GetEnabled() || exportItem.GetExportPath().empty()) { return false; } - if (exportItem.m_exists && !exportItem.m_overwrite) + if (exportItem.GetExists() && !exportItem.GetOverwrite()) { return true; } EditorMaterialComponentUtil::MaterialEditData editData; - if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData)) + if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.GetOriginalAssetId(), editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data."); return false; } - if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.m_exportPath, editData)) + if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.GetExportPath(), editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to save 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..4830db33c4 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h @@ -19,27 +19,48 @@ namespace AZ { namespace EditorMaterialComponentExporter { - // 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 + AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName); - // Generates a destination file path for exporting material source data - AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId); - - struct ExportItem + class ExportItem { + public: + //! @param originalAssetId AssetId of the original built-in material, which will be exported. + //! @param materialSlotName The name of the material slot will be used as part of the exported file name. + ExportItem(AZ::Data::AssetId originalAssetId, const AZStd::string& materialSlotName) + : m_originalAssetId(originalAssetId) + , m_materialSlotName(materialSlotName) + {} + + void SetEnabled(bool enabled) { m_enabled = enabled; } + void SetExists(bool exists) { m_exists = exists; } + void SetOverwrite(bool overwrite) { m_overwrite = overwrite; } + void SetExportPath(const AZStd::string& exportPath) { m_exportPath = exportPath; } + + bool GetEnabled() const { return m_enabled; } + bool GetExists() const { return m_exists; } + bool GetOverwrite() const { return m_overwrite; } + const AZStd::string& GetExportPath() const { return m_exportPath; } + + AZ::Data::AssetId GetOriginalAssetId() const { return m_originalAssetId; } + const AZStd::string& GetMaterialSlotName() const { return m_materialSlotName; } + + private: bool m_enabled = true; bool m_exists = false; bool m_overwrite = false; - AZ::Data::AssetId m_assetId; AZStd::string m_exportPath; + AZ::Data::AssetId m_originalAssetId; //!< AssetId of the original built-in material, which will be exported. + AZStd::string m_materialSlotName; }; 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..39dde65a99 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); } @@ -80,9 +80,10 @@ namespace AZ if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) + ->Version(6, &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,21 @@ 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; + EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label}; exportItems.push_back(exportItem); } @@ -218,7 +204,7 @@ namespace AZ } // Generate a new asset ID utilizing the export file path so that we can update this material slot to reference the new asset - const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0); + const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); if (assetIdOutcome) { m_materialAsset.Create(assetIdOutcome.GetValue()); @@ -258,7 +244,7 @@ namespace AZ // Treated as a special property. It will be updated together with properties. OnPropertyChanged(); }; - + if (m_materialAsset.GetId().IsValid()) { if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) @@ -275,7 +261,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 fa4586daba..cf548e965a 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->GetMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentId MeshComponentController::FindMaterialAssignmentId( const MaterialAssignmentLodIndex lod, const AZStd::string& label) const diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index d99d5000eb..63dba83fef 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -113,6 +113,7 @@ namespace AZ // MaterialReceiverRequestBus::Handler overrides ... virtual MaterialAssignmentId FindMaterialAssignmentId( const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; + 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..feabdb9510 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -96,12 +96,11 @@ 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_materialSlot = actor->GetMeshAsset()->FindMaterialSlot(modelMesh.GetMaterialSlotId()); // 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_materialSlot.m_defaultMaterialAsset.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 706ed27a4d..2711ce2f76 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->GetMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentId AtomActorInstance::FindMaterialAssignmentId( const MaterialAssignmentLodIndex lod, const AZStd::string& label) const @@ -501,16 +514,18 @@ namespace AZ const AZStd::vector< SkinnedSubMeshProperties>& subMeshProperties = inputLod.GetSubMeshProperties(); for (const SkinnedSubMeshProperties& submesh : subMeshProperties) { - AZ_Error("AtomActorInstance", submesh.m_material, "Actor does not have a valid default material in lod %d", lodIndex); - if (submesh.m_material) + Data::Asset materialAsset = submesh.m_materialSlot.m_defaultMaterialAsset; + AZ_Error("AtomActorInstance", materialAsset, "Actor does not have a valid default material in lod %d", lodIndex); + + if (materialAsset) { - if (!submesh.m_material->IsReady()) + if (!materialAsset->IsReady()) { // Start listening for the material's OnAssetReady event. // AtomActorInstance::Create is called on the main thread, so there should be no need to synchronize with the OnAssetReady event handler // since those events will also come from the main thread - m_waitForMaterialLoadIds.insert(submesh.m_material->GetId()); - Data::AssetBus::MultiHandler::BusConnect(submesh.m_material->GetId()); + m_waitForMaterialLoadIds.insert(materialAsset->GetId()); + Data::AssetBus::MultiHandler::BusConnect(materialAsset->GetId()); } } } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index 1686b52d1a..b3e1a8a989 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -122,6 +122,7 @@ namespace AZ // MaterialReceiverRequestBus::Handler overrides... virtual MaterialAssignmentId FindMaterialAssignmentId( const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; + 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..daa0ad59f8 100644 --- a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp +++ b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp @@ -112,17 +112,8 @@ namespace WhiteBox AddLodBuffers(modelLodCreator); modelLodCreator.BeginMesh(); modelLodCreator.SetMeshAabb(meshData.GetAabb()); - - // set the default material - if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath(TexturedMaterialPath.data())) - { - modelLodCreator.SetMeshMaterialAsset(materialAsset); - } - else - { - AZ_Error("CreateLodAsset", false, "Could not load material."); - return false; - } + + modelLodCreator.SetMeshMaterialSlot(OneMaterialSlotId); AddMeshBuffers(modelLodCreator); modelLodCreator.EndMesh(); @@ -154,6 +145,20 @@ namespace WhiteBox modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom())); modelCreator.SetName(ModelName); modelCreator.AddLodAsset(AZStd::move(m_lodAsset)); + + if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath(TexturedMaterialPath.data())) + { + AZ::RPI::ModelMaterialSlot materialSlot; + materialSlot.m_stableId = OneMaterialSlotId; + materialSlot.m_defaultMaterialAsset = materialAsset; + modelCreator.AddMaterialSlot(materialSlot); + } + else + { + AZ_Error("CreateLodAsset", false, "Could not load material."); + return; + } + modelCreator.End(m_modelAsset); } diff --git a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.h b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.h index 00179f196d..63aca62051 100644 --- a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.h +++ b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.h @@ -91,6 +91,7 @@ namespace WhiteBox // TODO: LYN-784 static constexpr AZStd::string_view TexturedMaterialPath = "materials/defaultpbr.azmaterial"; static constexpr AZStd::string_view SolidMaterialPath = "materials/defaultpbr.azmaterial"; + static constexpr AZ::RPI::ModelMaterialSlot::StableId OneMaterialSlotId = 0; //! White box model name. static constexpr AZStd::string_view ModelName = "WhiteBoxMesh";