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>
monroegm-disable-blank-issue-2
Chris Santora 4 years ago committed by santorac
parent 0cf6ecf3f7
commit 14d2e38b90

@ -15,6 +15,7 @@
#include <AzCore/RTTI/RTTI.h> #include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h> #include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/StringFunc/StringFunc.h> #include <AzCore/StringFunc/StringFunc.h>
#include <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
namespace AZ namespace AZ
{ {
@ -23,7 +24,7 @@ namespace AZ
using MaterialAssignmentLodIndex = AZ::u64; using MaterialAssignmentLodIndex = AZ::u64;
//! MaterialAssignmentId is used to address available and overridable material slots on a model. //! 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. //! a specific material slot or a set of slots matching either.
struct MaterialAssignmentId final struct MaterialAssignmentId final
{ {
@ -33,41 +34,39 @@ namespace AZ
MaterialAssignmentId() = default; 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(); static MaterialAssignmentId CreateDefault();
//! Create an ID that maps to all material slots with a corresponding asset ID, regardless of LOD. //! Create an ID that maps to all material slots with a corresponding slot ID, regardless of LOD.
static MaterialAssignmentId CreateFromAssetOnly(AZ::Data::AssetId materialAssetId); static MaterialAssignmentId CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId);
//! Create an ID that maps to a specific material slot with a corresponding asset ID and LOD. //! Create an ID that maps to a specific material slot with a corresponding stable ID and LOD.
static MaterialAssignmentId CreateFromLodAndAsset(MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId); 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; bool IsDefault() const;
//! Returns true if the asset ID is valid and LOD is invalid //! Returns true if the slot stable ID is valid and LOD is invalid, meaning this assignment applies to every LOD.
bool IsAssetOnly() const; bool IsSlotIdOnly() const;
//! Returns true if the asset ID and LOD are both valid //! 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 IsLodAndAsset() const; 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; 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; size_t GetHash() const;
//! Returns true if both asset ID sub IDs and LODs match
bool operator==(const MaterialAssignmentId& rhs) const; 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; bool operator!=(const MaterialAssignmentId& rhs) const;
static constexpr MaterialAssignmentLodIndex NonLodIndex = -1; static constexpr MaterialAssignmentLodIndex NonLodIndex = -1;
MaterialAssignmentLodIndex m_lodIndex = NonLodIndex; MaterialAssignmentLodIndex m_lodIndex = NonLodIndex;
AZ::Data::AssetId m_materialAssetId = AZ::Data::AssetId(); RPI::ModelMaterialSlot::StableId m_materialSlotStableId = RPI::ModelMaterialSlot::InvalidStableId;
}; };
} // namespace Render } // namespace Render

@ -123,7 +123,7 @@ namespace AZ
} }
const MaterialAssignment& assetAssignment = const MaterialAssignment& assetAssignment =
GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromAssetOnly(id.m_materialAssetId)); GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromStableIdOnly(id.m_materialSlotStableId));
if (assetAssignment.m_materialInstance.get()) if (assetAssignment.m_materialInstance.get())
{ {
return assetAssignment; return assetAssignment;
@ -152,11 +152,11 @@ namespace AZ
{ {
if (mesh.m_material) 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); materials[generalId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material);
const MaterialAssignmentId specificId = 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); materials[specificId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material);
} }
} }

