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/ReflectContext.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
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

@ -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);
}
}

@ -19,9 +19,9 @@ namespace AZ
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MaterialAssignmentId>()
->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<const MaterialAssignmentId&>()
->Constructor<MaterialAssignmentLodIndex, AZ::Data::AssetId>()
->Constructor<MaterialAssignmentLodIndex, RPI::ModelMaterialSlot::StableId>()
->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

@ -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<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
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<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);
if (materialAssignment.m_materialInstance.get())
{

@ -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();
}

@ -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<Material> m_material;

@ -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;

@ -16,6 +16,7 @@
#include <Atom/RPI.Reflect/Buffer/BufferAssetView.h>
#include <Atom/RPI.Reflect/Buffer/BufferAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Aabb.h>
@ -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 <MaterialAsset>& 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<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
// 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<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:
AZStd::vector<Mesh> 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<BufferAsset> m_indexBuffer;
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 SetReady();

@ -8,6 +8,7 @@
#pragma once
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
#include <Atom/RPI.Reflect/AssetCreator.h>
@ -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>& 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

@ -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))
{
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);
if (iter != materialAssetsByUid.end())
{
const Data::Asset<MaterialAsset>& 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);
}
}

@ -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));

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

@ -56,6 +56,30 @@ namespace AZ
{
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
{

@ -23,24 +23,25 @@ namespace AZ
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelLodAsset>()
->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<AZ::SerializeContext*>(context))
{
serializeContext->Class<ModelLodAsset::Mesh>()
->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 <MaterialAsset>& 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<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
{

@ -60,12 +60,32 @@ namespace AZ
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());
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();

@ -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/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

@ -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<AZ::Name> GetModelUvNames() const = 0;
};

@ -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<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);
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 = "<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
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<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.");
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<AZ::Data::AssetId> 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());
}
}
}
}
}

@ -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;

@ -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<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);
// 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

@ -20,7 +20,7 @@
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QMenu>
#include <QAction>
#include <QAction>
#include <QCursor>
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 : "<unknown>";
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();

@ -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<RPI::MaterialAsset> m_materialAsset;
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
MaterialPropertyOverrideMap m_propertyOverrides;
AZStd::function<void()> m_materialChangedCallback;
AZStd::function<void()> m_propertyChangedCallback;

@ -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;
}

@ -251,6 +251,19 @@ namespace AZ
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
{

@ -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<AZ::Name> GetModelUvNames() const override;

@ -96,12 +96,10 @@ namespace AZ
skinnedSubMesh.m_vertexCount = 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
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<RPI::MaterialAsset>(), skinnedSubMesh.m_material.GetAutoLoadBehavior());
skinnedSubMesh.m_material.QueueLoad();
subMeshes.push_back(skinnedSubMesh);
}
else

@ -307,6 +307,19 @@ namespace AZ
m_meshFeatureProcessor = 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
{

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

@ -116,7 +116,10 @@ namespace WhiteBox
// set the default material
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
{

Loading…
Cancel
Save