From 0cf6ecf3f7ff64cd987917a1ab64d9f73fe3d89a Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 13 Jul 2021 16:25:10 -0700 Subject: [PATCH 01/22] Deleted unused "default" materials from RPI. Long ago these were used as defaults for FBX material conversion process, but that's no longer the case. And I'm about to add a new approach for default material conversion in SceneAPI. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../RPI/Assets/Materials/Default.materialtype | 90 ------------- .../RPI/Assets/Materials/DefaultMaterial.azsl | 127 ------------------ .../Assets/Materials/DefaultMaterial.shader | 26 ---- .../Materials/DefaultMaterial_DepthPass.azsl | 33 ----- .../DefaultMaterial_DepthPass.shader | 20 --- .../RPI/Assets/atom_rpi_asset_files.cmake | 5 - 6 files changed, 301 deletions(-) delete mode 100644 Gems/Atom/RPI/Assets/Materials/Default.materialtype delete mode 100644 Gems/Atom/RPI/Assets/Materials/DefaultMaterial.azsl delete mode 100644 Gems/Atom/RPI/Assets/Materials/DefaultMaterial.shader delete mode 100644 Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.azsl delete mode 100644 Gems/Atom/RPI/Assets/Materials/DefaultMaterial_DepthPass.shader 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 From 14d2e38b90c07bf2bd4fc0afc7d2e3409d88e84f Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Sat, 17 Jul 2021 00:07:23 -0700 Subject: [PATCH 02/22] Refactored how model material slots work in preparation to support more flexible material conversion options for the scene asset pipeline. The material slot IDs are based on the MaterialUid that come from SceneAPI. Since these IDs are also used as the AssetId sub-ID for the converted material assets, the system was just checking the material asset sub-ID to determine the material slot ID. But in order to support certain FBX material conversion options, we needed to break this tie, so the slot ID is separate from the AssetId of the material in that slot. This will allow some other material to be used in the slot, instead of being forced to use one that was generated from the FBX. Here we inttroduce a new struct ModelMaterialSlot which formalizes the concept of material slot, with an ID, display name, and default material assignment. The ID still comes from the MaterialUid like before. The display name is built-in, rather than being parsed out from the asset file name. And the default material assignment can be any material asset, it doesn't have to come from the FBX (or other scene file). This commit is just the preliminary set of changes. Cursory testing shows that it works pretty well but more testing is needed (and likely some fixes) before merging. Here is what's left to do... Add serialization version converters to preserve prior prefab data. See if we can get rid of GetLabelByAssetId function only rely on the display name inside ModelMaterialSlot. I'm not sure if the condition for enabling the "Edit Material Instance..." context menu item is correct. Test actors Lots more testing in general Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Feature/Material/MaterialAssignmentId.h | 35 +++-- .../Source/Material/MaterialAssignment.cpp | 6 +- .../Source/Material/MaterialAssignmentId.cpp | 51 ++++--- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 20 +-- .../SkinnedMesh/SkinnedMeshInputBuffers.cpp | 7 +- .../Include/Atom/RPI.Public/Model/ModelLod.h | 2 + .../Atom/RPI.Reflect/Model/ModelAsset.h | 3 + .../Atom/RPI.Reflect/Model/ModelLodAsset.h | 29 +++- .../RPI.Reflect/Model/ModelLodAssetCreator.h | 6 +- .../RPI.Reflect/Model/ModelMaterialSlot.h | 43 ++++++ .../Model/ModelAssetBuilderComponent.cpp | 10 +- .../Code/Source/RPI.Public/Model/ModelLod.cpp | 9 +- .../Source/RPI.Public/Model/ModelSystem.cpp | 1 + .../Source/RPI.Reflect/Model/ModelAsset.cpp | 24 ++++ .../RPI.Reflect/Model/ModelLodAsset.cpp | 48 ++++++- .../Model/ModelLodAssetCreator.cpp | 32 ++++- .../RPI.Reflect/Model/ModelMaterialSlot.cpp | 30 ++++ .../RPI/Code/atom_rpi_reflect_files.cmake | 2 + .../Material/MaterialComponentBus.h | 3 + .../Material/EditorMaterialComponent.cpp | 136 +++++++++--------- .../EditorMaterialComponentExporter.cpp | 6 +- .../EditorMaterialComponentExporter.h | 11 +- .../Material/EditorMaterialComponentSlot.cpp | 33 ++--- .../Material/EditorMaterialComponentSlot.h | 4 +- .../Material/MaterialComponentConfig.cpp | 2 +- .../Source/Mesh/MeshComponentController.cpp | 13 ++ .../Source/Mesh/MeshComponentController.h | 1 + .../EMotionFXAtom/Code/Source/ActorAsset.cpp | 8 +- .../Code/Source/AtomActorInstance.cpp | 13 ++ .../Code/Source/AtomActorInstance.h | 1 + .../Rendering/Atom/WhiteBoxAtomRenderMesh.cpp | 5 +- 31 files changed, 399 insertions(+), 195 deletions(-) create mode 100644 Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h create mode 100644 Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h index dd198a7179..d9ae8099da 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignmentId.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ { @@ -23,7 +24,7 @@ namespace AZ using MaterialAssignmentLodIndex = AZ::u64; //! MaterialAssignmentId is used to address available and overridable material slots on a model. - //! The LOD and one of the model's original material asset IDs are used as coordinates that identify + //! The LOD and one of the model's original material slot IDs are used as coordinates that identify //! a specific material slot or a set of slots matching either. struct MaterialAssignmentId final { @@ -33,41 +34,39 @@ namespace AZ MaterialAssignmentId() = default; - MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, const AZ::Data::AssetId& materialAssetId); + MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Create an ID that maps to all material slots, regardless of asset ID or LOD, effectively applying to an entire model. + //! Create an ID that maps to all material slots, regardless of slot ID or LOD, effectively applying to an entire model. static MaterialAssignmentId CreateDefault(); - //! Create an ID that maps to all material slots with a corresponding asset ID, regardless of LOD. - static MaterialAssignmentId CreateFromAssetOnly(AZ::Data::AssetId materialAssetId); + //! Create an ID that maps to all material slots with a corresponding slot ID, regardless of LOD. + static MaterialAssignmentId CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Create an ID that maps to a specific material slot with a corresponding asset ID and LOD. - static MaterialAssignmentId CreateFromLodAndAsset(MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId); + //! Create an ID that maps to a specific material slot with a corresponding stable ID and LOD. + static MaterialAssignmentId CreateFromLodAndStableId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId); - //! Returns true if the asset ID and LOD are invalid + //! Returns true if the slot stable ID and LOD are invalid, meaning this assignment applies to the entire model. bool IsDefault() const; - //! Returns true if the asset ID is valid and LOD is invalid - bool IsAssetOnly() const; + //! Returns true if the slot stable ID is valid and LOD is invalid, meaning this assignment applies to every LOD. + bool IsSlotIdOnly() const; - //! Returns true if the asset ID and LOD are both valid - bool IsLodAndAsset() const; + //! Returns true if the slot stable ID and LOD are both valid, meaning this assignment applies to a single material slot on a specific LOD. + bool IsLodAndSlotId() const; - //! Creates a string composed of the asset path and LOD + //! Creates a string describing all the details of the assignment ID AZStd::string ToString() const; - //! Creates a hash composed of the asset ID sub ID and LOD + //! Creates a hash composed of all elements of the assignment ID size_t GetHash() const; - //! Returns true if both asset ID sub IDs and LODs match bool operator==(const MaterialAssignmentId& rhs) const; - - //! Returns true if both asset ID sub IDs and LODs do not match bool operator!=(const MaterialAssignmentId& rhs) const; static constexpr MaterialAssignmentLodIndex NonLodIndex = -1; + MaterialAssignmentLodIndex m_lodIndex = NonLodIndex; - AZ::Data::AssetId m_materialAssetId = AZ::Data::AssetId(); + RPI::ModelMaterialSlot::StableId m_materialSlotStableId = RPI::ModelMaterialSlot::InvalidStableId; }; } // namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ec48d57d1a..9ae4f65304 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -123,7 +123,7 @@ namespace AZ } const MaterialAssignment& assetAssignment = - GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromAssetOnly(id.m_materialAssetId)); + GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromStableIdOnly(id.m_materialSlotStableId)); if (assetAssignment.m_materialInstance.get()) { return assetAssignment; @@ -152,11 +152,11 @@ namespace AZ { if (mesh.m_material) { - const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromAssetOnly(mesh.m_material->GetAssetId()); + const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromStableIdOnly(mesh.m_materialSlotStableId); materials[generalId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material); const MaterialAssignmentId specificId = - MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId()); + MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, mesh.m_materialSlotStableId); materials[specificId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp index 71dea0596f..8b6b0be237 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp @@ -19,9 +19,9 @@ namespace AZ if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("lodIndex", &MaterialAssignmentId::m_lodIndex) - ->Field("materialAssetId", &MaterialAssignmentId::m_materialAssetId) + ->Field("materialSlotStableId", &MaterialAssignmentId::m_materialSlotStableId) ; } @@ -33,75 +33,72 @@ namespace AZ ->Attribute(AZ::Script::Attributes::Module, "render") ->Constructor() ->Constructor() - ->Constructor() + ->Constructor() ->Method("IsDefault", &MaterialAssignmentId::IsDefault) - ->Method("IsAssetOnly", &MaterialAssignmentId::IsAssetOnly) - ->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndAsset) + ->Method("IsAssetOnly", &MaterialAssignmentId::IsSlotIdOnly) // Included for compatibility. Use "IsSlotIdOnly" instead. + ->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndSlotId) // Included for compatibility. Use "IsLodAndSlotId" instead. + ->Method("IsSlotIdOnly", &MaterialAssignmentId::IsSlotIdOnly) + ->Method("IsLodAndSlotId", &MaterialAssignmentId::IsLodAndSlotId) ->Method("ToString", &MaterialAssignmentId::ToString) ->Property("lodIndex", BehaviorValueProperty(&MaterialAssignmentId::m_lodIndex)) - ->Property("materialAssetId", BehaviorValueProperty(&MaterialAssignmentId::m_materialAssetId)) + ->Property("materialSlotStableId", BehaviorValueProperty(&MaterialAssignmentId::m_materialSlotStableId)) ; } } - MaterialAssignmentId::MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, const AZ::Data::AssetId& materialAssetId) + MaterialAssignmentId::MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId) : m_lodIndex(lodIndex) - , m_materialAssetId(materialAssetId) + , m_materialSlotStableId(materialSlotStableId) { } MaterialAssignmentId MaterialAssignmentId::CreateDefault() { - return MaterialAssignmentId(NonLodIndex, AZ::Data::AssetId()); + return MaterialAssignmentId(NonLodIndex, RPI::ModelMaterialSlot::InvalidStableId); } - MaterialAssignmentId MaterialAssignmentId::CreateFromAssetOnly(AZ::Data::AssetId materialAssetId) + MaterialAssignmentId MaterialAssignmentId::CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId) { - return MaterialAssignmentId(NonLodIndex, materialAssetId); + return MaterialAssignmentId(NonLodIndex, materialSlotStableId); } - MaterialAssignmentId MaterialAssignmentId::CreateFromLodAndAsset( - MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId) + MaterialAssignmentId MaterialAssignmentId::CreateFromLodAndStableId( + MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId) { - return MaterialAssignmentId(lodIndex, materialAssetId); + return MaterialAssignmentId(lodIndex, materialSlotStableId); } bool MaterialAssignmentId::IsDefault() const { - return m_lodIndex == NonLodIndex && !m_materialAssetId.IsValid(); + return m_lodIndex == NonLodIndex && m_materialSlotStableId == RPI::ModelMaterialSlot::InvalidStableId; } - bool MaterialAssignmentId::IsAssetOnly() const + bool MaterialAssignmentId::IsSlotIdOnly() const { - return m_lodIndex == NonLodIndex && m_materialAssetId.IsValid(); + return m_lodIndex == NonLodIndex && m_materialSlotStableId != RPI::ModelMaterialSlot::InvalidStableId; } - bool MaterialAssignmentId::IsLodAndAsset() const + bool MaterialAssignmentId::IsLodAndSlotId() const { - return m_lodIndex != NonLodIndex && m_materialAssetId.IsValid(); + return m_lodIndex != NonLodIndex && m_materialSlotStableId != RPI::ModelMaterialSlot::InvalidStableId; } AZStd::string MaterialAssignmentId::ToString() const { - AZStd::string assetPathString; - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_materialAssetId); - AZ::StringFunc::Path::StripPath(assetPathString); - AZ::StringFunc::Path::StripExtension(assetPathString); - return AZStd::string::format("%s:%llu", assetPathString.c_str(), m_lodIndex); + return AZStd::string::format("%u:%llu", m_materialSlotStableId, m_lodIndex); } size_t MaterialAssignmentId::GetHash() const { size_t seed = 0; AZStd::hash_combine(seed, m_lodIndex); - AZStd::hash_combine(seed, m_materialAssetId.m_subId); + AZStd::hash_combine(seed, m_materialSlotStableId); return seed; } bool MaterialAssignmentId::operator==(const MaterialAssignmentId& rhs) const { - return m_lodIndex == rhs.m_lodIndex && m_materialAssetId.m_subId == rhs.m_materialAssetId.m_subId; + return m_lodIndex == rhs.m_lodIndex && m_materialSlotStableId == rhs.m_materialSlotStableId; } bool MaterialAssignmentId::operator!=(const MaterialAssignmentId& rhs) const diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index c6362f3a5d..6e031aa853 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -589,18 +589,6 @@ namespace AZ { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - auto modelAsset = model->GetModelAsset(); - for (const auto& modelLodAsset : modelAsset->GetLodAssets()) - { - for (const auto& mesh : modelLodAsset->GetMeshes()) - { - if (mesh.GetMaterialAsset().GetStatus() != Data::AssetData::AssetStatus::Ready) - { - - } - } - } - m_model = model; const size_t modelLodCount = m_model->GetLodCount(); m_drawPacketListsByLod.resize(modelLodCount); @@ -644,10 +632,12 @@ namespace AZ for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex) { - Data::Instance material = modelLod.GetMeshes()[meshIndex].m_material; + const RPI::ModelLod::Mesh& mesh = modelLod.GetMeshes()[meshIndex]; + + Data::Instance material = mesh.m_material; // Determine if there is a material override specified for this sub mesh - const MaterialAssignmentId materialAssignmentId(modelLodIndex, material ? material->GetAssetId() : AZ::Data::AssetId()); + const MaterialAssignmentId materialAssignmentId(modelLodIndex, mesh.m_materialSlotStableId); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId); if (materialAssignment.m_materialInstance.get()) { @@ -790,7 +780,7 @@ namespace AZ // retrieve the material Data::Instance material = mesh.m_material; - const MaterialAssignmentId materialAssignmentId(rayTracingLod, material ? material->GetAssetId() : AZ::Data::AssetId()); + const MaterialAssignmentId materialAssignmentId(rayTracingLod, mesh.m_materialSlotStableId); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId); if (materialAssignment.m_materialInstance.get()) { diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp index 155b751272..640e30d0f6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp @@ -639,8 +639,13 @@ namespace AZ Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); + + // Create a separate material slot for each sub-mesh + AZ::RPI::ModelMaterialSlot materialSlot; + materialSlot.m_stableId = i; + materialSlot.m_defaultMaterialAsset = lod.m_subMeshProperties[i].m_material; - modelLodCreator.SetMeshMaterialAsset(lod.m_subMeshProperties[i].m_material); + modelLodCreator.SetMeshMaterialSlot(materialSlot); modelLodCreator.EndMesh(); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h index dcc4813b3d..36892d0027 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/ModelLod.h @@ -72,6 +72,8 @@ namespace AZ RHI::IndexBufferView m_indexBufferView; StreamInfoList m_streamInfo; + + ModelMaterialSlot::StableId m_materialSlotStableId = ModelMaterialSlot::InvalidStableId; //! The default material assigned to the mesh by the asset. Data::Instance m_material; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h index b7414f73a2..891aec04b3 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h @@ -49,6 +49,9 @@ namespace AZ //! Returns the model-space axis aligned bounding box const AZ::Aabb& GetAabb() const; + + //! Returns the list of all ModelMaterialSlot's for the model, across all LODs. + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const; //! Returns the number of Lods in the model size_t GetLodCount() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h index 4e86b0e367..61e2ebeb05 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAsset.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -84,8 +85,9 @@ namespace AZ //! Returns the number of indices in this mesh uint32_t GetIndexCount() const; - //! Returns the reference to material asset used by this mesh - const Data::Asset & GetMaterialAsset() const; + //! Returns the index of the material slot used by this mesh. + //! This indexes into the ModelLodAsset's material slot list. + size_t GetMaterialSlotIndex() const; //! Returns the name of this mesh const AZ::Name& GetName() const; @@ -124,7 +126,9 @@ namespace AZ AZ::Name m_name; AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); - Data::Asset m_materialAsset{ Data::AssetLoadBehavior::PreLoad }; + // Identifies the material that is used by this mesh. + // References material slot in the ModelLodAsset that owns this mesh; see ModelLodAsset::GetMaterialSlot(). + size_t m_materialSlotIndex = 0; // Both the buffer in m_indexBufferAssetView and the buffers in m_streamBufferInfo // may point to either unique buffers for the mesh or to consolidated @@ -143,11 +147,21 @@ namespace AZ //! Returns the model-space axis-aligned bounding box of all meshes in the lod const AZ::Aabb& GetAabb() const; + + //! Returns an array view into the collection of material slots available to this lod + AZStd::array_view GetMaterialSlots() const; + + //! Returns a specific material slot by index, with error checking. + //! The index can be retrieved from Mesh::GetMaterialSlotIndex(). + const ModelMaterialSlot& GetMaterialSlot(size_t slotIndex) const; + + //! Find a material slot with the given stableId, or returns null if it isn't found. + const ModelMaterialSlot* FindMaterialSlot(uint32_t stableId) const; private: AZStd::vector m_meshes; AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); - + // These buffers owned by the lod are the consolidated super buffers. // Meshes may either have views into these buffers or they may own // their own buffers. @@ -155,6 +169,13 @@ namespace AZ Data::Asset m_indexBuffer; AZStd::vector> m_streamBuffers; + // Lists all of the material slots that are used by this LOD. + // Note the same slot can appear in multiple LODs in the model, so that LODs don't have to refer back to the model asset. + AZStd::vector m_materialSlots; + + // A default ModelMaterialSlot to be returned upon error conditions. + ModelMaterialSlot m_fallbackSlot; + void AddMesh(const Mesh& mesh); void SetReady(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h index c3291e5c73..5672b49f68 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include @@ -45,9 +46,10 @@ namespace AZ //! Begin and BeginMesh must be called first. void SetMeshAabb(AZ::Aabb&& aabb); - //! Sets the material asset for the current SubMesh. + //! Sets the material slot data for the current SubMesh. + //! Adds a new material slot to the ModelLodAsset if it doesn't already exist. //! Begin and BeginMesh must be called first - void SetMeshMaterialAsset(const Data::Asset& materialAsset); + void SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot); //! Sets the given BufferAssetView to the current SubMesh as the index buffer. //! Begin and BeginMesh must be called first diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h new file mode 100644 index 0000000000..27137459b7 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AZ +{ + class ReflectContext; + + namespace RPI + { + //! Use by model assets to identify a logical material slot. + //! Each slot has a unique ID, a name, and a default material. Each mesh in model will reference a single ModelMaterialSlot. + //! Other classes like MeshFeatureProcessor and MaterialComponent can override the material associated with individual slots + //! to alter the default appearance of the mesh. + struct ModelMaterialSlot + { + AZ_TYPE_INFO(ModelMaterialSlot, "{0E88A62A-D83D-4C1B-8DE7-CE972B8124B5}"); + + static void Reflect(AZ::ReflectContext* context); + + using StableId = uint32_t; + static const StableId InvalidStableId = -1; + + //! This ID must have a consistent value when the asset is reprocessed by the asset pipeline, and must be unique within the ModelLodAsset. + //! In practice, this set using the MaterialUid from SceneAPI. See ModelAssetBuilderComponent::CreateMesh. + StableId m_stableId = InvalidStableId; + + Name m_displayName; //!< The name of the slot as displayed to the user in UI. (Using Name instead of string for fast copies) + + Data::Asset m_defaultMaterialAsset{ Data::AssetLoadBehavior::PreLoad }; //!< The material that will be applied to this slot by default. + }; + + using ModelMaterialSlotMap = AZStd::unordered_map; + + } //namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp index aa2ad37647..608ee17d95 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp @@ -109,7 +109,7 @@ namespace AZ if (auto* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(27); // [ATOM-15658] + ->Version(29); // (updated to separate material slot ID from default material asset) } } @@ -1806,8 +1806,12 @@ namespace AZ auto iter = materialAssetsByUid.find(meshView.m_materialUid); if (iter != materialAssetsByUid.end()) { - const Data::Asset& materialAsset = iter->second.m_asset; - lodAssetCreator.SetMeshMaterialAsset(materialAsset); + ModelMaterialSlot materialSlot; + materialSlot.m_stableId = meshView.m_materialUid; + materialSlot.m_displayName = iter->second.m_name; + materialSlot.m_defaultMaterialAsset = iter->second.m_asset; + + lodAssetCreator.SetMeshMaterialSlot(materialSlot); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp index b68930e4a3..2dfee9e2f1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelLod.cpp @@ -100,10 +100,13 @@ namespace AZ } } - auto& materialAsset = mesh.GetMaterialAsset(); - if (materialAsset.IsReady()) + const ModelMaterialSlot& materialSlot = lodAsset.GetMaterialSlot(mesh.GetMaterialSlotIndex()); + + meshInstance.m_materialSlotStableId = materialSlot.m_stableId; + + if (materialSlot.m_defaultMaterialAsset.IsReady()) { - meshInstance.m_material = Material::FindOrCreate(materialAsset); + meshInstance.m_material = Material::FindOrCreate(materialSlot.m_defaultMaterialAsset); } m_meshes.emplace_back(AZStd::move(meshInstance)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp index 610f183eb1..b9781843cf 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp @@ -24,6 +24,7 @@ namespace AZ { ModelLodAsset::Reflect(context); ModelAsset::Reflect(context); + ModelMaterialSlot::Reflect(context); MorphTargetMetaAsset::Reflect(context); SkinMetaAsset::Reflect(context); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp index 083de10d47..486faafbdb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp @@ -56,6 +56,30 @@ namespace AZ { return m_aabb; } + + RPI::ModelMaterialSlotMap ModelAsset::GetModelMaterialSlots() const + { + RPI::ModelMaterialSlotMap slotMap; + + for (const Data::Asset& lod : GetLodAssets()) + { + for (const AZ::RPI::ModelMaterialSlot& materialSlot : lod->GetMaterialSlots()) + { + auto iter = slotMap.find(materialSlot.m_stableId); + if (iter == slotMap.end()) + { + slotMap.emplace(materialSlot.m_stableId, materialSlot); + } + else + { + AZ_Assert(materialSlot.m_displayName == iter->second.m_displayName && materialSlot.m_defaultMaterialAsset.GetId() == iter->second.m_defaultMaterialAsset.GetId(), + "Multiple LODs have mismatched data for the same material slot."); + } + } + } + + return slotMap; + } size_t ModelAsset::GetLodCount() const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp index 190e4c5315..4811f6a1db 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp @@ -23,24 +23,25 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("Meshes", &ModelLodAsset::m_meshes) ->Field("Aabb", &ModelLodAsset::m_aabb) + ->Field("MaterialSlots", &ModelLodAsset::m_materialSlots) ; } Mesh::Reflect(context); } - + void ModelLodAsset::Mesh::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) - ->Field("Material", &ModelLodAsset::Mesh::m_materialAsset) + ->Version(1) ->Field("Name", &ModelLodAsset::Mesh::m_name) ->Field("AABB", &ModelLodAsset::Mesh::m_aabb) + ->Field("MaterialSlotIndex", &ModelLodAsset::Mesh::m_materialSlotIndex) ->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView) ->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo) ; @@ -75,9 +76,9 @@ namespace AZ return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount; } - const Data::Asset & ModelLodAsset::Mesh::GetMaterialAsset() const + size_t ModelLodAsset::Mesh::GetMaterialSlotIndex() const { - return m_materialAsset; + return m_materialSlotIndex; } const AZ::Name& ModelLodAsset::Mesh::GetName() const @@ -118,6 +119,41 @@ namespace AZ { return m_aabb; } + + AZStd::array_view ModelLodAsset::GetMaterialSlots() const + { + return m_materialSlots; + } + + const ModelMaterialSlot& ModelLodAsset::GetMaterialSlot(size_t slotIndex) const + { + if (slotIndex < m_materialSlots.size()) + { + return m_materialSlots[slotIndex]; + } + else + { + AZ_Error("ModelAsset", false, "Material slot index %zu out of range. ModelAsset has %zu slots.", slotIndex, m_materialSlots.size()); + return m_fallbackSlot; + } + } + + const ModelMaterialSlot* ModelLodAsset::FindMaterialSlot(uint32_t stableId) const + { + auto iter = AZStd::find_if(m_materialSlots.begin(), m_materialSlots.end(), [&stableId](const ModelMaterialSlot& existingMaterialSlot) + { + return existingMaterialSlot.m_stableId == stableId; + }); + + if (iter == m_materialSlots.end()) + { + return nullptr; + } + else + { + return iter; + } + } const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp index 6f17a5c590..5a066d2517 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp @@ -60,12 +60,32 @@ namespace AZ m_currentMesh.m_aabb = AZStd::move(aabb); } } - - void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset& materialAsset) + + void ModelLodAssetCreator::SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot) { - if (ValidateIsMeshReady()) + auto iter = AZStd::find_if(m_asset->m_materialSlots.begin(), m_asset->m_materialSlots.end(), [&materialSlot](const ModelMaterialSlot& existingMaterialSlot) + { + return existingMaterialSlot.m_stableId == materialSlot.m_stableId; + }); + + if (iter == m_asset->m_materialSlots.end()) { - m_currentMesh.m_materialAsset = materialAsset; + m_currentMesh.m_materialSlotIndex = m_asset->m_materialSlots.size(); + m_asset->m_materialSlots.push_back(materialSlot); + } + else + { + if (materialSlot.m_displayName != iter->m_displayName) + { + ReportWarning("Material slot %u was already added with a different name.", materialSlot.m_stableId); + } + + if (materialSlot.m_defaultMaterialAsset != iter->m_defaultMaterialAsset) + { + ReportWarning("Material slot %u was already added with a different MaterialAsset.", materialSlot.m_stableId); + } + + *iter = materialSlot; } } @@ -288,7 +308,9 @@ namespace AZ creator.SetMeshName(sourceMesh.GetName()); AZ::Aabb aabb = sourceMesh.GetAabb(); creator.SetMeshAabb(AZStd::move(aabb)); - creator.SetMeshMaterialAsset(sourceMesh.GetMaterialAsset()); + + const ModelMaterialSlot& materialSlot = sourceAsset->GetMaterialSlot(sourceMesh.GetMaterialSlotIndex()); + creator.SetMeshMaterialSlot(materialSlot); // Mesh index buffer view const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp new file mode 100644 index 0000000000..61f3ebbe3a --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + void ModelMaterialSlot::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("StableId", &ModelMaterialSlot::m_stableId) + ->Field("DisplayName", &ModelMaterialSlot::m_displayName) + ->Field("DefaultMaterialAsset", &ModelMaterialSlot::m_defaultMaterialAsset) + ; + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index c049837045..49c7231fed 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -22,6 +22,7 @@ set(FILES Include/Atom/RPI.Reflect/Model/ModelKdTree.h Include/Atom/RPI.Reflect/Model/ModelLodAsset.h Include/Atom/RPI.Reflect/Model/ModelLodIndex.h + Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h Include/Atom/RPI.Reflect/Model/ModelAssetCreator.h Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h Include/Atom/RPI.Reflect/Model/MorphTargetDelta.h @@ -106,6 +107,7 @@ set(FILES Source/RPI.Reflect/Model/ModelLodAsset.cpp Source/RPI.Reflect/Model/ModelAssetCreator.cpp Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp + Source/RPI.Reflect/Model/ModelMaterialSlot.cpp Source/RPI.Reflect/Model/MorphTargetDelta.cpp Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp Source/RPI.Reflect/Model/MorphTargetMetaAssetCreator.cpp diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 134bf6db47..4072684987 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -69,6 +69,9 @@ namespace AZ : public ComponentBus { public: + //! Returns the list of all ModelMaterialSlot's for the model, across all LODs. + virtual RPI::ModelMaterialSlotMap GetModelMaterialSlots() const = 0; + virtual MaterialAssignmentMap GetMaterialAssignments() const = 0; virtual AZStd::unordered_set GetModelUvNames() const = 0; }; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index d7dc4335b3..f1767a8b1d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -44,57 +44,8 @@ namespace AZ if (classElement.GetVersion() < 3) { - // The default material was changed from an asset to an EditorMaterialComponentSlot and old data must be converted - constexpr AZ::u32 defaultMaterialAssetDataCrc = AZ_CRC("defaultMaterialAsset", 0x736fc071); - - Data::Asset oldDefaultMaterialData; - if (!classElement.GetChildData(defaultMaterialAssetDataCrc, oldDefaultMaterialData)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get defaultMaterialAsset element"); - return false; - } - - if (!classElement.RemoveElementByName(defaultMaterialAssetDataCrc)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove defaultMaterialAsset element"); - return false; - } - - EditorMaterialComponentSlot newDefaultMaterialData; - newDefaultMaterialData.m_id = DefaultMaterialAssignmentId; - newDefaultMaterialData.m_materialAsset = oldDefaultMaterialData; - classElement.AddElementWithData(context, "defaultMaterialSlot", newDefaultMaterialData); - - // Slots now support and display the default material asset when empty - // The old placeholder assignments are irrelevant and must be cleared - constexpr AZ::u32 materialSlotsByLodDataCrc = AZ_CRC("materialSlotsByLod", 0xb1498db6); - - EditorMaterialComponentSlotsByLodContainer lodSlotData; - if (!classElement.GetChildData(materialSlotsByLodDataCrc, lodSlotData)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get materialSlotsByLod element"); - return false; - } - - if (!classElement.RemoveElementByName(materialSlotsByLodDataCrc)) - { - AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove materialSlotsByLod element"); - return false; - } - - // Find and clear all slots that are assigned to the slot's default value - for (auto& lodSlots : lodSlotData) - { - for (auto& slot : lodSlots) - { - if (slot.m_materialAsset.GetId() == slot.m_id.m_materialAssetId) - { - slot.m_materialAsset = {}; - } - } - } - - classElement.AddElementWithData(context, "materialSlotsByLod", lodSlotData); + AZ_Error("EditorMaterialComponent", false, "Material Component version < 3 is no longer supported"); + return false; } if (classElement.GetVersion() < 4) @@ -238,7 +189,7 @@ namespace AZ for (auto& materialSlotPair : GetMaterialSlots()) { EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsAssetOnly()) + if (materialSlot->m_id.IsSlotIdOnly()) { materialSlot->Clear(); } @@ -251,7 +202,7 @@ namespace AZ for (auto& materialSlotPair : GetMaterialSlots()) { EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.IsLodAndAsset()) + if (materialSlot->m_id.IsLodAndSlotId()) { materialSlot->Clear(); } @@ -318,6 +269,9 @@ namespace AZ // Build the controller configuration from the editor configuration MaterialComponentConfig config = m_controller.GetConfiguration(); config.m_materials.clear(); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); for (const auto& materialSlotPair : GetMaterialSlots()) { @@ -340,10 +294,15 @@ namespace AZ } else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty()) { - MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; - materialAssignment.m_materialAsset.Create(materialSlot->m_id.m_materialAssetId); - materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; - materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; + auto materialSlotIter = modelMaterialSlots.find(materialSlot->m_id.m_materialSlotStableId); + + if (materialSlotIter != modelMaterialSlots.end()) + { + MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; + materialAssignment.m_materialAsset = materialSlotIter->second.m_defaultMaterialAsset; + materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; + materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; + } } } @@ -362,6 +321,9 @@ namespace AZ // Get the known material assignment slots from the associated model or other source MaterialAssignmentMap materialsFromSource; MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); // Generate the table of editable materials using the source data to define number of groups, elements, and initial values for (const auto& materialPair : materialsFromSource) @@ -385,6 +347,29 @@ namespace AZ OnConfigurationChanged(); }; + const char* UnknownSlotName = ""; + + // If this is the default material assignment ID then it represents the default slot which is not contained in any other group + if (slot.m_id == DefaultMaterialAssignmentId) + { + slot.m_label = "Default Material"; + } + else + { + auto slotIter = modelMaterialSlots.find(slot.m_id.m_materialSlotStableId); + if (slotIter != modelMaterialSlots.end()) + { + const Name& displayName = slotIter->second.m_displayName; + slot.m_label = !displayName.IsEmpty() ? displayName.GetStringView() : UnknownSlotName; + + slot.m_defaultMaterialAsset = slotIter->second.m_defaultMaterialAsset; + } + else + { + slot.m_label = UnknownSlotName; + } + } + // if material is present in controller configuration, assign its data const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); slot.m_materialAsset = materialFromController.m_materialAsset; @@ -400,13 +385,13 @@ namespace AZ continue; } - if (slot.m_id.IsAssetOnly()) + if (slot.m_id.IsSlotIdOnly()) { m_materialSlots.push_back(slot); continue; } - if (slot.m_id.IsLodAndAsset()) + if (slot.m_id.IsLodAndSlotId()) { // Resize the containers to fit all elements m_materialSlotsByLod.resize(AZ::GetMax(m_materialSlotsByLod.size(), aznumeric_cast(slot.m_id.m_lodIndex + 1))); @@ -452,17 +437,19 @@ namespace AZ { AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); + + RPI::ModelMaterialSlotMap modelMaterialSlots; + MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); // First generating a unique set of all material asset IDs that will be used for source data generation AZStd::unordered_set assetIds; - auto materialSlots = GetMaterialSlots(); - for (auto& materialSlotPair : materialSlots) + for (auto& materialSlot : modelMaterialSlots) { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot->m_id.m_materialAssetId.IsValid()) + Data::AssetId defaultMaterialAssetId = materialSlot.second.m_defaultMaterialAsset.GetId(); + if (defaultMaterialAssetId.IsValid()) { - assetIds.insert(materialSlot->m_id.m_materialAssetId); + assetIds.insert(defaultMaterialAssetId); } } @@ -472,7 +459,7 @@ namespace AZ for (const AZ::Data::AssetId& assetId : assetIds) { EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_assetId = assetId; + exportItem.m_originalAssetId = assetId; exportItems.push_back(exportItem); } @@ -489,12 +476,23 @@ namespace AZ const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0); if (assetIdOutcome) { - for (auto& materialSlotPair : materialSlots) + for (auto& materialSlotPair : GetMaterialSlots()) { - EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; - if (materialSlot && materialSlot->m_id.m_materialAssetId == exportItem.m_assetId) + EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second; + + if (editorMaterialSlot) { - materialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); + // Only update the slot of it was originally empty, having no override material. + // We need to check whether replaced material corresponds to this slot's default material. + if (!editorMaterialSlot->m_materialAsset.GetId().IsValid()) + { + auto materialSlot = modelMaterialSlots.find(editorMaterialSlot->m_id.m_materialSlotStableId); + if (materialSlot != modelMaterialSlots.end() && + materialSlot->second.m_defaultMaterialAsset.GetId() == exportItem.m_originalAssetId) + { + editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); + } + } } } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index bde500bdc5..1e6b19f616 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp @@ -132,7 +132,7 @@ namespace AZ int row = 0; for (ExportItem& exportItem : exportItems) { - QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_assetId).c_str()); + QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_originalAssetId).c_str()); // Configuring initial settings based on whether or not the target file already exists exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); @@ -147,7 +147,7 @@ namespace AZ // Create a check box for toggling the enabled state of this item QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget); materialSlotCheckBox->setChecked(exportItem.m_enabled); - materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_assetId).c_str()); + materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_originalAssetId).c_str()); tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox); // Create a file picker widget for selecting the save path for the exported material @@ -256,7 +256,7 @@ namespace AZ } EditorMaterialComponentUtil::MaterialEditData editData; - if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData)) + if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_originalAssetId, editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data."); return false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h index 00bef9ee66..289bde0c75 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h @@ -19,10 +19,10 @@ namespace AZ { namespace EditorMaterialComponentExporter { - // Attemts to generate a display label for a material slot by parsing its file name + //! Attemts to generate a display label for a material slot by parsing its file name AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId); - // Generates a destination file path for exporting material source data + //! Generates a destination file path for exporting material source data AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId); struct ExportItem @@ -30,16 +30,17 @@ namespace AZ bool m_enabled = true; bool m_exists = false; bool m_overwrite = false; - AZ::Data::AssetId m_assetId; + AZ::Data::AssetId m_originalAssetId; //!< AssetId of the original built-in material, which will be exported. AZStd::string m_exportPath; }; using ExportItemsContainer = AZStd::vector; - // Generates and opens a dialog for configuring material data export paths and actions + //! Generates and opens a dialog for configuring material data export paths and actions. + //! Note this will not modify the m_originalAssetId field in each ExportItem. bool OpenExportDialog(ExportItemsContainer& exportItems); - // Attemts to construct and save material source data from a product asset + //! Attemts to construct and save material source data from a product asset bool ExportMaterialSourceData(const ExportItem& exportItem); } // namespace EditorMaterialComponentExporter } // namespace Render diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index d341bbb290..c8a2024b39 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -20,7 +20,7 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include -#include +#include #include AZ_POP_DISABLE_WARNING @@ -48,7 +48,7 @@ namespace AZ return false; } - const MaterialAssignmentId newId(oldId.first, oldId.second); + const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId); classElement.AddElementWithData(context, "id", newId); } @@ -83,6 +83,7 @@ namespace AZ ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) + ->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -121,21 +122,12 @@ namespace AZ AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const { - return m_id.m_materialAssetId; + return m_defaultMaterialAsset.GetId(); } AZStd::string EditorMaterialComponentSlot::GetLabel() const { - // Generate the label for the material slot based on the assignment ID - // If this is the default material assignment ID then it represents the default slot which is not contained in any other group - if (m_id == DefaultMaterialAssignmentId) - { - return "Default Material"; - } - - // Otherwise the label can be generated by parsing the source file name associated with the asset ID - const AZStd::string& label = EditorMaterialComponentExporter::GetLabelByAssetId(m_id.m_materialAssetId); - return !label.empty() ? label : ""; + return m_label; } bool EditorMaterialComponentSlot::HasSourceData() const @@ -183,27 +175,22 @@ namespace AZ OnMaterialChanged(); } - void EditorMaterialComponentSlot::SetDefaultAsset() + void EditorMaterialComponentSlot::ResetToDefaultAsset() { - m_materialAsset = {}; + m_materialAsset = m_defaultMaterialAsset; m_propertyOverrides = {}; m_matModUvOverrides = {}; - if (m_id.m_materialAssetId.IsValid()) - { - // If no material is assigned to this slot, assign the default material from the slot id to edit its properties - m_materialAsset.Create(m_id.m_materialAssetId); - } OnMaterialChanged(); } void EditorMaterialComponentSlot::OpenMaterialExporter() { // Because we are generating a source material from this specific slot there is only one entry - // But we still need to allow the user to reconfigure it using the dialogue + // But we still need to allow the user to reconfigure it using the dialog EditorMaterialComponentExporter::ExportItemsContainer exportItems; { EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_assetId = m_id.m_materialAssetId; + exportItem.m_originalAssetId = m_defaultMaterialAsset.GetId(); exportItems.push_back(exportItem); } @@ -275,7 +262,7 @@ namespace AZ QAction* action = nullptr; action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); }); - action->setEnabled(m_id.m_materialAssetId.IsValid()); + action->setEnabled(m_defaultMaterialAsset.GetId().IsValid()); menu.addSeparator(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h index e8b4f46854..79fddf2611 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.h @@ -34,7 +34,7 @@ namespace AZ AZStd::string GetLabel() const; bool HasSourceData() const; void OpenMaterialEditor() const; - void SetDefaultAsset(); + void ResetToDefaultAsset(); void Clear(); void ClearOverrides(); void OpenMaterialExporter(); @@ -42,7 +42,9 @@ namespace AZ void OpenUvNameMapInspector(); MaterialAssignmentId m_id; + AZStd::string m_label; Data::Asset m_materialAsset; + Data::Asset m_defaultMaterialAsset; MaterialPropertyOverrideMap m_propertyOverrides; AZStd::function m_materialChangedCallback; AZStd::function m_propertyChangedCallback; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp index a0adcd3187..f7b7177e29 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentConfig.cpp @@ -44,7 +44,7 @@ namespace AZ for (const auto& oldPair : oldMaterials) { const DeprecatedMaterialAssignmentId& oldId = oldPair.first; - const MaterialAssignmentId newId(oldId.first, oldId.second); + const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId); newMaterials[newId] = oldPair.second; } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index 29bd9a839b..a2a3022e91 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -251,6 +251,19 @@ namespace AZ m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transformInterface->GetWorldTM(), m_cachedNonUniformScale); } } + + RPI::ModelMaterialSlotMap MeshComponentController::GetModelMaterialSlots() const + { + Data::Asset modelAsset = GetModelAsset(); + if (modelAsset.IsReady()) + { + return modelAsset->GetModelMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index 80b483452f..78c2e0797f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -111,6 +111,7 @@ namespace AZ void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; // MaterialReceiverRequestBus::Handler overrides ... + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp index 3143191135..ae617f910f 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -96,12 +96,10 @@ namespace AZ skinnedSubMesh.m_vertexCount = aznumeric_cast(subMeshVertexCount); lodVertexCount += aznumeric_cast(subMeshVertexCount); - // The default material id used by a sub-mesh is the guid of the source scene file plus the subId which is a unique material ID from the scene API - AZ::u32 subId = modelMesh.GetMaterialAsset().GetId().m_subId; - AZ::Data::AssetId materialId{ actorAssetId.m_guid, subId }; - + skinnedSubMesh.m_material = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()).m_defaultMaterialAsset; // Queue the material asset - the ModelLod seems to handle delayed material loads - skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid(), skinnedSubMesh.m_material.GetAutoLoadBehavior()); + skinnedSubMesh.m_material.QueueLoad(); + subMeshes.push_back(skinnedSubMesh); } else diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index 4c6045d7ce..6697162c18 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -307,6 +307,19 @@ namespace AZ m_meshFeatureProcessor = nullptr; m_skinnedMeshFeatureProcessor = nullptr; } + + RPI::ModelMaterialSlotMap AtomActorInstance::GetModelMaterialSlots() const + { + Data::Asset modelAsset = GetModelAsset(); + if (modelAsset.IsReady()) + { + return modelAsset->GetModelMaterialSlots(); + } + else + { + return {}; + } + } MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const { diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index c854fed3c1..e7a4047012 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -120,6 +120,7 @@ namespace AZ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MaterialReceiverRequestBus::Handler overrides... + RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp index 3f089a8639..5a9b8191ed 100644 --- a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp +++ b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp @@ -116,7 +116,10 @@ namespace WhiteBox // set the default material if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath(TexturedMaterialPath.data())) { - modelLodCreator.SetMeshMaterialAsset(materialAsset); + AZ::RPI::ModelMaterialSlot materialSlot; + materialSlot.m_stableId = 0; + materialSlot.m_defaultMaterialAsset = materialAsset; + modelLodCreator.SetMeshMaterialSlot(materialSlot); } else { From e3ceaa477e338d553920f8363fed99384dac0335 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Sat, 17 Jul 2021 12:54:40 -0700 Subject: [PATCH 03/22] Added a version converter for MaterialAssignmentId. This allowed me to successfully load the Sponza level in AtomTest. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Feature/Material/MaterialAssignmentId.h | 1 + .../Source/Material/MaterialAssignmentId.cpp | 27 ++++++++++++++++++- .../Material/EditorMaterialComponentSlot.cpp | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) 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 d9ae8099da..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 @@ -31,6 +31,7 @@ namespace AZ 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; diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp index 8b6b0be237..5db6c4c15c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp @@ -14,12 +14,37 @@ 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. + } + + classElement.AddElementWithData(context, "materialSlotStableId", materialAssetId.m_subId); + } + + return true; + } + void MaterialAssignmentId::Reflect(ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(2) + ->Version(2, &MaterialAssignmentId::ConvertVersion) ->Field("lodIndex", &MaterialAssignmentId::m_lodIndex) ->Field("materialSlotStableId", &MaterialAssignmentId::m_materialSlotStableId) ; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index c8a2024b39..0a32f9b125 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -80,7 +80,7 @@ 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) From 670dd6c5bc2031881f25737488075d6616cf3544 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Sat, 17 Jul 2021 18:18:28 -0700 Subject: [PATCH 04/22] Removed the GetLabelByAssetId function since now we can use the display name that comes with the ModelMaterialSlot. Updated OpenMaterialExporter() to account for the fact that multiple material slots can have the same default material asset. Updated the material inspector to sort material slots by name to match the order in the Material Component. Updated ExportItem to protect its data members, which makes it more clear that assetId and materialSlotName are readonly inputs. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponent.cpp | 53 +++++++---- .../EditorMaterialComponentExporter.cpp | 90 ++++++------------- .../EditorMaterialComponentExporter.h | 32 +++++-- .../Material/EditorMaterialComponentSlot.cpp | 5 +- 4 files changed, 88 insertions(+), 92 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index f1767a8b1d..16a657a64f 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 @@ -438,30 +439,46 @@ namespace AZ AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); + Data::AssetId modelAssetId; + MeshComponentRequestBus::EventResult(modelAssetId, GetEntityId(), &MeshComponentRequestBus::Events::GetModelAssetId); + RPI::ModelMaterialSlotMap modelMaterialSlots; MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); + + EditorMaterialComponentExporter::ExportItemsContainer exportItems; - // First generating a unique set of all material asset IDs that will be used for source data generation - AZStd::unordered_set assetIds; - - for (auto& materialSlot : modelMaterialSlots) + // Generate a list of export items for the set of unique default material assets from the model. + for (auto& materialSlotPair : modelMaterialSlots) { - Data::AssetId defaultMaterialAssetId = materialSlot.second.m_defaultMaterialAsset.GetId(); - if (defaultMaterialAssetId.IsValid()) + // We only care about material assets that were generated from the model source file, since those are the + // ones that would need conversion (other materials already have their own source file). This can be detected + // by matching GUID component of the AssetId. + Data::AssetId defaultMaterialAssetId = materialSlotPair.second.m_defaultMaterialAsset.GetId(); + bool materialWasGeneratedFromModel = defaultMaterialAssetId.IsValid() && defaultMaterialAssetId.m_guid == modelAssetId.m_guid; + if (materialWasGeneratedFromModel) { - assetIds.insert(defaultMaterialAssetId); + auto duplicateAssetIter = AZStd::find_if(exportItems.begin(), exportItems.end(), + [defaultMaterialAssetId](const EditorMaterialComponentExporter::ExportItem& existingExportItem) + { + return existingExportItem.GetOriginalAssetId() == defaultMaterialAssetId; + }); + + // It's possible for multiple material slots to have the same default material asset. So we just use the first one, which just means the + // exported material file name will be based on the first relevant material slot's name. + if (duplicateAssetIter == exportItems.end()) + { + EditorMaterialComponentExporter::ExportItem exportItem{defaultMaterialAssetId, materialSlotPair.second.m_displayName.GetStringView()}; + exportItems.push_back(exportItem); + } } } - // 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) - { - EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_originalAssetId = assetId; - exportItems.push_back(exportItem); - } + // Sort by display name so the list order will match what's displayed in the Material Component. + AZStd::sort(exportItems.begin(), exportItems.end(), + [](const EditorMaterialComponentExporter::ExportItem& a, const EditorMaterialComponentExporter::ExportItem& b) + { + return a.GetMaterialSlotName() < b.GetMaterialSlotName(); + }); // Display the export dialog so that the user can configure how they want different materials to be exported if (EditorMaterialComponentExporter::OpenExportDialog(exportItems)) @@ -473,7 +490,7 @@ 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 : GetMaterialSlots()) @@ -488,7 +505,7 @@ namespace AZ { auto materialSlot = modelMaterialSlots.find(editorMaterialSlot->m_id.m_materialSlotStableId); if (materialSlot != modelMaterialSlots.end() && - materialSlot->second.m_defaultMaterialAsset.GetId() == exportItem.m_originalAssetId) + materialSlot->second.m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) { editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index 1e6b19f616..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_originalAssetId).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_originalAssetId).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_originalAssetId, 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 289bde0c75..4830db33c4 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.h @@ -19,19 +19,39 @@ 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); + AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName); - 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_originalAssetId; //!< AssetId of the original built-in material, which will be exported. 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; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 0a32f9b125..717b87feec 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -189,8 +189,7 @@ namespace AZ // But we still need to allow the user to reconfigure it using the dialog EditorMaterialComponentExporter::ExportItemsContainer exportItems; { - EditorMaterialComponentExporter::ExportItem exportItem; - exportItem.m_originalAssetId = m_defaultMaterialAsset.GetId(); + EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label}; exportItems.push_back(exportItem); } @@ -205,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()); From e145ce1d01334ddea612077150138f893ea41dd7 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Sat, 17 Jul 2021 18:46:04 -0700 Subject: [PATCH 05/22] Updated EditorMaterialComponentSlot to support editing property overrides and UV overrides for the material, regardless of whether there is a material override or not. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponentSlot.cpp | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 717b87feec..f9243510cf 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -227,9 +227,11 @@ namespace AZ OnPropertyChanged(); }; - if (m_materialAsset.GetId().IsValid()) + Data::Asset assetToEdit = m_materialAsset.GetId().IsValid() ? m_materialAsset : m_defaultMaterialAsset; + + if (assetToEdit.GetId().IsValid()) { - if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback)) + if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), assetToEdit.GetId(), m_propertyOverrides, applyPropertyChangedCallback)) { OnMaterialChanged(); } @@ -244,10 +246,12 @@ namespace AZ // Treated as a special property. It will be updated together with properties. OnPropertyChanged(); }; - - if (m_materialAsset.GetId().IsValid()) + + Data::Asset assetToEdit = m_materialAsset.GetId().IsValid() ? m_materialAsset : m_defaultMaterialAsset; + + if (assetToEdit.GetId().IsValid()) { - if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) + if (EditorMaterialComponentInspector::OpenInspectorDialog(assetToEdit.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) { OnMaterialChanged(); } @@ -268,11 +272,13 @@ namespace AZ action = menu.addAction("Edit Source Material...", [this]() { OpenMaterialEditor(); }); action->setEnabled(HasSourceData()); + bool hasAnyMaterial = m_defaultMaterialAsset.GetId().IsValid() || m_materialAsset.GetId().IsValid(); + action = menu.addAction("Edit Material Instance...", [this]() { OpenMaterialInspector(); }); - action->setEnabled(m_materialAsset.GetId().IsValid()); + action->setEnabled(hasAnyMaterial); action = menu.addAction("Edit Material Instance UV Map...", [this]() { OpenUvNameMapInspector(); }); - action->setEnabled(m_materialAsset.GetId().IsValid()); + action->setEnabled(hasAnyMaterial); menu.addSeparator(); From 28671c8546179ffe0686f658fc6ef8095a5e4a76 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Mon, 19 Jul 2021 23:40:18 -0700 Subject: [PATCH 06/22] Addressed suggestions from gadams3 to make EditorMaterialComponent get the default material assets from its own data rather than fetching them from the asset. Presumably this should give more reliable behavior. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponent.cpp | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index 16a657a64f..866b2dec56 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -271,9 +271,6 @@ namespace AZ MaterialComponentConfig config = m_controller.GetConfiguration(); config.m_materials.clear(); - RPI::ModelMaterialSlotMap modelMaterialSlots; - MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); - for (const auto& materialSlotPair : GetMaterialSlots()) { const EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; @@ -295,15 +292,10 @@ namespace AZ } else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty()) { - auto materialSlotIter = modelMaterialSlots.find(materialSlot->m_id.m_materialSlotStableId); - - if (materialSlotIter != modelMaterialSlots.end()) - { - MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; - materialAssignment.m_materialAsset = materialSlotIter->second.m_defaultMaterialAsset; - materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; - materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; - } + MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; + materialAssignment.m_materialAsset = materialSlot->m_defaultMaterialAsset; + materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; + materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; } } @@ -442,18 +434,15 @@ namespace AZ Data::AssetId modelAssetId; MeshComponentRequestBus::EventResult(modelAssetId, GetEntityId(), &MeshComponentRequestBus::Events::GetModelAssetId); - RPI::ModelMaterialSlotMap modelMaterialSlots; - MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots); - EditorMaterialComponentExporter::ExportItemsContainer exportItems; // Generate a list of export items for the set of unique default material assets from the model. - for (auto& materialSlotPair : modelMaterialSlots) + for (auto& materialSlotPair : GetMaterialSlots()) { // We only care about material assets that were generated from the model source file, since those are the // ones that would need conversion (other materials already have their own source file). This can be detected // by matching GUID component of the AssetId. - Data::AssetId defaultMaterialAssetId = materialSlotPair.second.m_defaultMaterialAsset.GetId(); + Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId(); bool materialWasGeneratedFromModel = defaultMaterialAssetId.IsValid() && defaultMaterialAssetId.m_guid == modelAssetId.m_guid; if (materialWasGeneratedFromModel) { @@ -467,7 +456,7 @@ namespace AZ // exported material file name will be based on the first relevant material slot's name. if (duplicateAssetIter == exportItems.end()) { - EditorMaterialComponentExporter::ExportItem exportItem{defaultMaterialAssetId, materialSlotPair.second.m_displayName.GetStringView()}; + EditorMaterialComponentExporter::ExportItem exportItem{defaultMaterialAssetId, materialSlotPair.second->GetLabel()}; exportItems.push_back(exportItem); } } @@ -499,16 +488,10 @@ namespace AZ if (editorMaterialSlot) { - // Only update the slot of it was originally empty, having no override material. - // We need to check whether replaced material corresponds to this slot's default material. - if (!editorMaterialSlot->m_materialAsset.GetId().IsValid()) + if (!editorMaterialSlot->m_materialAsset.GetId().IsValid() && //< Only update the slot of it was originally empty, having no override material. + editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) //< We need to check whether replaced material corresponds to this slot's default material. { - auto materialSlot = modelMaterialSlots.find(editorMaterialSlot->m_id.m_materialSlotStableId); - if (materialSlot != modelMaterialSlots.end() && - materialSlot->second.m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) - { - editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); - } + editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); } } } From 3daf3f7d7ae71d5d98ba6ce0fe7cc4be28e542e8 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 20 Jul 2021 12:16:12 -0700 Subject: [PATCH 07/22] Fixed an issue with Actors where the material slot IDs were incorrect, and caused the displayed slot labels to be all "" (and likely other issues). Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Feature/SkinnedMesh/SkinnedMeshInputBuffers.h | 2 +- .../Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp | 7 +------ .../EMotionFXAtom/Code/Source/ActorAsset.cpp | 5 +++-- .../EMotionFXAtom/Code/Source/AtomActorInstance.cpp | 12 +++++++----- 4 files changed, 12 insertions(+), 14 deletions(-) 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/SkinnedMesh/SkinnedMeshInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp index 640e30d0f6..afd215b4bc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp @@ -640,12 +640,7 @@ namespace AZ Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); - // Create a separate material slot for each sub-mesh - AZ::RPI::ModelMaterialSlot materialSlot; - materialSlot.m_stableId = i; - materialSlot.m_defaultMaterialAsset = lod.m_subMeshProperties[i].m_material; - - modelLodCreator.SetMeshMaterialSlot(materialSlot); + modelLodCreator.SetMeshMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot); modelLodCreator.EndMesh(); } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp index ae617f910f..415f0d1ce3 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -96,9 +96,10 @@ namespace AZ skinnedSubMesh.m_vertexCount = aznumeric_cast(subMeshVertexCount); lodVertexCount += aznumeric_cast(subMeshVertexCount); - skinnedSubMesh.m_material = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()).m_defaultMaterialAsset; + skinnedSubMesh.m_materialSlot = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()); + // Queue the material asset - the ModelLod seems to handle delayed material loads - skinnedSubMesh.m_material.QueueLoad(); + skinnedSubMesh.m_materialSlot.m_defaultMaterialAsset.QueueLoad(); subMeshes.push_back(skinnedSubMesh); } diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index 6697162c18..f77e8d6bbd 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -503,16 +503,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()); } } } From a71ee7eb3a3c2e3b2f4d9defa80505eb6196ed4c Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 20 Jul 2021 13:43:24 -0700 Subject: [PATCH 08/22] Fixed the MaterialAssignmentId version converter to properly handle the default material assignment slot. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Common/Code/Source/Material/MaterialAssignmentId.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp index 5db6c4c15c..b8a03af6d1 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentId.cpp @@ -33,7 +33,14 @@ namespace AZ // No need to early-return, the object will still load successfully, it will just report more errors about the unrecognized element. } - classElement.AddElementWithData(context, "materialSlotStableId", materialAssetId.m_subId); + if (materialAssetId.IsValid()) + { + classElement.AddElementWithData(context, "materialSlotStableId", materialAssetId.m_subId); + } + else + { + classElement.AddElementWithData(context, "materialSlotStableId", RPI::ModelMaterialSlot::InvalidStableId); + } } return true; From fec79a7d53a0753dbd3d68a403ee9ea98a4ab172 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 20 Jul 2021 16:23:56 -0700 Subject: [PATCH 09/22] Moved the material slot list from ModelLodAsset to ModelAsset, so all the slots live in one main list. This removes data duplication between LODs and cleans up the code a bit. I had to update the ModelLod class to take in both the ModelLodAsset and ModelAsset for initialization so it can fetch the slots for each mesh. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../SkinnedMesh/SkinnedMeshInputBuffers.cpp | 5 ++- .../Include/Atom/RPI.Public/Model/Model.h | 4 +- .../Include/Atom/RPI.Public/Model/ModelLod.h | 7 +-- .../Atom/RPI.Reflect/Model/ModelAsset.h | 12 ++++- .../RPI.Reflect/Model/ModelAssetCreator.h | 4 ++ .../Atom/RPI.Reflect/Model/ModelLodAsset.h | 29 +++--------- .../RPI.Reflect/Model/ModelLodAssetCreator.h | 5 +-- .../Model/MaterialAssetBuilderComponent.cpp | 2 +- .../Model/ModelAssetBuilderComponent.cpp | 13 +++--- .../Model/ModelAssetBuilderComponent.h | 1 + .../Code/Source/RPI.Public/Model/Model.cpp | 12 ++--- .../Code/Source/RPI.Public/Model/ModelLod.cpp | 20 ++++++--- .../Source/RPI.Public/Model/ModelSystem.cpp | 6 +-- .../Source/RPI.Reflect/Model/ModelAsset.cpp | 34 +++++++------- .../RPI.Reflect/Model/ModelAssetCreator.cpp | 27 ++++++++++++ .../RPI.Reflect/Model/ModelLodAsset.cpp | 44 ++----------------- .../Model/ModelLodAssetCreator.cpp | 29 +++--------- .../Source/Mesh/MeshComponentController.cpp | 2 +- .../EMotionFXAtom/Code/Source/ActorAsset.cpp | 2 +- .../Code/Source/AtomActorInstance.cpp | 2 +- .../Rendering/Atom/WhiteBoxAtomRenderMesh.cpp | 30 +++++++------ .../Rendering/Atom/WhiteBoxAtomRenderMesh.h | 1 + 22 files changed, 134 insertions(+), 157 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp index afd215b4bc..de2c52d1c8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshInputBuffers.cpp @@ -639,8 +639,9 @@ namespace AZ Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); - - modelLodCreator.SetMeshMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot); + + 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/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 36892d0027..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 @@ -84,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; @@ -124,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 891aec04b3..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 @@ -51,7 +51,10 @@ namespace AZ const AZ::Aabb& GetAabb() const; //! Returns the list of all ModelMaterialSlot's for the model, across all LODs. - RPI::ModelMaterialSlotMap GetModelMaterialSlots() const; + 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; @@ -100,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 61e2ebeb05..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 @@ -85,9 +85,9 @@ namespace AZ //! Returns the number of indices in this mesh uint32_t GetIndexCount() const; - //! Returns the index of the material slot used by this mesh. - //! This indexes into the ModelLodAsset's material slot list. - size_t GetMaterialSlotIndex() const; + //! Returns the 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; @@ -126,9 +126,9 @@ namespace AZ AZ::Name m_name; AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); - // Identifies the material that is used by this mesh. - // References material slot in the ModelLodAsset that owns this mesh; see ModelLodAsset::GetMaterialSlot(). - size_t m_materialSlotIndex = 0; + // 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,16 +147,6 @@ namespace AZ //! Returns the model-space axis-aligned bounding box of all meshes in the lod const AZ::Aabb& GetAabb() const; - - //! Returns an array view into the collection of material slots available to this lod - AZStd::array_view GetMaterialSlots() const; - - //! Returns a specific material slot by index, with error checking. - //! The index can be retrieved from Mesh::GetMaterialSlotIndex(). - const ModelMaterialSlot& GetMaterialSlot(size_t slotIndex) const; - - //! Find a material slot with the given stableId, or returns null if it isn't found. - const ModelMaterialSlot* FindMaterialSlot(uint32_t stableId) const; private: AZStd::vector m_meshes; @@ -169,13 +159,6 @@ namespace AZ Data::Asset m_indexBuffer; AZStd::vector> m_streamBuffers; - // Lists all of the material slots that are used by this LOD. - // Note the same slot can appear in multiple LODs in the model, so that LODs don't have to refer back to the model asset. - AZStd::vector m_materialSlots; - - // A default ModelMaterialSlot to be returned upon error conditions. - ModelMaterialSlot m_fallbackSlot; - void AddMesh(const Mesh& mesh); void SetReady(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h index 5672b49f68..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 @@ -46,10 +46,9 @@ namespace AZ //! Begin and BeginMesh must be called first. void SetMeshAabb(AZ::Aabb&& aabb); - //! Sets the material slot data for the current SubMesh. - //! Adds a new material slot to the ModelLodAsset if it doesn't already exist. + //! Sets the ID of the model's material slot that this mesh uses. //! Begin and BeginMesh must be called first - void SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot); + 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/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 608ee17d95..7100b2cd48 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp @@ -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) { @@ -1811,7 +1811,8 @@ namespace AZ materialSlot.m_displayName = iter->second.m_name; materialSlot.m_defaultMaterialAsset = iter->second.m_asset; - lodAssetCreator.SetMeshMaterialSlot(materialSlot); + 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..832a8700ba 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.h @@ -294,6 +294,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 2dfee9e2f1..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,7 +106,7 @@ namespace AZ } } - const ModelMaterialSlot& materialSlot = lodAsset.GetMaterialSlot(mesh.GetMaterialSlotIndex()); + const ModelMaterialSlot& materialSlot = modelAsset->FindMaterialSlot(mesh.GetMaterialSlotId()); meshInstance.m_materialSlotStableId = materialSlot.m_stableId; 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 b9781843cf..1ed81f8e16 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/ModelSystem.cpp @@ -41,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); @@ -51,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 486faafbdb..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) ; } @@ -57,28 +58,23 @@ namespace AZ return m_aabb; } - RPI::ModelMaterialSlotMap ModelAsset::GetModelMaterialSlots() const + const ModelMaterialSlotMap& ModelAsset::GetMaterialSlots() const { - RPI::ModelMaterialSlotMap slotMap; + return m_materialSlots; + } - for (const Data::Asset& lod : GetLodAssets()) + const ModelMaterialSlot& ModelAsset::FindMaterialSlot(uint32_t stableId) const + { + auto iter = m_materialSlots.find(stableId); + + if (iter == m_materialSlots.end()) { - for (const AZ::RPI::ModelMaterialSlot& materialSlot : lod->GetMaterialSlots()) - { - auto iter = slotMap.find(materialSlot.m_stableId); - if (iter == slotMap.end()) - { - slotMap.emplace(materialSlot.m_stableId, materialSlot); - } - else - { - AZ_Assert(materialSlot.m_displayName == iter->second.m_displayName && materialSlot.m_defaultMaterialAsset.GetId() == iter->second.m_defaultMaterialAsset.GetId(), - "Multiple LODs have mismatched data for the same material slot."); - } - } + return m_fallbackSlot; + } + else + { + return iter->second; } - - return slotMap; } 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 4811f6a1db..ccf0d49b46 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAsset.cpp @@ -23,10 +23,9 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(0) ->Field("Meshes", &ModelLodAsset::m_meshes) ->Field("Aabb", &ModelLodAsset::m_aabb) - ->Field("MaterialSlots", &ModelLodAsset::m_materialSlots) ; } @@ -41,7 +40,7 @@ namespace AZ ->Version(1) ->Field("Name", &ModelLodAsset::Mesh::m_name) ->Field("AABB", &ModelLodAsset::Mesh::m_aabb) - ->Field("MaterialSlotIndex", &ModelLodAsset::Mesh::m_materialSlotIndex) + ->Field("MaterialSlotId", &ModelLodAsset::Mesh::m_materialSlotId) ->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView) ->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo) ; @@ -76,9 +75,9 @@ namespace AZ return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount; } - size_t ModelLodAsset::Mesh::GetMaterialSlotIndex() const + ModelMaterialSlot::StableId ModelLodAsset::Mesh::GetMaterialSlotId() const { - return m_materialSlotIndex; + return m_materialSlotId; } const AZ::Name& ModelLodAsset::Mesh::GetName() const @@ -120,41 +119,6 @@ namespace AZ return m_aabb; } - AZStd::array_view ModelLodAsset::GetMaterialSlots() const - { - return m_materialSlots; - } - - const ModelMaterialSlot& ModelLodAsset::GetMaterialSlot(size_t slotIndex) const - { - if (slotIndex < m_materialSlots.size()) - { - return m_materialSlots[slotIndex]; - } - else - { - AZ_Error("ModelAsset", false, "Material slot index %zu out of range. ModelAsset has %zu slots.", slotIndex, m_materialSlots.size()); - return m_fallbackSlot; - } - } - - const ModelMaterialSlot* ModelLodAsset::FindMaterialSlot(uint32_t stableId) const - { - auto iter = AZStd::find_if(m_materialSlots.begin(), m_materialSlots.end(), [&stableId](const ModelMaterialSlot& existingMaterialSlot) - { - return existingMaterialSlot.m_stableId == stableId; - }); - - if (iter == m_materialSlots.end()) - { - return nullptr; - } - else - { - return iter; - } - } - const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const { 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 5a066d2517..f94116db70 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp @@ -61,32 +61,14 @@ namespace AZ } } - void ModelLodAssetCreator::SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot) + void ModelLodAssetCreator::SetMeshMaterialSlot(ModelMaterialSlot::StableId id) { - auto iter = AZStd::find_if(m_asset->m_materialSlots.begin(), m_asset->m_materialSlots.end(), [&materialSlot](const ModelMaterialSlot& existingMaterialSlot) - { - return existingMaterialSlot.m_stableId == materialSlot.m_stableId; - }); - - if (iter == m_asset->m_materialSlots.end()) + if (!ValidateIsMeshReady()) { - m_currentMesh.m_materialSlotIndex = m_asset->m_materialSlots.size(); - m_asset->m_materialSlots.push_back(materialSlot); + return; } - else - { - if (materialSlot.m_displayName != iter->m_displayName) - { - ReportWarning("Material slot %u was already added with a different name.", materialSlot.m_stableId); - } - if (materialSlot.m_defaultMaterialAsset != iter->m_defaultMaterialAsset) - { - ReportWarning("Material slot %u was already added with a different MaterialAsset.", materialSlot.m_stableId); - } - - *iter = materialSlot; - } + m_currentMesh.m_materialSlotId = id; } void ModelLodAssetCreator::SetMeshIndexBuffer(const BufferAssetView& bufferAssetView) @@ -309,8 +291,7 @@ namespace AZ AZ::Aabb aabb = sourceMesh.GetAabb(); creator.SetMeshAabb(AZStd::move(aabb)); - const ModelMaterialSlot& materialSlot = sourceAsset->GetMaterialSlot(sourceMesh.GetMaterialSlotIndex()); - creator.SetMeshMaterialSlot(materialSlot); + creator.SetMeshMaterialSlot(sourceMesh.GetMaterialSlotId()); // Mesh index buffer view const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index a2a3022e91..2f4a649c30 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -257,7 +257,7 @@ namespace AZ Data::Asset modelAsset = GetModelAsset(); if (modelAsset.IsReady()) { - return modelAsset->GetModelMaterialSlots(); + return modelAsset->GetMaterialSlots(); } else { diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp index 415f0d1ce3..feabdb9510 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorAsset.cpp @@ -96,7 +96,7 @@ namespace AZ skinnedSubMesh.m_vertexCount = aznumeric_cast(subMeshVertexCount); lodVertexCount += aznumeric_cast(subMeshVertexCount); - skinnedSubMesh.m_materialSlot = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()); + skinnedSubMesh.m_materialSlot = actor->GetMeshAsset()->FindMaterialSlot(modelMesh.GetMaterialSlotId()); // Queue the material asset - the ModelLod seems to handle delayed material loads skinnedSubMesh.m_materialSlot.m_defaultMaterialAsset.QueueLoad(); diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index f77e8d6bbd..062e39b844 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -313,7 +313,7 @@ namespace AZ Data::Asset modelAsset = GetModelAsset(); if (modelAsset.IsReady()) { - return modelAsset->GetModelMaterialSlots(); + return modelAsset->GetMaterialSlots(); } else { diff --git a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp index 5a9b8191ed..daa0ad59f8 100644 --- a/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp +++ b/Gems/WhiteBox/Code/Source/Rendering/Atom/WhiteBoxAtomRenderMesh.cpp @@ -112,20 +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())) - { - AZ::RPI::ModelMaterialSlot materialSlot; - materialSlot.m_stableId = 0; - materialSlot.m_defaultMaterialAsset = materialAsset; - modelLodCreator.SetMeshMaterialSlot(materialSlot); - } - else - { - AZ_Error("CreateLodAsset", false, "Could not load material."); - return false; - } + + modelLodCreator.SetMeshMaterialSlot(OneMaterialSlotId); AddMeshBuffers(modelLodCreator); modelLodCreator.EndMesh(); @@ -157,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"; From 75b4d62dcb2ae68d5900e5d5d9c07d5269e5a246 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 20 Jul 2021 16:59:25 -0700 Subject: [PATCH 10/22] Restored the version converter EditorMaterialComponent::ConvertVersion for version 3, which wasn't possible with an earlier version of my changes. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponent.cpp | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index 866b2dec56..d2f8c1daa0 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -45,8 +45,57 @@ namespace AZ if (classElement.GetVersion() < 3) { - AZ_Error("EditorMaterialComponent", false, "Material Component version < 3 is no longer supported"); - return false; + // 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_defaultMaterialAsset.GetId()) + { + slot.m_materialAsset = {}; + } + } + } + + classElement.AddElementWithData(context, "materialSlotsByLod", lodSlotData); } if (classElement.GetVersion() < 4) From abec7a4f5bcb69ba4450ae538c5f0c11291f1b4e Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 21 Jul 2021 09:16:01 -0700 Subject: [PATCH 11/22] Fixed an issue where a default material should show up as a filled-in value in the UI even though it should appear as empty, indicating the default is being used. Also, I'm going back on what I said in my last commit, and removing the converter for version 3 in EditorMaterialComponent::ConvertVersion. The code that I had put in before wouldn't work because it was relying on the new m_defaultMaterialAsset which will be empty for old data. The only way we could support version conversion is if we preserve legacy versions of multiple types like EditorMaterialComponentSlot and MaterialAssignmentId. Since this serialization version is old and pre-dates the public release of O3DE, it's unlikely that we need to continue supporting this version so isn't worth maintaining. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponent.cpp | 62 +++---------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index d2f8c1daa0..c7bd88219a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -45,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_defaultMaterialAsset.GetId()) - { - 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; } @@ -414,7 +365,11 @@ namespace AZ // 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; + if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field + { + slot.m_materialAsset = materialFromController.m_materialAsset; + } + slot.m_propertyOverrides = materialFromController.m_propertyOverrides; slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides; @@ -629,3 +584,4 @@ namespace AZ } } // namespace Render } // namespace AZ + From 21d5baa1843099e573e7f7085f788e02880ab20c Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 21 Jul 2021 11:28:24 -0700 Subject: [PATCH 12/22] Fixed an issue where I had changed prior functionality by mistake, preventing exported materials from replacing material assignments. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Source/Material/EditorMaterialComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index c7bd88219a..7d0573c027 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -492,8 +492,8 @@ namespace AZ if (editorMaterialSlot) { - if (!editorMaterialSlot->m_materialAsset.GetId().IsValid() && //< Only update the slot of it was originally empty, having no override material. - editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) //< We need to check whether replaced material corresponds to this slot's default material. + // 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()); } From 6fa891848df9a82d5cc46d01d83b8c17771b3557 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 21 Jul 2021 23:52:40 -0700 Subject: [PATCH 13/22] Factored out redundant call to GetMaterialSlots(). Removed code that was intended to handle duplicate default material assignments, but duplicacate default material assignments aren't possible yet. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/EditorMaterialComponent.cpp | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index 7d0573c027..bc91e8adad 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -365,7 +365,7 @@ namespace AZ // if material is present in controller configuration, assign its data const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); - if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field + //if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field { slot.m_materialAsset = materialFromController.m_materialAsset; } @@ -438,40 +438,27 @@ namespace AZ Data::AssetId modelAssetId; MeshComponentRequestBus::EventResult(modelAssetId, GetEntityId(), &MeshComponentRequestBus::Events::GetModelAssetId); - EditorMaterialComponentExporter::ExportItemsContainer exportItems; + // First generating a unique set of all material asset IDs that will be used for source data generation + AZStd::unordered_map assetIdMap; - // Generate a list of export items for the set of unique default material assets from the model. - for (auto& materialSlotPair : GetMaterialSlots()) + auto materialSlots = GetMaterialSlots(); + for (auto& materialSlotPair : materialSlots) { - // We only care about material assets that were generated from the model source file, since those are the - // ones that would need conversion (other materials already have their own source file). This can be detected - // by matching GUID component of the AssetId. Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId(); - bool materialWasGeneratedFromModel = defaultMaterialAssetId.IsValid() && defaultMaterialAssetId.m_guid == modelAssetId.m_guid; - if (materialWasGeneratedFromModel) + if (defaultMaterialAssetId.IsValid()) { - auto duplicateAssetIter = AZStd::find_if(exportItems.begin(), exportItems.end(), - [defaultMaterialAssetId](const EditorMaterialComponentExporter::ExportItem& existingExportItem) - { - return existingExportItem.GetOriginalAssetId() == defaultMaterialAssetId; - }); - - // It's possible for multiple material slots to have the same default material asset. So we just use the first one, which just means the - // exported material file name will be based on the first relevant material slot's name. - if (duplicateAssetIter == exportItems.end()) - { - EditorMaterialComponentExporter::ExportItem exportItem{defaultMaterialAssetId, materialSlotPair.second->GetLabel()}; - exportItems.push_back(exportItem); - } + assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel(); } } - // Sort by display name so the list order will match what's displayed in the Material Component. - AZStd::sort(exportItems.begin(), exportItems.end(), - [](const EditorMaterialComponentExporter::ExportItem& a, const EditorMaterialComponentExporter::ExportItem& b) - { - return a.GetMaterialSlotName() < b.GetMaterialSlotName(); - }); + // 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 (auto assetIdInfo : assetIdMap) + { + EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second}; + exportItems.push_back(exportItem); + } // Display the export dialog so that the user can configure how they want different materials to be exported if (EditorMaterialComponentExporter::OpenExportDialog(exportItems)) @@ -486,7 +473,7 @@ namespace AZ const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); if (assetIdOutcome) { - for (auto& materialSlotPair : GetMaterialSlots()) + for (auto& materialSlotPair : materialSlots) { EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second; From b19a89588948d0ccffe9385e53e8bfcec65da154 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 21 Jul 2021 23:54:37 -0700 Subject: [PATCH 14/22] Reverted accidentally commented out code. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Source/Material/EditorMaterialComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index bc91e8adad..9f1d0d14d2 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -365,7 +365,7 @@ namespace AZ // if material is present in controller configuration, assign its data const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); - //if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field + if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field { slot.m_materialAsset = materialFromController.m_materialAsset; } From 1a478608a7fd74e98ac94070faf5ea288897da28 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Thu, 22 Jul 2021 16:11:35 -0700 Subject: [PATCH 15/22] Restored the previous behavior of preventing material property overrides when there is no explicit material override assignment. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Source/Material/EditorMaterialComponent.cpp | 8 +------- .../Code/Source/Material/EditorMaterialComponentSlot.cpp | 6 ++---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index 9f1d0d14d2..a4e058561e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -365,10 +365,7 @@ namespace AZ // if material is present in controller configuration, assign its data const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); - if (materialFromController.m_materialAsset != slot.m_defaultMaterialAsset) // Prevents the default material from showing up as a filled-in value in the property field - { - slot.m_materialAsset = materialFromController.m_materialAsset; - } + slot.m_materialAsset = materialFromController.m_materialAsset; slot.m_propertyOverrides = materialFromController.m_propertyOverrides; slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides; @@ -435,9 +432,6 @@ namespace AZ AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); SetDirty(); - Data::AssetId modelAssetId; - MeshComponentRequestBus::EventResult(modelAssetId, GetEntityId(), &MeshComponentRequestBus::Events::GetModelAssetId); - // First generating a unique set of all material asset IDs that will be used for source data generation AZStd::unordered_map assetIdMap; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index f9243510cf..7d89fb0571 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -272,13 +272,11 @@ namespace AZ action = menu.addAction("Edit Source Material...", [this]() { OpenMaterialEditor(); }); action->setEnabled(HasSourceData()); - bool hasAnyMaterial = m_defaultMaterialAsset.GetId().IsValid() || m_materialAsset.GetId().IsValid(); - action = menu.addAction("Edit Material Instance...", [this]() { OpenMaterialInspector(); }); - action->setEnabled(hasAnyMaterial); + action->setEnabled(m_materialAsset.GetId().IsValid()); action = menu.addAction("Edit Material Instance UV Map...", [this]() { OpenUvNameMapInspector(); }); - action->setEnabled(hasAnyMaterial); + action->setEnabled(m_materialAsset.GetId().IsValid()); menu.addSeparator(); From 66f7fa2f4273ac5adc46cef0a6e0f161472a54fb Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Fri, 23 Jul 2021 10:53:55 -0700 Subject: [PATCH 16/22] Fixed a bug where a new entity using a mesh that was already loaded would not be able to correctly initialize a material component. Repro steps: - Create two entities. - Entity 1 - Add a mesh component and assign a model with multiple sub-meshes - Add a material component. The material component looks correct. - Entity 2 - Add a mesh component and assign the same model as the other entity - Add a material component. The material component shows "" for all material slot names The problem was that ReflectedPropertyEditor creates a new Asset<> reference with the correct ID but does not load it. This asset is passed to EditorMaterialComponent, MaterialComponentController, and MeshFeatureProcessor and none of these tell the Asset to load. The MeshFeatureProcessor was not loading the Asset or connecting to the AssetBus because the instance already existed in the InstanceDatabse so from the FP's perspecive there was no need. But for the FP's GetModelAsset() API to function correctly it needs to have the asset initialized to the available AssetData pointer. So we updated the MeshFeatureProcessor to always connect to the AssetBus so it will find the available AssetData via the OnAssetReady callback. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 6e031aa853..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()); } From 13679a7cc3437bd45430e57e7cf076ca87cbbbe8 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Fri, 23 Jul 2021 11:02:06 -0700 Subject: [PATCH 17/22] Reverted partial support for property overrides on default material assignments. This needs more UI design discussion first. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Source/Material/EditorMaterialComponentSlot.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 7d89fb0571..39dde65a99 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -227,11 +227,9 @@ namespace AZ OnPropertyChanged(); }; - Data::Asset assetToEdit = m_materialAsset.GetId().IsValid() ? m_materialAsset : m_defaultMaterialAsset; - - if (assetToEdit.GetId().IsValid()) + if (m_materialAsset.GetId().IsValid()) { - if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), assetToEdit.GetId(), m_propertyOverrides, applyPropertyChangedCallback)) + if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback)) { OnMaterialChanged(); } @@ -247,11 +245,9 @@ namespace AZ OnPropertyChanged(); }; - Data::Asset assetToEdit = m_materialAsset.GetId().IsValid() ? m_materialAsset : m_defaultMaterialAsset; - - if (assetToEdit.GetId().IsValid()) + if (m_materialAsset.GetId().IsValid()) { - if (EditorMaterialComponentInspector::OpenInspectorDialog(assetToEdit.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) + if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) { OnMaterialChanged(); } From e2eba69d338f90493ca7ea624957f1b7bf520a03 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Fri, 30 Jul 2021 18:19:35 -0500 Subject: [PATCH 18/22] updating FindMaterialAssignmentIdInLod to use ModelMaterialSlot } Signed-off-by: Guthrie Adams --- .../Source/Material/MaterialAssignment.cpp | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index 2c68309e27..e43dde5d78 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"); - } } @@ -152,7 +149,8 @@ namespace AZ { if (mesh.m_material) { - const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromStableIdOnly(mesh.m_materialSlotStableId); + const MaterialAssignmentId generalId = + MaterialAssignmentId::CreateFromStableIdOnly(mesh.m_materialSlotStableId); materials[generalId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material); const MaterialAssignmentId specificId = @@ -168,19 +166,17 @@ 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(); @@ -193,13 +189,13 @@ namespace AZ { 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; From 5d3d3b907ed528ff417091a8633ea95c39326dbb Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Fri, 30 Jul 2021 16:52:43 -0700 Subject: [PATCH 19/22] Changed a couple function parameters to const& Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Include/Atom/Feature/Material/MaterialAssignment.h | 2 +- .../Common/Code/Source/Material/MaterialAssignment.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index e43dde5d78..4d437be244 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -166,7 +166,7 @@ namespace AZ } MaterialAssignmentId FindMaterialAssignmentIdInLod( - const Data::Instance model, + const Data::Instance& model, const Data::Instance& lod, const MaterialAssignmentLodIndex lodIndex, const AZStd::string& labelFilter) @@ -183,7 +183,7 @@ namespace AZ } 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()) { From 7d84a005c00c1b1c3d1b9ef0e5456d0ecf15146d Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 2 Aug 2021 10:49:13 -0700 Subject: [PATCH 20/22] Updated unit tests and fixed build failures. --- .../Model/ModelAssetBuilderComponent.h | 1 + Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp | 46 +++++++++++-------- 2 files changed, 29 insertions(+), 18 deletions(-) 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 832a8700ba..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; diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index cdf0d0166d..625240a01f 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 @@ -136,6 +137,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 +150,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 +168,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 +184,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 +199,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 +217,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 +243,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 +276,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) @@ -687,11 +700,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 +712,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 +720,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 +840,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 +855,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 +898,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 +920,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 +1032,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)); From afe5398f0ff6280f039a71e7f278e1c34a0d7644 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 2 Aug 2021 11:19:03 -0700 Subject: [PATCH 21/22] Fixed model unit tests --- Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 625240a01f..368fb2c3c6 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -107,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")); @@ -312,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; }; From 8542de8c3291458b11bebc158e273d067d442504 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 2 Aug 2021 12:02:58 -0700 Subject: [PATCH 22/22] Fixed a link error on android (clang) --- .../Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h | 2 +- .../RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) 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 index 27137459b7..dd46aca6cd 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelMaterialSlot.h @@ -26,7 +26,7 @@ namespace AZ static void Reflect(AZ::ReflectContext* context); using StableId = uint32_t; - static const StableId InvalidStableId = -1; + 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. diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp index 61f3ebbe3a..0900949625 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelMaterialSlot.cpp @@ -13,6 +13,10 @@ 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))