@ -19,9 +19,9 @@ namespace AZ
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{ {
serializeContext->Class<MaterialAssignmentId>() serializeContext->Class<MaterialAssignmentId>()
->Version(1) ->Version(2)
->Field("lodIndex", &MaterialAssignmentId::m_lodIndex) ->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") ->Attribute(AZ::Script::Attributes::Module, "render")
->Constructor() ->Constructor()
->Constructor<const MaterialAssignmentId&>() ->Constructor<const MaterialAssignmentId&>()
->Constructor<MaterialAssignmentLodIndex, AZ::Data::AssetId>() ->Constructor<MaterialAssignmentLodIndex, RPI::ModelMaterialSlot::StableId>()
->Method("IsDefault", &MaterialAssignmentId::IsDefault) ->Method("IsDefault", &MaterialAssignmentId::IsDefault)
->Method("IsAssetOnly", &MaterialAssignmentId::IsAssetOnly) ->Method("IsAssetOnly", &MaterialAssignmentId::IsSlotIdOnly) // Included for compatibility. Use "IsSlotIdOnly" instead.
->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndAsset) ->Method("IsLodAndAsset", &MaterialAssignmentId::IsLodAndSlotId) // Included for compatibility. Use "IsLodAndSlotId" instead.
->Method("IsSlotIdOnly", &MaterialAssignmentId::IsSlotIdOnly)
->Method("IsLodAndSlotId", &MaterialAssignmentId::IsLodAndSlotId)
->Method("ToString", &MaterialAssignmentId::ToString) ->Method("ToString", &MaterialAssignmentId::ToString)
->Property("lodIndex", BehaviorValueProperty(&MaterialAssignmentId::m_lodIndex)) ->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_lodIndex(lodIndex)
, m_materialAssetId(materialAssetId) , m_materialSlotStableId(materialSlotStableId)
{ {
} }
MaterialAssignmentId MaterialAssignmentId::CreateDefault() 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( MaterialAssignmentId MaterialAssignmentId::CreateFromLodAndStableId(
MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId) MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId)
{ {
return MaterialAssignmentId(lodIndex, materialAssetId); return MaterialAssignmentId(lodIndex, materialSlotStableId);
} }
bool MaterialAssignmentId::IsDefault() const 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 MaterialAssignmentId::ToString() const
{ {
AZStd::string assetPathString; return AZStd::string::format("%u:%llu", m_materialSlotStableId, m_lodIndex);
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);
} }
size_t MaterialAssignmentId::GetHash() const size_t MaterialAssignmentId::GetHash() const
{ {
size_t seed = 0; size_t seed = 0;
AZStd::hash_combine(seed, m_lodIndex); AZStd::hash_combine(seed, m_lodIndex);
AZStd::hash_combine(seed, m_materialAssetId.m_subId); AZStd::hash_combine(seed, m_materialSlotStableId);
return seed; return seed;
} }
bool MaterialAssignmentId::operator==(const MaterialAssignmentId& rhs) const 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 bool MaterialAssignmentId::operator!=(const MaterialAssignmentId& rhs) const

