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>
This commit is contained in:
+17
-18
@@ -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
|
||||
|
||||
+3
@@ -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;
|
||||
};
|
||||
|
||||
+67
-69
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+6
-5
@@ -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
|
||||
|
||||
+10
-23
@@ -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();
|
||||
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user