@ -589,18 +589,6 @@ namespace AZ
{ {
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); 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; m_model = model;
const size_t modelLodCount = m_model->GetLodCount(); const size_t modelLodCount = m_model->GetLodCount();
m_drawPacketListsByLod.resize(modelLodCount); m_drawPacketListsByLod.resize(modelLodCount);
@ -644,10 +632,12 @@ namespace AZ
for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex) for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex)
{ {
Data::Instance<RPI::Material> material = modelLod.GetMeshes()[meshIndex].m_material; const RPI::ModelLod::Mesh& mesh = modelLod.GetMeshes()[meshIndex];
Data::Instance<RPI::Material> material = mesh.m_material;
// Determine if there is a material override specified for this sub mesh // 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); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId);
if (materialAssignment.m_materialInstance.get()) if (materialAssignment.m_materialInstance.get())
{ {
@ -790,7 +780,7 @@ namespace AZ
// retrieve the material // retrieve the material
Data::Instance<RPI::Material> material = mesh.m_material; Data::Instance<RPI::Material> 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); const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId);
if (materialAssignment.m_materialInstance.get()) if (materialAssignment.m_materialInstance.get())
{ {

@ -639,8 +639,13 @@ namespace AZ
Aabb localAabb = lod.m_subMeshProperties[i].m_aabb; Aabb localAabb = lod.m_subMeshProperties[i].m_aabb;
modelLodCreator.SetMeshAabb(AZStd::move(localAabb)); 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(); modelLodCreator.EndMesh();
} }

@ -72,6 +72,8 @@ namespace AZ
RHI::IndexBufferView m_indexBufferView; RHI::IndexBufferView m_indexBufferView;
StreamInfoList m_streamInfo; StreamInfoList m_streamInfo;
ModelMaterialSlot::StableId m_materialSlotStableId = ModelMaterialSlot::InvalidStableId;
//! The default material assigned to the mesh by the asset. //! The default material assigned to the mesh by the asset.
Data::Instance<Material> m_material; Data::Instance<Material> m_material;

@ -49,6 +49,9 @@ namespace AZ
//! Returns the model-space axis aligned bounding box //! Returns the model-space axis aligned bounding box
const AZ::Aabb& GetAabb() const; 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 //! Returns the number of Lods in the model
size_t GetLodCount() const; size_t GetLodCount() const;

@ -16,6 +16,7 @@
#include <Atom/RPI.Reflect/Buffer/BufferAssetView.h> #include <Atom/RPI.Reflect/Buffer/BufferAssetView.h>
#include <Atom/RPI.Reflect/Buffer/BufferAsset.h> #include <Atom/RPI.Reflect/Buffer/BufferAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h> #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
#include <AzCore/Asset/AssetCommon.h> #include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Aabb.h> #include <AzCore/Math/Aabb.h>
@ -84,8 +85,9 @@ namespace AZ
//! Returns the number of indices in this mesh //! Returns the number of indices in this mesh
uint32_t GetIndexCount() const; uint32_t GetIndexCount() const;
//! Returns the reference to material asset used by this mesh //! Returns the index of the material slot used by this mesh.
const Data::Asset <MaterialAsset>& GetMaterialAsset() const; //! This indexes into the ModelLodAsset's material slot list.
size_t GetMaterialSlotIndex() const;
//! Returns the name of this mesh //! Returns the name of this mesh
const AZ::Name& GetName() const; const AZ::Name& GetName() const;
@ -124,7 +126,9 @@ namespace AZ
AZ::Name m_name; AZ::Name m_name;
AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
Data::Asset<MaterialAsset> 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 // Both the buffer in m_indexBufferAssetView and the buffers in m_streamBufferInfo
// may point to either unique buffers for the mesh or to consolidated // 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 //! Returns the model-space axis-aligned bounding box of all meshes in the lod
const AZ::Aabb& GetAabb() const; const AZ::Aabb& GetAabb() const;
//! Returns an array view into the collection of material slots available to this lod
AZStd::array_view<ModelMaterialSlot> 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: private:
AZStd::vector<Mesh> m_meshes; AZStd::vector<Mesh> m_meshes;
AZ::Aabb m_aabb = AZ::Aabb::CreateNull(); AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
// These buffers owned by the lod are the consolidated super buffers. // These buffers owned by the lod are the consolidated super buffers.
// Meshes may either have views into these buffers or they may own // Meshes may either have views into these buffers or they may own
// their own buffers. // their own buffers.
@ -155,6 +169,13 @@ namespace AZ
Data::Asset<BufferAsset> m_indexBuffer; Data::Asset<BufferAsset> m_indexBuffer;
AZStd::vector<Data::Asset<BufferAsset>> m_streamBuffers; AZStd::vector<Data::Asset<BufferAsset>> 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<ModelMaterialSlot> m_materialSlots;
// A default ModelMaterialSlot to be returned upon error conditions.
ModelMaterialSlot m_fallbackSlot;
void AddMesh(const Mesh& mesh); void AddMesh(const Mesh& mesh);
void SetReady(); void SetReady();

@ -8,6 +8,7 @@
#pragma once #pragma once
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Atom/RPI.Reflect/Model/ModelLodAsset.h> #include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
#include <Atom/RPI.Reflect/AssetCreator.h> #include <Atom/RPI.Reflect/AssetCreator.h>
@ -45,9 +46,10 @@ namespace AZ
//! Begin and BeginMesh must be called first. //! Begin and BeginMesh must be called first.
void SetMeshAabb(AZ::Aabb&& aabb); 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 //! Begin and BeginMesh must be called first
void SetMeshMaterialAsset(const Data::Asset<MaterialAsset>& materialAsset); void SetMeshMaterialSlot(const ModelMaterialSlot& materialSlot);
//! Sets the given BufferAssetView to the current SubMesh as the index buffer. //! Sets the given BufferAssetView to the current SubMesh as the index buffer.
//! Begin and BeginMesh must be called first //! Begin and BeginMesh must be called first

@ -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 <Atom/RPI.Reflect/Material/MaterialAsset.h>
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<MaterialAsset> m_defaultMaterialAsset{ Data::AssetLoadBehavior::PreLoad }; //!< The material that will be applied to this slot by default.
};
using ModelMaterialSlotMap = AZStd::unordered_map<ModelMaterialSlot::StableId, ModelMaterialSlot>;
} //namespace RPI
} // namespace AZ

@ -109,7 +109,7 @@ namespace AZ
if (auto* serialize = azrtti_cast<SerializeContext*>(context)) if (auto* serialize = azrtti_cast<SerializeContext*>(context))
{ {
serialize->Class<ModelAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>() serialize->Class<ModelAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>()
->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); auto iter = materialAssetsByUid.find(meshView.m_materialUid);
if (iter != materialAssetsByUid.end()) if (iter != materialAssetsByUid.end())
{ {
const Data::Asset<MaterialAsset>& materialAsset = iter->second.m_asset; ModelMaterialSlot materialSlot;
lodAssetCreator.SetMeshMaterialAsset(materialAsset); materialSlot.m_stableId = meshView.m_materialUid;
materialSlot.m_displayName = iter->second.m_name;
materialSlot.m_defaultMaterialAsset = iter->second.m_asset;
lodAssetCreator.SetMeshMaterialSlot(materialSlot);
} }
} }

@ -100,10 +100,13 @@ namespace AZ
} }
} }
auto& materialAsset = mesh.GetMaterialAsset(); const ModelMaterialSlot& materialSlot = lodAsset.GetMaterialSlot(mesh.GetMaterialSlotIndex());
if (materialAsset.IsReady())
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)); m_meshes.emplace_back(AZStd::move(meshInstance));

@ -24,6 +24,7 @@ namespace AZ
{ {
ModelLodAsset::Reflect(context); ModelLodAsset::Reflect(context);
ModelAsset::Reflect(context); ModelAsset::Reflect(context);
ModelMaterialSlot::Reflect(context);
MorphTargetMetaAsset::Reflect(context); MorphTargetMetaAsset::Reflect(context);
SkinMetaAsset::Reflect(context); SkinMetaAsset::Reflect(context);
} }

@ -56,6 +56,30 @@ namespace AZ
{ {
return m_aabb; return m_aabb;
} }
RPI::ModelMaterialSlotMap ModelAsset::GetModelMaterialSlots() const
{
RPI::ModelMaterialSlotMap slotMap;
for (const Data::Asset<AZ::RPI::ModelLodAsset>& 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 size_t ModelAsset::GetLodCount() const
{ {

@ -23,24 +23,25 @@ namespace AZ
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{ {
serializeContext->Class<ModelLodAsset>() serializeContext->Class<ModelLodAsset>()
->Version(0) ->Version(1)
->Field("Meshes", &ModelLodAsset::m_meshes) ->Field("Meshes", &ModelLodAsset::m_meshes)
->Field("Aabb", &ModelLodAsset::m_aabb) ->Field("Aabb", &ModelLodAsset::m_aabb)
->Field("MaterialSlots", &ModelLodAsset::m_materialSlots)
; ;
} }
Mesh::Reflect(context); Mesh::Reflect(context);
} }
void ModelLodAsset::Mesh::Reflect(AZ::ReflectContext* context) void ModelLodAsset::Mesh::Reflect(AZ::ReflectContext* context)
{ {
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{ {
serializeContext->Class<ModelLodAsset::Mesh>() serializeContext->Class<ModelLodAsset::Mesh>()
->Version(0) ->Version(1)
->Field("Material", &ModelLodAsset::Mesh::m_materialAsset)
->Field("Name", &ModelLodAsset::Mesh::m_name) ->Field("Name", &ModelLodAsset::Mesh::m_name)
->Field("AABB", &ModelLodAsset::Mesh::m_aabb) ->Field("AABB", &ModelLodAsset::Mesh::m_aabb)
->Field("MaterialSlotIndex", &ModelLodAsset::Mesh::m_materialSlotIndex)
->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView) ->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView)
->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo) ->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo)
; ;
@ -75,9 +76,9 @@ namespace AZ
return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount; return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount;
} }
const Data::Asset <MaterialAsset>& ModelLodAsset::Mesh::GetMaterialAsset() const size_t ModelLodAsset::Mesh::GetMaterialSlotIndex() const
{ {
return m_materialAsset; return m_materialSlotIndex;
} }
const AZ::Name& ModelLodAsset::Mesh::GetName() const const AZ::Name& ModelLodAsset::Mesh::GetName() const
@ -118,6 +119,41 @@ namespace AZ
{ {
return m_aabb; return m_aabb;
} }
AZStd::array_view<ModelMaterialSlot> 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 BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const
{ {

@ -60,12 +60,32 @@ namespace AZ
m_currentMesh.m_aabb = AZStd::move(aabb); m_currentMesh.m_aabb = AZStd::move(aabb);
} }
} }
void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset<MaterialAsset>& 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()); creator.SetMeshName(sourceMesh.GetName());
AZ::Aabb aabb = sourceMesh.GetAabb(); AZ::Aabb aabb = sourceMesh.GetAabb();
creator.SetMeshAabb(AZStd::move(aabb)); creator.SetMeshAabb(AZStd::move(aabb));
creator.SetMeshMaterialAsset(sourceMesh.GetMaterialAsset());
const ModelMaterialSlot& materialSlot = sourceAsset->GetMaterialSlot(sourceMesh.GetMaterialSlotIndex());
creator.SetMeshMaterialSlot(materialSlot);
// Mesh index buffer view // Mesh index buffer view
const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView(); const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView();

@ -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 <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace RPI
{
void ModelMaterialSlot::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelMaterialSlot>()
->Version(0)
->Field("StableId", &ModelMaterialSlot::m_stableId)
->Field("DisplayName", &ModelMaterialSlot::m_displayName)
->Field("DefaultMaterialAsset", &ModelMaterialSlot::m_defaultMaterialAsset)
;
}
}
} // namespace RPI
} // namespace AZ

@ -22,6 +22,7 @@ set(FILES
Include/Atom/RPI.Reflect/Model/ModelKdTree.h Include/Atom/RPI.Reflect/Model/ModelKdTree.h
Include/Atom/RPI.Reflect/Model/ModelLodAsset.h Include/Atom/RPI.Reflect/Model/ModelLodAsset.h
Include/Atom/RPI.Reflect/Model/ModelLodIndex.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/ModelAssetCreator.h
Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h Include/Atom/RPI.Reflect/Model/ModelLodAssetCreator.h
Include/Atom/RPI.Reflect/Model/MorphTargetDelta.h Include/Atom/RPI.Reflect/Model/MorphTargetDelta.h
@ -106,6 +107,7 @@ set(FILES
Source/RPI.Reflect/Model/ModelLodAsset.cpp Source/RPI.Reflect/Model/ModelLodAsset.cpp
Source/RPI.Reflect/Model/ModelAssetCreator.cpp Source/RPI.Reflect/Model/ModelAssetCreator.cpp
Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp Source/RPI.Reflect/Model/ModelLodAssetCreator.cpp
Source/RPI.Reflect/Model/ModelMaterialSlot.cpp
Source/RPI.Reflect/Model/MorphTargetDelta.cpp Source/RPI.Reflect/Model/MorphTargetDelta.cpp
Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp Source/RPI.Reflect/Model/MorphTargetMetaAsset.cpp
Source/RPI.Reflect/Model/MorphTargetMetaAssetCreator.cpp Source/RPI.Reflect/Model/MorphTargetMetaAssetCreator.cpp

@ -69,6 +69,9 @@ namespace AZ
: public ComponentBus : public ComponentBus
{ {
public: 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 MaterialAssignmentMap GetMaterialAssignments() const = 0;
virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0; virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0;
}; };

@ -44,57 +44,8 @@ namespace AZ
if (classElement.GetVersion() < 3) if (classElement.GetVersion() < 3)
{ {
// The default material was changed from an asset to an EditorMaterialComponentSlot and old data must be converted AZ_Error("EditorMaterialComponent", false, "Material Component version < 3 is no longer supported");
constexpr AZ::u32 defaultMaterialAssetDataCrc = AZ_CRC("defaultMaterialAsset", 0x736fc071); return false;
Data::Asset<RPI::MaterialAsset> 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);
} }
if (classElement.GetVersion() < 4) if (classElement.GetVersion() < 4)
@ -238,7 +189,7 @@ namespace AZ
for (auto& materialSlotPair : GetMaterialSlots()) for (auto& materialSlotPair : GetMaterialSlots())
{ {
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
if (materialSlot->m_id.IsAssetOnly()) if (materialSlot->m_id.IsSlotIdOnly())
{ {
materialSlot->Clear(); materialSlot->Clear();
} }
@ -251,7 +202,7 @@ namespace AZ
for (auto& materialSlotPair : GetMaterialSlots()) for (auto& materialSlotPair : GetMaterialSlots())
{ {
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
if (materialSlot->m_id.IsLodAndAsset()) if (materialSlot->m_id.IsLodAndSlotId())
{ {
materialSlot->Clear(); materialSlot->Clear();
} }
@ -318,6 +269,9 @@ namespace AZ
// Build the controller configuration from the editor configuration // Build the controller configuration from the editor configuration
MaterialComponentConfig config = m_controller.GetConfiguration(); MaterialComponentConfig config = m_controller.GetConfiguration();
config.m_materials.clear(); config.m_materials.clear();
RPI::ModelMaterialSlotMap modelMaterialSlots;
MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
for (const auto& materialSlotPair : GetMaterialSlots()) for (const auto& materialSlotPair : GetMaterialSlots())
{ {
@ -340,10 +294,15 @@ namespace AZ
} }
else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty()) else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty())
{ {
MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id]; auto materialSlotIter = modelMaterialSlots.find(materialSlot->m_id.m_materialSlotStableId);
materialAssignment.m_materialAsset.Create(materialSlot->m_id.m_materialAssetId);
materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides; if (materialSlotIter != modelMaterialSlots.end())
materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides; {
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 // Get the known material assignment slots from the associated model or other source
MaterialAssignmentMap materialsFromSource; MaterialAssignmentMap materialsFromSource;
MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments); 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 // Generate the table of editable materials using the source data to define number of groups, elements, and initial values
for (const auto& materialPair : materialsFromSource) for (const auto& materialPair : materialsFromSource)
@ -385,6 +347,29 @@ namespace AZ
OnConfigurationChanged(); OnConfigurationChanged();
}; };
const char* UnknownSlotName = "<unknown>";
// 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 // if material is present in controller configuration, assign its data
const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id);
slot.m_materialAsset = materialFromController.m_materialAsset; slot.m_materialAsset = materialFromController.m_materialAsset;
@ -400,13 +385,13 @@ namespace AZ
continue; continue;
} }
if (slot.m_id.IsAssetOnly()) if (slot.m_id.IsSlotIdOnly())
{ {
m_materialSlots.push_back(slot); m_materialSlots.push_back(slot);
continue; continue;
} }
if (slot.m_id.IsLodAndAsset()) if (slot.m_id.IsLodAndSlotId())
{ {
// Resize the containers to fit all elements // Resize the containers to fit all elements
m_materialSlotsByLod.resize(AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1))); m_materialSlotsByLod.resize(AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1)));
@ -452,17 +437,19 @@ namespace AZ
{ {
AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials.");
SetDirty(); 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 // First generating a unique set of all material asset IDs that will be used for source data generation
AZStd::unordered_set<AZ::Data::AssetId> assetIds; AZStd::unordered_set<AZ::Data::AssetId> assetIds;
auto materialSlots = GetMaterialSlots(); for (auto& materialSlot : modelMaterialSlots)
for (auto& materialSlotPair : materialSlots)
{ {
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; Data::AssetId defaultMaterialAssetId = materialSlot.second.m_defaultMaterialAsset.GetId();
if (materialSlot->m_id.m_materialAssetId.IsValid()) 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) for (const AZ::Data::AssetId& assetId : assetIds)
{ {
EditorMaterialComponentExporter::ExportItem exportItem; EditorMaterialComponentExporter::ExportItem exportItem;
exportItem.m_assetId = assetId; exportItem.m_originalAssetId = assetId;
exportItems.push_back(exportItem); exportItems.push_back(exportItem);
} }
@ -489,12 +476,23 @@ namespace AZ
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0); const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0);
if (assetIdOutcome) if (assetIdOutcome)
{ {
for (auto& materialSlotPair : materialSlots) for (auto& materialSlotPair : GetMaterialSlots())
{ {
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second; EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second;
if (materialSlot && materialSlot->m_id.m_materialAssetId == exportItem.m_assetId)
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());
}
}
} }
} }
} }

@ -132,7 +132,7 @@ namespace AZ
int row = 0; int row = 0;
for (ExportItem& exportItem : exportItems) 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 // Configuring initial settings based on whether or not the target file already exists
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); 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 // Create a check box for toggling the enabled state of this item
QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget); QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget);
materialSlotCheckBox->setChecked(exportItem.m_enabled); 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); tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox);
// Create a file picker widget for selecting the save path for the exported material // Create a file picker widget for selecting the save path for the exported material
@ -256,7 +256,7 @@ namespace AZ
} }
EditorMaterialComponentUtil::MaterialEditData editData; 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."); AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data.");
return false; return false;

@ -19,10 +19,10 @@ namespace AZ
{ {
namespace EditorMaterialComponentExporter 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); 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); AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId);
struct ExportItem struct ExportItem
@ -30,16 +30,17 @@ namespace AZ
bool m_enabled = true; bool m_enabled = true;
bool m_exists = false; bool m_exists = false;
bool m_overwrite = 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; AZStd::string m_exportPath;
}; };
using ExportItemsContainer = AZStd::vector<ExportItem>; using ExportItemsContainer = AZStd::vector<ExportItem>;
// 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); 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); bool ExportMaterialSourceData(const ExportItem& exportItem);
} // namespace EditorMaterialComponentExporter } // namespace EditorMaterialComponentExporter
} // namespace Render } // namespace Render

@ -20,7 +20,7 @@
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
#include <QCursor> #include <QCursor>
AZ_POP_DISABLE_WARNING AZ_POP_DISABLE_WARNING
@ -48,7 +48,7 @@ namespace AZ
return false; return false;
} }
const MaterialAssignmentId newId(oldId.first, oldId.second); const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId);
classElement.AddElementWithData(context, "id", newId); classElement.AddElementWithData(context, "id", newId);
} }
@ -83,6 +83,7 @@ namespace AZ
->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Version(5, &EditorMaterialComponentSlot::ConvertVersion)
->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("id", &EditorMaterialComponentSlot::m_id)
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset)
; ;
if (AZ::EditContext* editContext = serializeContext->GetEditContext()) if (AZ::EditContext* editContext = serializeContext->GetEditContext())
@ -121,21 +122,12 @@ namespace AZ
AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const
{ {
return m_id.m_materialAssetId; return m_defaultMaterialAsset.GetId();
} }
AZStd::string EditorMaterialComponentSlot::GetLabel() const AZStd::string EditorMaterialComponentSlot::GetLabel() const
{ {
// Generate the label for the material slot based on the assignment ID return m_label;
// 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 : "<unknown>";
} }
bool EditorMaterialComponentSlot::HasSourceData() const bool EditorMaterialComponentSlot::HasSourceData() const
@ -183,27 +175,22 @@ namespace AZ
OnMaterialChanged(); OnMaterialChanged();
} }
void EditorMaterialComponentSlot::SetDefaultAsset() void EditorMaterialComponentSlot::ResetToDefaultAsset()
{ {
m_materialAsset = {}; m_materialAsset = m_defaultMaterialAsset;
m_propertyOverrides = {}; m_propertyOverrides = {};
m_matModUvOverrides = {}; 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(); OnMaterialChanged();
} }
void EditorMaterialComponentSlot::OpenMaterialExporter() void EditorMaterialComponentSlot::OpenMaterialExporter()
{ {
// Because we are generating a source material from this specific slot there is only one entry // 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::ExportItemsContainer exportItems;
{ {
EditorMaterialComponentExporter::ExportItem exportItem; EditorMaterialComponentExporter::ExportItem exportItem;
exportItem.m_assetId = m_id.m_materialAssetId; exportItem.m_originalAssetId = m_defaultMaterialAsset.GetId();
exportItems.push_back(exportItem); exportItems.push_back(exportItem);
} }
@ -275,7 +262,7 @@ namespace AZ
QAction* action = nullptr; QAction* action = nullptr;
action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); }); action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); });
action->setEnabled(m_id.m_materialAssetId.IsValid()); action->setEnabled(m_defaultMaterialAsset.GetId().IsValid());
menu.addSeparator(); menu.addSeparator();

@ -34,7 +34,7 @@ namespace AZ
AZStd::string GetLabel() const; AZStd::string GetLabel() const;
bool HasSourceData() const; bool HasSourceData() const;
void OpenMaterialEditor() const; void OpenMaterialEditor() const;
void SetDefaultAsset(); void ResetToDefaultAsset();
void Clear(); void Clear();
void ClearOverrides(); void ClearOverrides();
void OpenMaterialExporter(); void OpenMaterialExporter();
@ -42,7 +42,9 @@ namespace AZ
void OpenUvNameMapInspector(); void OpenUvNameMapInspector();
MaterialAssignmentId m_id; MaterialAssignmentId m_id;
AZStd::string m_label;
Data::Asset<RPI::MaterialAsset> m_materialAsset; Data::Asset<RPI::MaterialAsset> m_materialAsset;
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
MaterialPropertyOverrideMap m_propertyOverrides; MaterialPropertyOverrideMap m_propertyOverrides;
AZStd::function<void()> m_materialChangedCallback; AZStd::function<void()> m_materialChangedCallback;
AZStd::function<void()> m_propertyChangedCallback; AZStd::function<void()> m_propertyChangedCallback;

@ -44,7 +44,7 @@ namespace AZ
for (const auto& oldPair : oldMaterials) for (const auto& oldPair : oldMaterials)
{ {
const DeprecatedMaterialAssignmentId& oldId = oldPair.first; 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; newMaterials[newId] = oldPair.second;
} }

@ -251,6 +251,19 @@ namespace AZ
m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transformInterface->GetWorldTM(), m_cachedNonUniformScale); m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transformInterface->GetWorldTM(), m_cachedNonUniformScale);
} }
} }
RPI::ModelMaterialSlotMap MeshComponentController::GetModelMaterialSlots() const
{
Data::Asset<const RPI::ModelAsset> modelAsset = GetModelAsset();
if (modelAsset.IsReady())
{
return modelAsset->GetModelMaterialSlots();
}
else
{
return {};
}
}
MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const
{ {

@ -111,6 +111,7 @@ namespace AZ
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// MaterialReceiverRequestBus::Handler overrides ... // MaterialReceiverRequestBus::Handler overrides ...
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
MaterialAssignmentMap GetMaterialAssignments() const override; MaterialAssignmentMap GetMaterialAssignments() const override;
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override; AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;

@ -96,12 +96,10 @@ namespace AZ
skinnedSubMesh.m_vertexCount = aznumeric_cast<uint32_t>(subMeshVertexCount); skinnedSubMesh.m_vertexCount = aznumeric_cast<uint32_t>(subMeshVertexCount);
lodVertexCount += aznumeric_cast<uint32_t>(subMeshVertexCount); lodVertexCount += aznumeric_cast<uint32_t>(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 skinnedSubMesh.m_material = lodAsset->GetMaterialSlot(modelMesh.GetMaterialSlotIndex()).m_defaultMaterialAsset;
AZ::u32 subId = modelMesh.GetMaterialAsset().GetId().m_subId;
AZ::Data::AssetId materialId{ actorAssetId.m_guid, subId };
// Queue the material asset - the ModelLod seems to handle delayed material loads // Queue the material asset - the ModelLod seems to handle delayed material loads
skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid<RPI::MaterialAsset>(), skinnedSubMesh.m_material.GetAutoLoadBehavior()); skinnedSubMesh.m_material.QueueLoad();
subMeshes.push_back(skinnedSubMesh); subMeshes.push_back(skinnedSubMesh);
} }
else else

@ -307,6 +307,19 @@ namespace AZ
m_meshFeatureProcessor = nullptr; m_meshFeatureProcessor = nullptr;
m_skinnedMeshFeatureProcessor = nullptr; m_skinnedMeshFeatureProcessor = nullptr;
} }
RPI::ModelMaterialSlotMap AtomActorInstance::GetModelMaterialSlots() const
{
Data::Asset<const RPI::ModelAsset> modelAsset = GetModelAsset();
if (modelAsset.IsReady())
{
return modelAsset->GetModelMaterialSlots();
}
else
{
return {};
}
}
MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const
{ {

@ -120,6 +120,7 @@ namespace AZ
///////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MaterialReceiverRequestBus::Handler overrides... // MaterialReceiverRequestBus::Handler overrides...
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
MaterialAssignmentMap GetMaterialAssignments() const override; MaterialAssignmentMap GetMaterialAssignments() const override;
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override; AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;

@ -116,7 +116,10 @@ namespace WhiteBox
// set the default material // set the default material
if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(TexturedMaterialPath.data())) if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(TexturedMaterialPath.data()))
{ {
modelLodCreator.SetMeshMaterialAsset(materialAsset); AZ::RPI::ModelMaterialSlot materialSlot;
materialSlot.m_stableId = 0;
materialSlot.m_defaultMaterialAsset = materialAsset;
modelLodCreator.SetMeshMaterialSlot(materialSlot);
} }
else else
{ {

Loading…
Cancel
Save