Formalized the concept of an model's material slots
Formalized the concept of an model's material slots Before, the ModelAsset and MaterialComponent code was conflating the idea of a material slot ID and a default material assignment. The default material asset's sub-ID was being used to uniquely identify the material slot as well. This blocks our ability to use other materials as the default assignment for individual meshes; we are forced to use whatever material that was generated from the source model file (like FBX). With these changes, we separate the concept of a material AssetId and a material slot ID, and store them separately. There is a new ModelMaterialSlot struct to describe each slot, including a unique "StableId". The ModelAsset stores a map of the slots, and each mesh refers to a slot by its StableId. This is a precursor to another task that will optionally disable the auto-conversion of materials from source model files. Also: - These changes also enable material property overrides without having to make an editable material first, which I don't think was supported before. - Removed unused Default.materialtype from the RPI Assets folder. - Encapsulated members in EditorMaterialComponentExporter::ExportItem for better maintainability. See also https://github.com/o3de/o3de-atom-sampleviewer/pull/175 Testing: - Took screenshots of several AtomTest levels with material overrides before making any changes. Compared these after the changes. Test levels included ActorTest_SingleActor, ActorTest_MultipleActors, and two custom levels that used shaderball and multi-mat_mesh-groups_1m_cubes. - Lots of manual fiddling with material component. - Created a white box component and saw that it rendered correctly. - Cherry-picked these changes into Apocalypse's code base and verified with one of their levels. - Ran AtomSampleViewer automated test suite. Some tests failed, but these were failing before my changes.
This commit is contained in:
@@ -66,6 +66,6 @@ namespace AZ
|
||||
|
||||
//! Find an assignment id corresponding to the lod and label substring filters
|
||||
MaterialAssignmentId FindMaterialAssignmentIdInModel(
|
||||
const Data::Instance<AZ::RPI::Model> model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter);
|
||||
const Data::Instance<AZ::RPI::Model>& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter);
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
+18
-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,51 +24,50 @@ namespace AZ
|
||||
using MaterialAssignmentLodIndex = AZ::u64;
|
||||
|
||||
//! MaterialAssignmentId is used to address available and overridable material slots on a model.
|
||||
//! The LOD and one of the model's original material asset IDs are used as coordinates that identify
|
||||
//! The LOD and one of the model's original material slot IDs are used as coordinates that identify
|
||||
//! a specific material slot or a set of slots matching either.
|
||||
struct MaterialAssignmentId final
|
||||
{
|
||||
AZ_RTTI(AZ::Render::MaterialAssignmentId, "{EB603581-4654-4C17-B6DE-AE61E79EDA97}");
|
||||
AZ_CLASS_ALLOCATOR(AZ::Render::MaterialAssignmentId, SystemAllocator, 0);
|
||||
static void Reflect(ReflectContext* context);
|
||||
static bool ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
|
||||
|
||||
MaterialAssignmentId() = default;
|
||||
|
||||
MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, const AZ::Data::AssetId& materialAssetId);
|
||||
MaterialAssignmentId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId);
|
||||
|
||||
//! Create an ID that maps to all material slots, regardless of asset ID or LOD, effectively applying to an entire model.
|
||||
//! Create an ID that maps to all material slots, regardless of slot ID or LOD, effectively applying to an entire model.
|
||||
static MaterialAssignmentId CreateDefault();
|
||||
|
||||
//! Create an ID that maps to all material slots with a corresponding asset ID, regardless of LOD.
|
||||
static MaterialAssignmentId CreateFromAssetOnly(AZ::Data::AssetId materialAssetId);
|
||||
//! Create an ID that maps to all material slots with a corresponding slot ID, regardless of LOD.
|
||||
static MaterialAssignmentId CreateFromStableIdOnly(RPI::ModelMaterialSlot::StableId materialSlotStableId);
|
||||
|
||||
//! Create an ID that maps to a specific material slot with a corresponding asset ID and LOD.
|
||||
static MaterialAssignmentId CreateFromLodAndAsset(MaterialAssignmentLodIndex lodIndex, AZ::Data::AssetId materialAssetId);
|
||||
//! Create an ID that maps to a specific material slot with a corresponding stable ID and LOD.
|
||||
static MaterialAssignmentId CreateFromLodAndStableId(MaterialAssignmentLodIndex lodIndex, RPI::ModelMaterialSlot::StableId materialSlotStableId);
|
||||
|
||||
//! Returns true if the asset ID and LOD are invalid
|
||||
//! Returns true if the slot stable ID and LOD are invalid, meaning this assignment applies to the entire model.
|
||||
bool IsDefault() const;
|
||||
|
||||
//! Returns true if the asset ID is valid and LOD is invalid
|
||||
bool IsAssetOnly() const;
|
||||
//! Returns true if the slot stable ID is valid and LOD is invalid, meaning this assignment applies to every LOD.
|
||||
bool IsSlotIdOnly() const;
|
||||
|
||||
//! Returns true if the asset ID and LOD are both valid
|
||||
bool IsLodAndAsset() const;
|
||||
//! Returns true if the slot stable ID and LOD are both valid, meaning this assignment applies to a single material slot on a specific LOD.
|
||||
bool IsLodAndSlotId() const;
|
||||
|
||||
//! Creates a string composed of the asset path and LOD
|
||||
//! Creates a string describing all the details of the assignment ID
|
||||
AZStd::string ToString() const;
|
||||
|
||||
//! Creates a hash composed of the asset ID sub ID and LOD
|
||||
//! Creates a hash composed of all elements of the assignment ID
|
||||
size_t GetHash() const;
|
||||
|
||||
//! Returns true if both asset ID sub IDs and LODs match
|
||||
bool operator==(const MaterialAssignmentId& rhs) const;
|
||||
|
||||
//! Returns true if both asset ID sub IDs and LODs do not match
|
||||
bool operator!=(const MaterialAssignmentId& rhs) const;
|
||||
|
||||
static constexpr MaterialAssignmentLodIndex NonLodIndex = -1;
|
||||
|
||||
MaterialAssignmentLodIndex m_lodIndex = NonLodIndex;
|
||||
AZ::Data::AssetId m_materialAssetId = AZ::Data::AssetId();
|
||||
RPI::ModelMaterialSlot::StableId m_materialSlotStableId = RPI::ModelMaterialSlot::InvalidStableId;
|
||||
};
|
||||
|
||||
} // namespace Render
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ namespace AZ
|
||||
uint32_t m_vertexOffset = 0;
|
||||
uint32_t m_vertexCount = 0;
|
||||
Aabb m_aabb = Aabb::CreateNull();
|
||||
Data::Asset<RPI::MaterialAsset> m_material;
|
||||
AZ::RPI::ModelMaterialSlot m_materialSlot;
|
||||
};
|
||||
|
||||
//! Buffer views for a specific sub-mesh that are not modified during skinning and thus are shared by all instances of the same skinned mesh
|
||||
|
||||
@@ -33,8 +33,7 @@ namespace AZ
|
||||
serializeContext->Class<MaterialAssignment>()
|
||||
->Version(1)
|
||||
->Field("MaterialAsset", &MaterialAssignment::m_materialAsset)
|
||||
->Field("PropertyOverrides", &MaterialAssignment::m_propertyOverrides)
|
||||
;
|
||||
->Field("PropertyOverrides", &MaterialAssignment::m_propertyOverrides);
|
||||
}
|
||||
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
@@ -50,8 +49,7 @@ namespace AZ
|
||||
->Constructor<const Data::Asset<RPI::MaterialAsset>&, const Data::Instance<RPI::Material>&>()
|
||||
->Method("ToString", &MaterialAssignment::ToString)
|
||||
->Property("materialAsset", BehaviorValueProperty(&MaterialAssignment::m_materialAsset))
|
||||
->Property("propertyOverrides", BehaviorValueProperty(&MaterialAssignment::m_propertyOverrides))
|
||||
;
|
||||
->Property("propertyOverrides", BehaviorValueProperty(&MaterialAssignment::m_propertyOverrides));
|
||||
|
||||
behaviorContext->ConstantProperty("DefaultMaterialAssignment", BehaviorConstant(DefaultMaterialAssignment))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
@@ -67,7 +65,6 @@ namespace AZ
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Category, "render")
|
||||
->Attribute(AZ::Script::Attributes::Module, "render");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +120,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
const MaterialAssignment& assetAssignment =
|
||||
GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromAssetOnly(id.m_materialAssetId));
|
||||
GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromStableIdOnly(id.m_materialSlotStableId));
|
||||
if (assetAssignment.m_materialInstance.get())
|
||||
{
|
||||
return assetAssignment;
|
||||
@@ -152,11 +149,12 @@ namespace AZ
|
||||
{
|
||||
if (mesh.m_material)
|
||||
{
|
||||
const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromAssetOnly(mesh.m_material->GetAssetId());
|
||||
const MaterialAssignmentId generalId =
|
||||
MaterialAssignmentId::CreateFromStableIdOnly(mesh.m_materialSlotStableId);
|
||||
materials[generalId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material);
|
||||
|
||||
const MaterialAssignmentId specificId =
|
||||
MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId());
|
||||
MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, mesh.m_materialSlotStableId);
|
||||
materials[specificId] = MaterialAssignment(mesh.m_material->GetAsset(), mesh.m_material);
|
||||
}
|
||||
}
|
||||
@@ -168,38 +166,36 @@ namespace AZ
|
||||
}
|
||||
|
||||
MaterialAssignmentId FindMaterialAssignmentIdInLod(
|
||||
const Data::Instance<AZ::RPI::ModelLod>& lod, const MaterialAssignmentLodIndex lodIndex, const AZStd::string& labelFilter)
|
||||
const Data::Instance<AZ::RPI::Model>& model,
|
||||
const Data::Instance<AZ::RPI::ModelLod>& lod,
|
||||
const MaterialAssignmentLodIndex lodIndex,
|
||||
const AZStd::string& labelFilter)
|
||||
{
|
||||
for (const AZ::RPI::ModelLod::Mesh& mesh : lod->GetMeshes())
|
||||
{
|
||||
if (mesh.m_material && mesh.m_material->GetAssetId().IsValid())
|
||||
const AZ::RPI::ModelMaterialSlot& slot = model->GetModelAsset()->FindMaterialSlot(mesh.m_materialSlotStableId);
|
||||
if (AZ::StringFunc::Contains(slot.m_displayName.GetCStr(), labelFilter, true))
|
||||
{
|
||||
AZ::Data::AssetInfo assetInfo;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, mesh.m_material->GetAssetId());
|
||||
if (assetInfo.m_assetId.IsValid() && AZ::StringFunc::Contains(assetInfo.m_relativePath, labelFilter, true))
|
||||
{
|
||||
return MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId());
|
||||
}
|
||||
return MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, mesh.m_materialSlotStableId);
|
||||
}
|
||||
}
|
||||
return MaterialAssignmentId();
|
||||
}
|
||||
|
||||
MaterialAssignmentId FindMaterialAssignmentIdInModel(
|
||||
const Data::Instance<AZ::RPI::Model> model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter)
|
||||
const Data::Instance<AZ::RPI::Model>& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter)
|
||||
{
|
||||
if (model && !labelFilter.empty())
|
||||
{
|
||||
if (lodFilter < model->GetLodCount())
|
||||
{
|
||||
return FindMaterialAssignmentIdInLod(model->GetLods()[lodFilter], lodFilter, labelFilter);
|
||||
return FindMaterialAssignmentIdInLod(model, model->GetLods()[lodFilter], lodFilter, labelFilter);
|
||||
}
|
||||
|
||||
for (size_t lodIndex = 0; lodIndex < model->GetLodCount(); ++lodIndex)
|
||||
{
|
||||
const MaterialAssignmentId result =
|
||||
FindMaterialAssignmentIdInLod(model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter);
|
||||
FindMaterialAssignmentIdInLod(model, model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter);
|
||||
if (!result.IsDefault())
|
||||
{
|
||||
return result;
|
||||
|
||||
@@ -14,14 +14,46 @@ namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
bool MaterialAssignmentId::ConvertVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
|
||||
{
|
||||
if (classElement.GetVersion() < 2)
|
||||
{
|
||||
constexpr AZ::u32 materialAssetIdCrc = AZ_CRC("materialAssetId");
|
||||
|
||||
AZ::Data::AssetId materialAssetId;
|
||||
if (!classElement.GetChildData(materialAssetIdCrc, materialAssetId))
|
||||
{
|
||||
AZ_Error("AZ::Render::MaterialAssignmentId::ConvertVersion", false, "Failed to get AssetId element");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!classElement.RemoveElementByName(materialAssetIdCrc))
|
||||
{
|
||||
AZ_Error("AZ::Render::MaterialAssignmentId::ConvertVersion", false, "Failed to remove deprecated element materialAssetId");
|
||||
// No need to early-return, the object will still load successfully, it will just report more errors about the unrecognized element.
|
||||
}
|
||||
|
||||
if (materialAssetId.IsValid())
|
||||
{
|
||||
classElement.AddElementWithData(context, "materialSlotStableId", materialAssetId.m_subId);
|
||||
}
|
||||
else
|
||||
{
|
||||
classElement.AddElementWithData(context, "materialSlotStableId", RPI::ModelMaterialSlot::InvalidStableId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MaterialAssignmentId::Reflect(ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<MaterialAssignmentId>()
|
||||
->Version(1)
|
||||
->Version(2, &MaterialAssignmentId::ConvertVersion)
|
||||
->Field("lodIndex", &MaterialAssignmentId::m_lodIndex)
|
||||
->Field("materialAssetId", &MaterialAssignmentId::m_materialAssetId)
|
||||
->Field("materialSlotStableId", &MaterialAssignmentId::m_materialSlotStableId)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -33,75 +65,72 @@ namespace AZ
|
||||
->Attribute(AZ::Script::Attributes::Module, "render")
|
||||
->Constructor()
|
||||
->Constructor<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
|
||||
|
||||
@@ -485,20 +485,12 @@ namespace AZ
|
||||
AZ_Error("MeshDataInstance::MeshLoader", false, "Invalid model asset Id.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the model is in the instance database and skip the loading process in this case.
|
||||
// The model asset id is used as instance id to indicate that it is a static and shared.
|
||||
Data::Instance<RPI::Model> model = Data::InstanceDatabase<RPI::Model>::Instance().Find(Data::InstanceId::CreateFromAssetId(m_modelAsset.GetId()));
|
||||
if (model)
|
||||
|
||||
if (!m_modelAsset.IsReady())
|
||||
{
|
||||
// In case the mesh asset requires instancing (e.g. when containing a cloth buffer), the model will always be cloned and there will not be a
|
||||
// model instance with the asset id as instance id as searched above.
|
||||
m_parent->Init(model);
|
||||
m_modelChangedEvent.Signal(AZStd::move(model));
|
||||
return;
|
||||
m_modelAsset.QueueLoad();
|
||||
}
|
||||
|
||||
m_modelAsset.QueueLoad();
|
||||
Data::AssetBus::Handler::BusConnect(modelAsset.GetId());
|
||||
}
|
||||
|
||||
@@ -589,18 +581,6 @@ namespace AZ
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
|
||||
|
||||
auto modelAsset = model->GetModelAsset();
|
||||
for (const auto& modelLodAsset : modelAsset->GetLodAssets())
|
||||
{
|
||||
for (const auto& mesh : modelLodAsset->GetMeshes())
|
||||
{
|
||||
if (mesh.GetMaterialAsset().GetStatus() != Data::AssetData::AssetStatus::Ready)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_model = model;
|
||||
const size_t modelLodCount = m_model->GetLodCount();
|
||||
m_drawPacketListsByLod.resize(modelLodCount);
|
||||
@@ -644,10 +624,12 @@ namespace AZ
|
||||
|
||||
for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex)
|
||||
{
|
||||
Data::Instance<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 +772,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())
|
||||
{
|
||||
|
||||
@@ -640,7 +640,8 @@ namespace AZ
|
||||
Aabb localAabb = lod.m_subMeshProperties[i].m_aabb;
|
||||
modelLodCreator.SetMeshAabb(AZStd::move(localAabb));
|
||||
|
||||
modelLodCreator.SetMeshMaterialAsset(lod.m_subMeshProperties[i].m_material);
|
||||
modelCreator.AddMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot);
|
||||
modelLodCreator.SetMeshMaterialSlot(lod.m_subMeshProperties[i].m_materialSlot.m_stableId);
|
||||
|
||||
modelLodCreator.EndMesh();
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"description": "A simple default base material used primarily for imported model files like FBX.",
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"id": "DiffuseColor",
|
||||
"type": "color",
|
||||
"defaultValue": [ 1.0, 1.0, 1.0 ],
|
||||
"connection": {
|
||||
"type": "shaderInput",
|
||||
"id": "m_diffuseColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "DiffuseMap",
|
||||
"type": "image",
|
||||
"defaultValue": "",
|
||||
"connection": {
|
||||
"type": "shaderInput",
|
||||
"id": "m_diffuseMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "UseDiffuseMap",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "shaderOption",
|
||||
"id": "o_useDiffuseMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SpecularColor",
|
||||
"type": "color",
|
||||
"defaultValue": [ 0.0, 0.0, 0.0 ],
|
||||
"connection": {
|
||||
"type": "shaderInput",
|
||||
"id": "m_specularColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SpecularMap",
|
||||
"type": "image",
|
||||
"defaultValue": "",
|
||||
"connection": {
|
||||
"type": "shaderInput",
|
||||
"id": "m_specularMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "UseSpecularMap",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "shaderOption",
|
||||
"id": "o_useSpecularMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "NormalMap",
|
||||
"type": "image",
|
||||
"defaultValue": "",
|
||||
"connection": {
|
||||
"type": "shaderInput",
|
||||
"id": "m_normalMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "UseNormalMap",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "shaderOption",
|
||||
"id": "o_useNormalMap"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"shaders": [
|
||||
{
|
||||
"file": "DefaultMaterial.shader"
|
||||
},
|
||||
{
|
||||
"file": "DefaultMaterial_DepthPass.shader"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultObjectSrg.azsli>
|
||||
#include <Atom/RPI/TangentSpace.azsli>
|
||||
|
||||
ShaderResourceGroup MaterialSrg : SRG_PerMaterial
|
||||
{
|
||||
float4 m_diffuseColor;
|
||||
float3 m_specularColor;
|
||||
|
||||
Texture2D m_diffuseMap;
|
||||
Texture2D m_normalMap;
|
||||
Texture2D m_specularMap;
|
||||
|
||||
Sampler m_sampler
|
||||
{
|
||||
MaxAnisotropy = 16;
|
||||
AddressU = Wrap;
|
||||
AddressV = Wrap;
|
||||
AddressW = Wrap;
|
||||
};
|
||||
}
|
||||
|
||||
option bool o_useDiffuseMap = false;
|
||||
option bool o_useSpecularMap = false;
|
||||
option bool o_useNormalMap = false;
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
float3 m_normal : NORMAL;
|
||||
float4 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float2 m_uv : UV0;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float2 m_uv : UV0;
|
||||
float3 m_positionToCamera : VIEW;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput input)
|
||||
{
|
||||
const float4x4 objectToWorldMatrix = ObjectSrg::GetWorldMatrix();
|
||||
|
||||
VertexOutput output;
|
||||
float3 worldPosition = mul(objectToWorldMatrix, float4(input.m_position,1)).xyz;
|
||||
output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
|
||||
output.m_uv = input.m_uv;
|
||||
|
||||
output.m_positionToCamera = ViewSrg::m_worldPosition - worldPosition;
|
||||
|
||||
float3x3 objectToWorldMatrixIT = ObjectSrg::GetWorldMatrixInverseTranspose();
|
||||
|
||||
ConstructTBN(input.m_normal, input.m_tangent, input.m_bitangent, objectToWorldMatrix, objectToWorldMatrixIT, output.m_normal, output.m_tangent, output.m_bitangent);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
struct PixelOutput
|
||||
{
|
||||
float4 m_color : SV_Target0;
|
||||
};
|
||||
|
||||
PixelOutput MainPS(VertexOutput input)
|
||||
{
|
||||
PixelOutput output;
|
||||
|
||||
// Very rough placeholder lighting
|
||||
static const float3 lightDir = normalize(float3(1,1,1));
|
||||
|
||||
float4 baseColor = MaterialSrg::m_diffuseColor;
|
||||
if (o_useDiffuseMap)
|
||||
{
|
||||
baseColor *= MaterialSrg::m_diffuseMap.Sample(MaterialSrg::m_sampler, input.m_uv);
|
||||
}
|
||||
|
||||
float3 specular = MaterialSrg::m_specularColor;
|
||||
if (o_useSpecularMap)
|
||||
{
|
||||
specular *= MaterialSrg::m_specularMap.Sample(MaterialSrg::m_sampler, input.m_uv).rgb;
|
||||
}
|
||||
|
||||
float3 normal;
|
||||
if (o_useNormalMap)
|
||||
{
|
||||
float4 sampledValue = MaterialSrg::m_normalMap.Sample(MaterialSrg::m_sampler, input.m_uv);
|
||||
normal = GetWorldSpaceNormal(sampledValue.xy, input.m_normal, input.m_tangent, input.m_bitangent);
|
||||
}
|
||||
else
|
||||
{
|
||||
normal = normalize(input.m_normal);
|
||||
}
|
||||
|
||||
float3 viewDir = normalize(input.m_positionToCamera);
|
||||
float3 H = normalize(lightDir + viewDir);
|
||||
float NdotH = max(0.001, dot(normal, H));
|
||||
float NdotL = saturate(dot(normal, lightDir));
|
||||
|
||||
float3 diffuse = NdotL * baseColor.xyz;
|
||||
|
||||
specular = pow(NdotH, 5.0) * specular;
|
||||
|
||||
// Combined
|
||||
float3 result = diffuse + specular + float3(0.1, 0.1, 0.1) * baseColor.xyz;
|
||||
|
||||
output.m_color = float4(result.xyz, baseColor.a);
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"Source" : "DefaultMaterial.azsl",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "Equal" }
|
||||
},
|
||||
|
||||
"DrawList" : "forward",
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
},
|
||||
{
|
||||
"name": "MainPS",
|
||||
"type": "Fragment"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultObjectSrg.azsli>
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput input)
|
||||
{
|
||||
const float4x4 objectToWorldMatrix = ObjectSrg::GetWorldMatrix();
|
||||
|
||||
VertexOutput output;
|
||||
float3 worldPosition = mul(objectToWorldMatrix, float4(input.m_position,1)).xyz;
|
||||
output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
return output;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"Source" : "DefaultMaterial_DepthPass.azsl",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
|
||||
},
|
||||
|
||||
"DrawList" : "depth",
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,6 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Materials/Default.materialtype
|
||||
Materials/DefaultMaterial.azsl
|
||||
Materials/DefaultMaterial.shader
|
||||
Materials/DefaultMaterial_DepthPass.azsl
|
||||
Materials/DefaultMaterial_DepthPass.shader
|
||||
Shader/DecomposeMsImage.azsl
|
||||
Shader/DecomposeMsImage.shader
|
||||
Shader/ImagePreview.azsl
|
||||
|
||||
@@ -90,8 +90,8 @@ namespace AZ
|
||||
private:
|
||||
Model() = default;
|
||||
|
||||
static Data::Instance<Model> CreateInternal(ModelAsset& modelAsset);
|
||||
RHI::ResultCode Init(ModelAsset& modelAsset);
|
||||
static Data::Instance<Model> CreateInternal(const Data::Asset<ModelAsset>& modelAsset);
|
||||
RHI::ResultCode Init(const Data::Asset<ModelAsset>& modelAsset);
|
||||
|
||||
AZStd::fixed_vector<Data::Instance<ModelLod>, ModelLodAsset::LodCountMax> m_lods;
|
||||
Data::Asset<ModelAsset> m_modelAsset;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <Atom/RHI.Reflect/Limits.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
|
||||
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
|
||||
|
||||
#include <AtomCore/std/containers/array_view.h>
|
||||
#include <AtomCore/std/containers/vector_set.h>
|
||||
@@ -72,6 +73,8 @@ namespace AZ
|
||||
RHI::IndexBufferView m_indexBufferView;
|
||||
|
||||
StreamInfoList m_streamInfo;
|
||||
|
||||
ModelMaterialSlot::StableId m_materialSlotStableId = ModelMaterialSlot::InvalidStableId;
|
||||
|
||||
//! The default material assigned to the mesh by the asset.
|
||||
Data::Instance<Material> m_material;
|
||||
@@ -82,7 +85,7 @@ namespace AZ
|
||||
AZ_INSTANCE_DATA(ModelLod, "{3C796FC9-2067-4E0F-A660-269F8254D1D5}");
|
||||
AZ_CLASS_ALLOCATOR(ModelLod, AZ::SystemAllocator, 0);
|
||||
|
||||
static Data::Instance<ModelLod> FindOrCreate(const Data::Asset<ModelLodAsset>& lodAsset);
|
||||
static Data::Instance<ModelLod> FindOrCreate(const Data::Asset<ModelLodAsset>& lodAsset, const Data::Asset<ModelAsset>& modelAsset);
|
||||
|
||||
~ModelLod() = default;
|
||||
|
||||
@@ -122,8 +125,8 @@ namespace AZ
|
||||
private:
|
||||
ModelLod() = default;
|
||||
|
||||
static Data::Instance<ModelLod> CreateInternal(ModelLodAsset& lodAsset);
|
||||
RHI::ResultCode Init(ModelLodAsset& lodAsset);
|
||||
static Data::Instance<ModelLod> CreateInternal(const Data::Asset<ModelLodAsset>& lodAsset, const AZStd::any* modelAssetAny);
|
||||
RHI::ResultCode Init(const Data::Asset<ModelLodAsset>& lodAsset, const Data::Asset<ModelAsset>& modelAsset);
|
||||
|
||||
bool SetMeshInstanceData(
|
||||
const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo,
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace AZ
|
||||
|
||||
//! Returns the model-space axis aligned bounding box
|
||||
const AZ::Aabb& GetAabb() const;
|
||||
|
||||
//! Returns the list of all ModelMaterialSlot's for the model, across all LODs.
|
||||
const ModelMaterialSlotMap& GetMaterialSlots() const;
|
||||
|
||||
//! Find a material slot with the given stableId, or returns an invalid slot if it isn't found.
|
||||
const ModelMaterialSlot& FindMaterialSlot(uint32_t stableId) const;
|
||||
|
||||
//! Returns the number of Lods in the model
|
||||
size_t GetLodCount() const;
|
||||
@@ -97,6 +103,13 @@ namespace AZ
|
||||
volatile mutable bool m_isKdTreeCalculationRunning = false;
|
||||
mutable AZStd::mutex m_kdTreeLock;
|
||||
mutable AZStd::optional<AZStd::size_t> m_modelTriangleCount;
|
||||
|
||||
// Lists all of the material slots that are used by this LOD.
|
||||
// Note the same slot can appear in multiple LODs in the model, so that LODs don't have to refer back to the model asset.
|
||||
ModelMaterialSlotMap m_materialSlots;
|
||||
|
||||
// A default ModelMaterialSlot to be returned upon error conditions.
|
||||
ModelMaterialSlot m_fallbackSlot;
|
||||
|
||||
AZStd::size_t CalculateTriangleCount() const;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace AZ
|
||||
|
||||
//! Assigns a name to the model
|
||||
void SetName(AZStd::string_view name);
|
||||
|
||||
//! Adds a new material slot to the asset.
|
||||
//! If a slot with the same stable ID already exists, it will be replaced.
|
||||
void AddMaterialSlot(const ModelMaterialSlot& materialSlot);
|
||||
|
||||
//! Adds a Lod to the model.
|
||||
void AddLodAsset(Data::Asset<ModelLodAsset>&& lodAsset);
|
||||
|
||||
@@ -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 ID of the material slot used by this mesh.
|
||||
//! This maps into the ModelAsset's material slot list.
|
||||
ModelMaterialSlot::StableId GetMaterialSlotId() const;
|
||||
|
||||
//! Returns the name of this mesh
|
||||
const AZ::Name& GetName() const;
|
||||
@@ -124,7 +126,9 @@ namespace AZ
|
||||
AZ::Name m_name;
|
||||
AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
|
||||
|
||||
Data::Asset<MaterialAsset> m_materialAsset{ Data::AssetLoadBehavior::PreLoad };
|
||||
// Identifies the material slot that is used by this mesh.
|
||||
// References material slot in the ModelAsset that owns this mesh; see ModelAsset::FindMaterialSlot().
|
||||
ModelMaterialSlot::StableId m_materialSlotId = ModelMaterialSlot::InvalidStableId;
|
||||
|
||||
// Both the buffer in m_indexBufferAssetView and the buffers in m_streamBufferInfo
|
||||
// may point to either unique buffers for the mesh or to consolidated
|
||||
@@ -147,7 +151,7 @@ namespace AZ
|
||||
private:
|
||||
AZStd::vector<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.
|
||||
|
||||
@@ -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,9 @@ namespace AZ
|
||||
//! Begin and BeginMesh must be called first.
|
||||
void SetMeshAabb(AZ::Aabb&& aabb);
|
||||
|
||||
//! Sets the material asset for the current SubMesh.
|
||||
//! Sets the ID of the model's material slot that this mesh uses.
|
||||
//! Begin and BeginMesh must be called first
|
||||
void SetMeshMaterialAsset(const Data::Asset<MaterialAsset>& materialAsset);
|
||||
void SetMeshMaterialSlot(ModelMaterialSlot::StableId id);
|
||||
|
||||
//! Sets the given BufferAssetView to the current SubMesh as the index buffer.
|
||||
//! Begin and BeginMesh must be called first
|
||||
|
||||
@@ -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;
|
||||
|
||||
//! 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
|
||||
@@ -91,7 +91,7 @@ namespace AZ
|
||||
if (auto* serialize = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<MaterialAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>()
|
||||
->Version(14); // [ATOM-13410]
|
||||
->Version(16); // Optional material conversion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +367,9 @@ namespace AZ
|
||||
|
||||
MorphTargetMetaAssetCreator morphTargetMetaCreator;
|
||||
morphTargetMetaCreator.Begin(MorphTargetMetaAsset::ConstructAssetId(modelAssetId, modelAssetName));
|
||||
|
||||
ModelAssetCreator modelAssetCreator;
|
||||
modelAssetCreator.Begin(modelAssetId);
|
||||
|
||||
uint32_t lodIndex = 0;
|
||||
for (const SourceMeshContentList& sourceMeshContentList : sourceMeshContentListsByLod)
|
||||
@@ -429,7 +432,7 @@ namespace AZ
|
||||
|
||||
for (const ProductMeshView& meshView : lodMeshViews)
|
||||
{
|
||||
if (!CreateMesh(meshView, indexBuffer, streamBuffers, lodAssetCreator, context.m_materialsByUid))
|
||||
if (!CreateMesh(meshView, indexBuffer, streamBuffers, modelAssetCreator, lodAssetCreator, context.m_materialsByUid))
|
||||
{
|
||||
return AZ::SceneAPI::Events::ProcessingResult::Failure;
|
||||
}
|
||||
@@ -469,10 +472,6 @@ namespace AZ
|
||||
}
|
||||
sourceMeshContentListsByLod.clear();
|
||||
|
||||
// Build the final asset structure
|
||||
ModelAssetCreator modelAssetCreator;
|
||||
modelAssetCreator.Begin(modelAssetId);
|
||||
|
||||
// Finalize all LOD assets
|
||||
for (auto& lodAsset : lodAssets)
|
||||
{
|
||||
@@ -1796,6 +1795,7 @@ namespace AZ
|
||||
const ProductMeshView& meshView,
|
||||
const BufferAssetView& lodIndexBuffer,
|
||||
const AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& lodStreamBuffers,
|
||||
ModelAssetCreator& modelAssetCreator,
|
||||
ModelLodAssetCreator& lodAssetCreator,
|
||||
const MaterialAssetsByUid& materialAssetsByUid)
|
||||
{
|
||||
@@ -1806,8 +1806,13 @@ namespace AZ
|
||||
auto iter = materialAssetsByUid.find(meshView.m_materialUid);
|
||||
if (iter != materialAssetsByUid.end())
|
||||
{
|
||||
const Data::Asset<MaterialAsset>& materialAsset = iter->second.m_asset;
|
||||
lodAssetCreator.SetMeshMaterialAsset(materialAsset);
|
||||
ModelMaterialSlot materialSlot;
|
||||
materialSlot.m_stableId = meshView.m_materialUid;
|
||||
materialSlot.m_displayName = iter->second.m_name;
|
||||
materialSlot.m_defaultMaterialAsset = iter->second.m_asset;
|
||||
|
||||
modelAssetCreator.AddMaterialSlot(materialSlot);
|
||||
lodAssetCreator.SetMeshMaterialSlot(materialSlot.m_stableId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace AZ
|
||||
using SkinData = AZ::SceneAPI::DataTypes::ISkinWeightData;
|
||||
|
||||
class Stream;
|
||||
class ModelAssetCreator;
|
||||
class ModelLodAssetCreator;
|
||||
class BufferAssetCreator;
|
||||
struct PackedCompressedMorphTargetDelta;
|
||||
@@ -294,6 +295,7 @@ namespace AZ
|
||||
const ProductMeshView& meshView,
|
||||
const BufferAssetView& lodIndexBuffer,
|
||||
const AZStd::vector<ModelLodAsset::Mesh::StreamBufferInfo>& lodStreamBuffers,
|
||||
ModelAssetCreator& modelAssetCreator,
|
||||
ModelLodAssetCreator& lodAssetCreator,
|
||||
const MaterialAssetsByUid& materialAssetsByUid);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace AZ
|
||||
return m_lods;
|
||||
}
|
||||
|
||||
Data::Instance<Model> Model::CreateInternal(ModelAsset& modelAsset)
|
||||
Data::Instance<Model> Model::CreateInternal(const Data::Asset<ModelAsset>& modelAsset)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
|
||||
Data::Instance<Model> model = aznew Model();
|
||||
@@ -54,15 +54,15 @@ namespace AZ
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::ResultCode Model::Init(ModelAsset& modelAsset)
|
||||
RHI::ResultCode Model::Init(const Data::Asset<ModelAsset>& modelAsset)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
|
||||
|
||||
m_lods.resize(modelAsset.GetLodAssets().size());
|
||||
m_lods.resize(modelAsset->GetLodAssets().size());
|
||||
|
||||
for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex)
|
||||
{
|
||||
const Data::Asset<ModelLodAsset>& lodAsset = modelAsset.GetLodAssets()[lodIndex];
|
||||
const Data::Asset<ModelLodAsset>& lodAsset = modelAsset->GetLodAssets()[lodIndex];
|
||||
|
||||
if (!lodAsset)
|
||||
{
|
||||
@@ -70,7 +70,7 @@ namespace AZ
|
||||
return RHI::ResultCode::Fail;
|
||||
}
|
||||
|
||||
Data::Instance<ModelLod> lodInstance = ModelLod::FindOrCreate(lodAsset);
|
||||
Data::Instance<ModelLod> lodInstance = ModelLod::FindOrCreate(lodAsset, modelAsset);
|
||||
if (lodInstance == nullptr)
|
||||
{
|
||||
return RHI::ResultCode::Fail;
|
||||
@@ -98,7 +98,7 @@ namespace AZ
|
||||
m_lods[lodIndex] = AZStd::move(lodInstance);
|
||||
}
|
||||
|
||||
m_modelAsset = { &modelAsset, AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
m_modelAsset = modelAsset;
|
||||
m_isUploadPending = true;
|
||||
return RHI::ResultCode::Success;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,14 @@ namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
Data::Instance<ModelLod> ModelLod::FindOrCreate(const Data::Asset<ModelLodAsset>& lodAsset)
|
||||
Data::Instance<ModelLod> ModelLod::FindOrCreate(const Data::Asset<ModelLodAsset>& lodAsset, const Data::Asset<ModelAsset>& modelAsset)
|
||||
{
|
||||
AZStd::any modelAssetAny{&modelAsset};
|
||||
|
||||
return Data::InstanceDatabase<ModelLod>::Instance().FindOrCreate(
|
||||
Data::InstanceId::CreateFromAssetId(lodAsset.GetId()),
|
||||
lodAsset);
|
||||
lodAsset,
|
||||
&modelAssetAny);
|
||||
}
|
||||
|
||||
AZStd::array_view<ModelLod::Mesh> ModelLod::GetMeshes() const
|
||||
@@ -31,10 +34,13 @@ namespace AZ
|
||||
return m_meshes;
|
||||
}
|
||||
|
||||
Data::Instance<ModelLod> ModelLod::CreateInternal(ModelLodAsset& lodAsset)
|
||||
Data::Instance<ModelLod> ModelLod::CreateInternal(const Data::Asset<ModelLodAsset>& lodAsset, const AZStd::any* modelAssetAny)
|
||||
{
|
||||
AZ_Assert(modelAssetAny != nullptr, "Invalid model asset param");
|
||||
auto modelAsset = AZStd::any_cast<Data::Asset<ModelAsset>*>(*modelAssetAny);
|
||||
|
||||
Data::Instance<ModelLod> lod = aznew ModelLod();
|
||||
const RHI::ResultCode resultCode = lod->Init(lodAsset);
|
||||
const RHI::ResultCode resultCode = lod->Init(lodAsset, *modelAsset);
|
||||
|
||||
if (resultCode == RHI::ResultCode::Success)
|
||||
{
|
||||
@@ -44,11 +50,11 @@ namespace AZ
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::ResultCode ModelLod::Init(ModelLodAsset& lodAsset)
|
||||
RHI::ResultCode ModelLod::Init(const Data::Asset<ModelLodAsset>& lodAsset, const Data::Asset<ModelAsset>& modelAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
|
||||
for (const ModelLodAsset::Mesh& mesh : lodAsset.GetMeshes())
|
||||
for (const ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes())
|
||||
{
|
||||
Mesh meshInstance;
|
||||
|
||||
@@ -100,10 +106,13 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
auto& materialAsset = mesh.GetMaterialAsset();
|
||||
if (materialAsset.IsReady())
|
||||
const ModelMaterialSlot& materialSlot = modelAsset->FindMaterialSlot(mesh.GetMaterialSlotId());
|
||||
|
||||
meshInstance.m_materialSlotStableId = materialSlot.m_stableId;
|
||||
|
||||
if (materialSlot.m_defaultMaterialAsset.IsReady())
|
||||
{
|
||||
meshInstance.m_material = Material::FindOrCreate(materialAsset);
|
||||
meshInstance.m_material = Material::FindOrCreate(materialSlot.m_defaultMaterialAsset);
|
||||
}
|
||||
|
||||
m_meshes.emplace_back(AZStd::move(meshInstance));
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace AZ
|
||||
{
|
||||
ModelLodAsset::Reflect(context);
|
||||
ModelAsset::Reflect(context);
|
||||
ModelMaterialSlot::Reflect(context);
|
||||
MorphTargetMetaAsset::Reflect(context);
|
||||
SkinMetaAsset::Reflect(context);
|
||||
}
|
||||
@@ -40,9 +41,9 @@ namespace AZ
|
||||
{
|
||||
//Create Lod Database
|
||||
AZ::Data::InstanceHandler<ModelLod> lodInstanceHandler;
|
||||
lodInstanceHandler.m_createFunction = [](Data::AssetData* modelLodAsset)
|
||||
lodInstanceHandler.m_createFunctionWithParam = [](Data::AssetData* modelLodAsset, const AZStd::any* modelAsset)
|
||||
{
|
||||
return ModelLod::CreateInternal(*(azrtti_cast<ModelLodAsset*>(modelLodAsset)));
|
||||
return ModelLod::CreateInternal(Data::Asset<ModelLodAsset>{modelLodAsset, AZ::Data::AssetLoadBehavior::PreLoad}, modelAsset);
|
||||
};
|
||||
Data::InstanceDatabase<ModelLod>::Create(azrtti_typeid<ModelLodAsset>(), lodInstanceHandler);
|
||||
|
||||
@@ -50,7 +51,7 @@ namespace AZ
|
||||
AZ::Data::InstanceHandler<Model> modelInstanceHandler;
|
||||
modelInstanceHandler.m_createFunction = [](Data::AssetData* modelAsset)
|
||||
{
|
||||
return Model::CreateInternal(*(azrtti_cast<ModelAsset*>(modelAsset)));
|
||||
return Model::CreateInternal(Data::Asset<ModelAsset>{modelAsset, AZ::Data::AssetLoadBehavior::PreLoad});
|
||||
};
|
||||
Data::InstanceDatabase<Model>::Create(azrtti_typeid<ModelAsset>(), modelInstanceHandler);
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@ namespace AZ
|
||||
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<ModelAsset, Data::AssetData>()
|
||||
->Version(0)
|
||||
->Version(1)
|
||||
->Field("Name", &ModelAsset::m_name)
|
||||
->Field("Aabb", &ModelAsset::m_aabb)
|
||||
->Field("MaterialSlots", &ModelAsset::m_materialSlots)
|
||||
->Field("LodAssets", &ModelAsset::m_lodAssets)
|
||||
;
|
||||
}
|
||||
@@ -56,6 +57,25 @@ namespace AZ
|
||||
{
|
||||
return m_aabb;
|
||||
}
|
||||
|
||||
const ModelMaterialSlotMap& ModelAsset::GetMaterialSlots() const
|
||||
{
|
||||
return m_materialSlots;
|
||||
}
|
||||
|
||||
const ModelMaterialSlot& ModelAsset::FindMaterialSlot(uint32_t stableId) const
|
||||
{
|
||||
auto iter = m_materialSlots.find(stableId);
|
||||
|
||||
if (iter == m_materialSlots.end())
|
||||
{
|
||||
return m_fallbackSlot;
|
||||
}
|
||||
else
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
size_t ModelAsset::GetLodCount() const
|
||||
{
|
||||
|
||||
@@ -29,6 +29,33 @@ namespace AZ
|
||||
m_asset->m_name = name;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelAssetCreator::AddMaterialSlot(const ModelMaterialSlot& materialSlot)
|
||||
{
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
auto iter = m_asset->m_materialSlots.find(materialSlot.m_stableId);
|
||||
|
||||
if (iter == m_asset->m_materialSlots.end())
|
||||
{
|
||||
m_asset->m_materialSlots[materialSlot.m_stableId] = materialSlot;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (materialSlot.m_displayName != iter->second.m_displayName)
|
||||
{
|
||||
ReportWarning("Material slot %u was already added with a different name.", materialSlot.m_stableId);
|
||||
}
|
||||
|
||||
if (materialSlot.m_defaultMaterialAsset != iter->second.m_defaultMaterialAsset)
|
||||
{
|
||||
ReportWarning("Material slot %u was already added with a different default MaterialAsset.", materialSlot.m_stableId);
|
||||
}
|
||||
|
||||
iter->second = materialSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelAssetCreator::AddLodAsset(Data::Asset<ModelLodAsset>&& lodAsset)
|
||||
{
|
||||
|
||||
@@ -31,16 +31,16 @@ namespace AZ
|
||||
|
||||
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("MaterialSlotId", &ModelLodAsset::Mesh::m_materialSlotId)
|
||||
->Field("IndexBufferAssetView", &ModelLodAsset::Mesh::m_indexBufferAssetView)
|
||||
->Field("StreamBufferInfo", &ModelLodAsset::Mesh::m_streamBufferInfo)
|
||||
;
|
||||
@@ -75,9 +75,9 @@ namespace AZ
|
||||
return m_indexBufferAssetView.GetBufferViewDescriptor().m_elementCount;
|
||||
}
|
||||
|
||||
const Data::Asset <MaterialAsset>& ModelLodAsset::Mesh::GetMaterialAsset() const
|
||||
ModelMaterialSlot::StableId ModelLodAsset::Mesh::GetMaterialSlotId() const
|
||||
{
|
||||
return m_materialAsset;
|
||||
return m_materialSlotId;
|
||||
}
|
||||
|
||||
const AZ::Name& ModelLodAsset::Mesh::GetName() const
|
||||
@@ -118,7 +118,7 @@ namespace AZ
|
||||
{
|
||||
return m_aabb;
|
||||
}
|
||||
|
||||
|
||||
const BufferAssetView* ModelLodAsset::Mesh::GetSemanticBufferAssetView(const AZ::Name& semantic) const
|
||||
{
|
||||
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = GetStreamBufferInfoList();
|
||||
|
||||
@@ -60,13 +60,15 @@ namespace AZ
|
||||
m_currentMesh.m_aabb = AZStd::move(aabb);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelLodAssetCreator::SetMeshMaterialAsset(const Data::Asset<MaterialAsset>& materialAsset)
|
||||
|
||||
void ModelLodAssetCreator::SetMeshMaterialSlot(ModelMaterialSlot::StableId id)
|
||||
{
|
||||
if (ValidateIsMeshReady())
|
||||
if (!ValidateIsMeshReady())
|
||||
{
|
||||
m_currentMesh.m_materialAsset = materialAsset;
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentMesh.m_materialSlotId = id;
|
||||
}
|
||||
|
||||
void ModelLodAssetCreator::SetMeshIndexBuffer(const BufferAssetView& bufferAssetView)
|
||||
@@ -288,7 +290,8 @@ namespace AZ
|
||||
creator.SetMeshName(sourceMesh.GetName());
|
||||
AZ::Aabb aabb = sourceMesh.GetAabb();
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(sourceMesh.GetMaterialAsset());
|
||||
|
||||
creator.SetMeshMaterialSlot(sourceMesh.GetMaterialSlotId());
|
||||
|
||||
// Mesh index buffer view
|
||||
const BufferAssetView& sourceIndexBufferView = sourceMesh.GetIndexBufferAssetView();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/RPI.Reflect/Model/ModelMaterialSlot.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
// Normally this would be defined in the header file and substituted by the compiler, but for
|
||||
// some reason clang doesn't accept it.
|
||||
const ModelMaterialSlot::StableId ModelMaterialSlot::InvalidStableId = -1;
|
||||
|
||||
void ModelMaterialSlot::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<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
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <AzCore/std/limits.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Math/Sfmt.h>
|
||||
|
||||
#include <AZTestShared/Math/MathTestHelpers.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
@@ -91,7 +92,7 @@ namespace UnitTest
|
||||
AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
|
||||
uint32_t m_indexCount = 0;
|
||||
uint32_t m_vertexCount = 0;
|
||||
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_material;
|
||||
AZ::RPI::ModelMaterialSlot::StableId m_materialSlotId = AZ::RPI::ModelMaterialSlot::InvalidStableId;
|
||||
};
|
||||
|
||||
struct ExpectedLod
|
||||
@@ -106,6 +107,18 @@ namespace UnitTest
|
||||
AZStd::vector<ExpectedLod> m_lods;
|
||||
};
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
RPITestFixture::SetUp();
|
||||
|
||||
auto assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0);
|
||||
auto typeId = AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid();
|
||||
m_materialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(assetId, typeId, "");
|
||||
|
||||
// Some tests attempt to serialize-in the model asset, which should not attempt to actually load this dummy asset reference.
|
||||
m_materialAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehaviorNamespace::NoLoad);
|
||||
}
|
||||
|
||||
AZ::RHI::ShaderSemantic GetPositionSemantic() const
|
||||
{
|
||||
return AZ::RHI::ShaderSemantic(AZ::Name("POSITION"));
|
||||
@@ -136,6 +149,7 @@ namespace UnitTest
|
||||
return true;
|
||||
}
|
||||
|
||||
//! This function assumes the model has "sharedMeshCount + separateMeshCount" unique material slots, with incremental IDs starting at 0.
|
||||
AZ::Data::Asset<AZ::RPI::ModelLodAsset> BuildTestLod(const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedLod& expectedLod)
|
||||
{
|
||||
using namespace AZ;
|
||||
@@ -148,6 +162,8 @@ namespace UnitTest
|
||||
const uint32_t indexCount = 36;
|
||||
const uint32_t vertexCount = 36;
|
||||
|
||||
RPI::ModelMaterialSlot::StableId materialSlotId = 0;
|
||||
|
||||
if(sharedMeshCount > 0)
|
||||
{
|
||||
const uint32_t sharedIndexCount = indexCount * sharedMeshCount;
|
||||
@@ -164,7 +180,7 @@ namespace UnitTest
|
||||
ExpectedMesh expectedMesh;
|
||||
expectedMesh.m_indexCount = indexCount;
|
||||
expectedMesh.m_vertexCount = vertexCount;
|
||||
expectedMesh.m_material = m_materialAsset;
|
||||
expectedMesh.m_materialSlotId = i;
|
||||
|
||||
RHI::BufferViewDescriptor indexBufferViewDescriptor =
|
||||
RHI::BufferViewDescriptor::CreateStructured(i * indexCount, indexCount, sizeof(uint32_t));
|
||||
@@ -180,7 +196,7 @@ namespace UnitTest
|
||||
creator.BeginMesh();
|
||||
Aabb aabb = expectedMesh.m_aabb;
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(materialSlotId++);
|
||||
creator.SetMeshIndexBuffer({ sharedIndexBuffer, indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { sharedPositionBuffer, vertexBufferViewDescriptor });
|
||||
creator.EndMesh();
|
||||
@@ -195,7 +211,7 @@ namespace UnitTest
|
||||
ExpectedMesh expectedMesh;
|
||||
expectedMesh.m_indexCount = indexCount;
|
||||
expectedMesh.m_vertexCount = vertexCount;
|
||||
expectedMesh.m_material = m_materialAsset;
|
||||
expectedMesh.m_materialSlotId = sharedMeshCount + i;
|
||||
|
||||
RHI::BufferViewDescriptor indexBufferViewDescriptor =
|
||||
RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
|
||||
@@ -213,7 +229,7 @@ namespace UnitTest
|
||||
creator.BeginMesh();
|
||||
Aabb aabb = expectedMesh.m_aabb;
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(materialSlotId++);
|
||||
creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { positonBuffer, positionBufferViewDescriptor });
|
||||
|
||||
@@ -239,6 +255,15 @@ namespace UnitTest
|
||||
|
||||
creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
|
||||
creator.SetName("TestModel");
|
||||
|
||||
for (RPI::ModelMaterialSlot::StableId materialSlotId = 0; materialSlotId < sharedMeshCount + separateMeshCount; ++materialSlotId)
|
||||
{
|
||||
RPI::ModelMaterialSlot slot;
|
||||
slot.m_defaultMaterialAsset = m_materialAsset;
|
||||
slot.m_displayName = AZStd::string::format("Slot%d", materialSlotId);
|
||||
slot.m_stableId = materialSlotId;
|
||||
creator.AddMaterialSlot(slot);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < lodCount; ++i)
|
||||
{
|
||||
@@ -263,7 +288,7 @@ namespace UnitTest
|
||||
EXPECT_TRUE(mesh.GetAabb() == expectedMesh.m_aabb);
|
||||
EXPECT_TRUE(mesh.GetIndexCount() == expectedMesh.m_indexCount);
|
||||
EXPECT_TRUE(mesh.GetVertexCount() == expectedMesh.m_vertexCount);
|
||||
EXPECT_TRUE(mesh.GetMaterialAsset() == expectedMesh.m_material);
|
||||
EXPECT_TRUE(mesh.GetMaterialSlotId() == expectedMesh.m_materialSlotId);
|
||||
}
|
||||
|
||||
void ValidateLodAsset(const AZ::RPI::ModelLodAsset* lodAsset, const ExpectedLod& expectedLod)
|
||||
@@ -299,9 +324,7 @@ namespace UnitTest
|
||||
}
|
||||
|
||||
const uint32_t m_manyMesh = 100; // Not too much to hold up the tests but enough to stress them
|
||||
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset =
|
||||
AZ::Data::Asset<AZ::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0),
|
||||
AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "");
|
||||
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
|
||||
|
||||
};
|
||||
|
||||
@@ -687,11 +710,11 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if we try to set the material id on a mesh
|
||||
// Tests that if we try to set the material slot on a mesh
|
||||
// without calling Begin or BeginMesh that it fails
|
||||
// as expected. Also tests the case that Begin *is*
|
||||
// called but BeginMesh is not.
|
||||
TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh)
|
||||
TEST_F(ModelTests, SetMaterialSlotNoBeginNoBeginMesh)
|
||||
{
|
||||
using namespace AZ;
|
||||
|
||||
@@ -699,7 +722,7 @@ namespace UnitTest
|
||||
|
||||
{
|
||||
ErrorMessageFinder messageFinder("Begin() was not called");
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
}
|
||||
|
||||
creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
|
||||
@@ -707,7 +730,7 @@ namespace UnitTest
|
||||
//This should still fail even if we call Begin but not BeginMesh
|
||||
{
|
||||
ErrorMessageFinder messageFinder("BeginMesh() was not called");
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +850,7 @@ namespace UnitTest
|
||||
|
||||
creator.BeginMesh();
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
|
||||
|
||||
@@ -842,7 +865,7 @@ namespace UnitTest
|
||||
ErrorMessageFinder messageFinder("BeginMesh() was not called", 5);
|
||||
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
|
||||
|
||||
@@ -885,7 +908,7 @@ namespace UnitTest
|
||||
|
||||
creator.BeginMesh();
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
|
||||
|
||||
@@ -907,7 +930,7 @@ namespace UnitTest
|
||||
|
||||
creator.BeginMesh();
|
||||
creator.SetMeshAabb(AZStd::move(aabb));
|
||||
creator.SetMeshMaterialAsset(m_materialAsset);
|
||||
creator.SetMeshMaterialSlot(0);
|
||||
creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
|
||||
creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
|
||||
|
||||
@@ -1019,10 +1042,7 @@ namespace UnitTest
|
||||
|
||||
lodCreator.BeginMesh();
|
||||
lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({-1.0f, -1.0f, -0.5f}, {1.0f, 1.0f, 0.5f}));
|
||||
lodCreator.SetMeshMaterialAsset(
|
||||
AZ::Data::Asset<AZ::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0),
|
||||
AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "")
|
||||
);
|
||||
lodCreator.SetMeshMaterialSlot(AZ::Sfmt::GetInstance().Rand32());
|
||||
|
||||
{
|
||||
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t));
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
@@ -74,6 +74,10 @@ namespace AZ
|
||||
//! Get material assignment id matching lod and label substring
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
|
||||
|
||||
//! Returns the list of all ModelMaterialSlot's for the model, across all LODs.
|
||||
virtual RPI::ModelMaterialSlotMap GetModelMaterialSlots() const = 0;
|
||||
|
||||
virtual MaterialAssignmentMap GetMaterialAssignments() const = 0;
|
||||
virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0;
|
||||
};
|
||||
|
||||
+54
-70
@@ -17,6 +17,7 @@
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
|
||||
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
|
||||
#include <QMenu>
|
||||
@@ -44,64 +45,15 @@ namespace AZ
|
||||
|
||||
if (classElement.GetVersion() < 3)
|
||||
{
|
||||
// The default material was changed from an asset to an EditorMaterialComponentSlot and old data must be converted
|
||||
constexpr AZ::u32 defaultMaterialAssetDataCrc = AZ_CRC("defaultMaterialAsset", 0x736fc071);
|
||||
|
||||
Data::Asset<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)
|
||||
{
|
||||
classElement.AddElementWithData(context, "materialSlotsByLodEnabled", true);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,7 +190,7 @@ namespace AZ
|
||||
for (auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.IsAssetOnly())
|
||||
if (materialSlot->m_id.IsSlotIdOnly())
|
||||
{
|
||||
materialSlot->Clear();
|
||||
}
|
||||
@@ -251,7 +203,7 @@ namespace AZ
|
||||
for (auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.IsLodAndAsset())
|
||||
if (materialSlot->m_id.IsLodAndSlotId())
|
||||
{
|
||||
materialSlot->Clear();
|
||||
}
|
||||
@@ -318,7 +270,7 @@ namespace AZ
|
||||
// Build the controller configuration from the editor configuration
|
||||
MaterialComponentConfig config = m_controller.GetConfiguration();
|
||||
config.m_materials.clear();
|
||||
|
||||
|
||||
for (const auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
const EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
@@ -341,7 +293,7 @@ namespace AZ
|
||||
else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty())
|
||||
{
|
||||
MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id];
|
||||
materialAssignment.m_materialAsset.Create(materialSlot->m_id.m_materialAssetId);
|
||||
materialAssignment.m_materialAsset = materialSlot->m_defaultMaterialAsset;
|
||||
materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides;
|
||||
materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides;
|
||||
}
|
||||
@@ -362,6 +314,9 @@ namespace AZ
|
||||
// Get the known material assignment slots from the associated model or other source
|
||||
MaterialAssignmentMap materialsFromSource;
|
||||
MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
|
||||
|
||||
RPI::ModelMaterialSlotMap modelMaterialSlots;
|
||||
MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
|
||||
|
||||
// Generate the table of editable materials using the source data to define number of groups, elements, and initial values
|
||||
for (const auto& materialPair : materialsFromSource)
|
||||
@@ -385,9 +340,33 @@ namespace AZ
|
||||
OnConfigurationChanged();
|
||||
};
|
||||
|
||||
const char* UnknownSlotName = "<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;
|
||||
|
||||
slot.m_propertyOverrides = materialFromController.m_propertyOverrides;
|
||||
slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides;
|
||||
|
||||
@@ -400,13 +379,13 @@ namespace AZ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slot.m_id.IsAssetOnly())
|
||||
if (slot.m_id.IsSlotIdOnly())
|
||||
{
|
||||
m_materialSlots.push_back(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slot.m_id.IsLodAndAsset())
|
||||
if (slot.m_id.IsLodAndSlotId())
|
||||
{
|
||||
// Resize the containers to fit all elements
|
||||
m_materialSlotsByLod.resize(AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1)));
|
||||
@@ -452,27 +431,26 @@ namespace AZ
|
||||
{
|
||||
AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials.");
|
||||
SetDirty();
|
||||
|
||||
|
||||
// First generating a unique set of all material asset IDs that will be used for source data generation
|
||||
AZStd::unordered_set<AZ::Data::AssetId> assetIds;
|
||||
AZStd::unordered_map<AZ::Data::AssetId, AZStd::string /*slot name*/> assetIdMap;
|
||||
|
||||
auto materialSlots = GetMaterialSlots();
|
||||
for (auto& materialSlotPair : materialSlots)
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.m_materialAssetId.IsValid())
|
||||
Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId();
|
||||
if (defaultMaterialAssetId.IsValid())
|
||||
{
|
||||
assetIds.insert(materialSlot->m_id.m_materialAssetId);
|
||||
assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the unique set of asset IDs into export items that can be configured in the dialog
|
||||
// The order should not matter because the table in the dialog can sort itself for a specific row
|
||||
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
|
||||
for (const AZ::Data::AssetId& assetId : assetIds)
|
||||
for (auto assetIdInfo : assetIdMap)
|
||||
{
|
||||
EditorMaterialComponentExporter::ExportItem exportItem;
|
||||
exportItem.m_assetId = assetId;
|
||||
EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second};
|
||||
exportItems.push_back(exportItem);
|
||||
}
|
||||
|
||||
@@ -486,15 +464,20 @@ namespace AZ
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0);
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0);
|
||||
if (assetIdOutcome)
|
||||
{
|
||||
for (auto& materialSlotPair : materialSlots)
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot && materialSlot->m_id.m_materialAssetId == exportItem.m_assetId)
|
||||
EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second;
|
||||
|
||||
if (editorMaterialSlot)
|
||||
{
|
||||
materialSlot->m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
// We need to check whether replaced material corresponds to this slot's default material.
|
||||
if (editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId())
|
||||
{
|
||||
editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,3 +565,4 @@ namespace AZ
|
||||
}
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
|
||||
+25
-65
@@ -37,47 +37,7 @@ namespace AZ
|
||||
{
|
||||
namespace EditorMaterialComponentExporter
|
||||
{
|
||||
AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId)
|
||||
{
|
||||
AZStd::string label;
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
// Material assets that are exported through the scene pipeline have their filenames generated by adding
|
||||
// the DCC material name as a prefix and a unique number to the end of the source file name.
|
||||
// Rather than storing the DCC material name inside of the material asset we can reproduce it by removing
|
||||
// the prefix and suffix from the product file name.
|
||||
|
||||
// We need the material product path as the initial string that will be stripped down
|
||||
const AZStd::string& productPath = AZ::RPI::AssetUtils::GetProductPathByAssetId(assetId);
|
||||
if (!productPath.empty() && AzFramework::StringFunc::Path::GetFileName(productPath.c_str(), label))
|
||||
{
|
||||
// If there is a source file, typically an FBX or other model file, we must get its filename to remove the prefix from the label
|
||||
AZStd::string prefix;
|
||||
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId);
|
||||
if (!sourcePath.empty() && AZ::StringFunc::Path::GetFileName(sourcePath.c_str(), prefix))
|
||||
{
|
||||
if (!prefix.empty() && prefix.size() < label.size())
|
||||
{
|
||||
if (AZ::StringFunc::StartsWith(label, prefix, false))
|
||||
{
|
||||
// All of the product filename's tokens are separated by underscores so we must also remove the first underscore after the prefix
|
||||
label = label.substr(prefix.size() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can remove the numeric suffix by stripping the label of everything after the last underscore
|
||||
const auto iter = label.find_last_of("_");
|
||||
if (iter != AZStd::string::npos)
|
||||
{
|
||||
label = label.substr(0, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId)
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName)
|
||||
{
|
||||
AZStd::string exportPath;
|
||||
if (assetId.IsValid())
|
||||
@@ -85,7 +45,7 @@ namespace AZ
|
||||
exportPath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId);
|
||||
AZ::StringFunc::Path::StripExtension(exportPath);
|
||||
exportPath += "_";
|
||||
exportPath += GetLabelByAssetId(assetId);
|
||||
exportPath += materialSlotName;
|
||||
exportPath += ".";
|
||||
exportPath += AZ::RPI::MaterialSourceData::Extension;
|
||||
AZ::StringFunc::Path::Normalize(exportPath);
|
||||
@@ -132,12 +92,12 @@ namespace AZ
|
||||
int row = 0;
|
||||
for (ExportItem& exportItem : exportItems)
|
||||
{
|
||||
QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_assetId).c_str());
|
||||
QFileInfo fileInfo(GetExportPathByAssetId(exportItem.GetOriginalAssetId(), exportItem.GetMaterialSlotName()).c_str());
|
||||
|
||||
// Configuring initial settings based on whether or not the target file already exists
|
||||
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
|
||||
exportItem.m_exists = fileInfo.exists();
|
||||
exportItem.m_overwrite = false;
|
||||
exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData());
|
||||
exportItem.SetExists(fileInfo.exists());
|
||||
exportItem.SetOverwrite(false);
|
||||
|
||||
// Populate the table with data for every column
|
||||
tableWidget->setItem(row, MaterialSlotColumn, new QTableWidgetItem());
|
||||
@@ -146,23 +106,23 @@ namespace AZ
|
||||
|
||||
// Create a check box for toggling the enabled state of this item
|
||||
QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget);
|
||||
materialSlotCheckBox->setChecked(exportItem.m_enabled);
|
||||
materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_assetId).c_str());
|
||||
materialSlotCheckBox->setChecked(exportItem.GetEnabled());
|
||||
materialSlotCheckBox->setText(exportItem.GetMaterialSlotName().c_str());
|
||||
tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox);
|
||||
|
||||
// Create a file picker widget for selecting the save path for the exported material
|
||||
AzQtComponents::BrowseEdit* materialFileWidget = new AzQtComponents::BrowseEdit(tableWidget);
|
||||
materialFileWidget->setLineEditReadOnly(true);
|
||||
materialFileWidget->setClearButtonEnabled(false);
|
||||
materialFileWidget->setEnabled(exportItem.m_enabled);
|
||||
materialFileWidget->setEnabled(exportItem.GetEnabled());
|
||||
materialFileWidget->setText(fileInfo.fileName());
|
||||
tableWidget->setCellWidget(row, MaterialFileColumn, materialFileWidget);
|
||||
|
||||
// Create a check box for toggling the overwrite state of this item
|
||||
QWidget* overwriteCheckBoxContainer = new QWidget(tableWidget);
|
||||
QCheckBox* overwriteCheckBox = new QCheckBox(overwriteCheckBoxContainer);
|
||||
overwriteCheckBox->setChecked(exportItem.m_overwrite);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
overwriteCheckBox->setChecked(exportItem.GetOverwrite());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
|
||||
overwriteCheckBoxContainer->setLayout(new QHBoxLayout(overwriteCheckBoxContainer));
|
||||
overwriteCheckBoxContainer->layout()->addWidget(overwriteCheckBox);
|
||||
@@ -173,21 +133,21 @@ namespace AZ
|
||||
|
||||
// Whenever the selection is updated, automatically apply the change to the export item
|
||||
QObject::connect(materialSlotCheckBox, &QCheckBox::stateChanged, materialSlotCheckBox, [&exportItem, materialFileWidget, materialSlotCheckBox, overwriteCheckBox]([[maybe_unused]] int state) {
|
||||
exportItem.m_enabled = materialSlotCheckBox->isChecked();
|
||||
materialFileWidget->setEnabled(exportItem.m_enabled);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
exportItem.SetEnabled(materialSlotCheckBox->isChecked());
|
||||
materialFileWidget->setEnabled(exportItem.GetEnabled());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
});
|
||||
|
||||
// Whenever the overwrite check box is updated, automatically apply the change to the export item
|
||||
QObject::connect(overwriteCheckBox, &QCheckBox::stateChanged, overwriteCheckBox, [&exportItem, overwriteCheckBox]([[maybe_unused]] int state) {
|
||||
exportItem.m_overwrite = overwriteCheckBox->isChecked();
|
||||
exportItem.SetOverwrite(overwriteCheckBox->isChecked());
|
||||
});
|
||||
|
||||
// Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting
|
||||
QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&dialog, &exportItem, materialFileWidget, overwriteCheckBox]() {
|
||||
QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog,
|
||||
QString("Select Material Filename"),
|
||||
exportItem.m_exportPath.c_str(),
|
||||
exportItem.GetExportPath().c_str(),
|
||||
QString("Material (*.material)"),
|
||||
nullptr,
|
||||
QFileDialog::DontConfirmOverwrite);
|
||||
@@ -195,14 +155,14 @@ namespace AZ
|
||||
// Only update the export data if a valid path and filename was selected
|
||||
if (!fileInfo.absoluteFilePath().isEmpty())
|
||||
{
|
||||
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
|
||||
exportItem.m_exists = fileInfo.exists();
|
||||
exportItem.m_overwrite = fileInfo.exists();
|
||||
exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData());
|
||||
exportItem.SetExists(fileInfo.exists());
|
||||
exportItem.SetOverwrite(fileInfo.exists());
|
||||
|
||||
// Update the controls to display the new state
|
||||
materialFileWidget->setText(fileInfo.fileName());
|
||||
overwriteCheckBox->setChecked(exportItem.m_overwrite);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
overwriteCheckBox->setChecked(exportItem.GetOverwrite());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -245,24 +205,24 @@ namespace AZ
|
||||
|
||||
bool ExportMaterialSourceData(const ExportItem& exportItem)
|
||||
{
|
||||
if (!exportItem.m_enabled || exportItem.m_exportPath.empty())
|
||||
if (!exportItem.GetEnabled() || exportItem.GetExportPath().empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (exportItem.m_exists && !exportItem.m_overwrite)
|
||||
if (exportItem.GetExists() && !exportItem.GetOverwrite())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
EditorMaterialComponentUtil::MaterialEditData editData;
|
||||
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData))
|
||||
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.GetOriginalAssetId(), editData))
|
||||
{
|
||||
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.m_exportPath, editData))
|
||||
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.GetExportPath(), editData))
|
||||
{
|
||||
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to save material data.");
|
||||
return false;
|
||||
|
||||
+30
-9
@@ -19,27 +19,48 @@ namespace AZ
|
||||
{
|
||||
namespace EditorMaterialComponentExporter
|
||||
{
|
||||
// Attemts to generate a display label for a material slot by parsing its file name
|
||||
AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId);
|
||||
//! Generates a destination file path for exporting material source data
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName);
|
||||
|
||||
// Generates a destination file path for exporting material source data
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId);
|
||||
|
||||
struct ExportItem
|
||||
class ExportItem
|
||||
{
|
||||
public:
|
||||
//! @param originalAssetId AssetId of the original built-in material, which will be exported.
|
||||
//! @param materialSlotName The name of the material slot will be used as part of the exported file name.
|
||||
ExportItem(AZ::Data::AssetId originalAssetId, const AZStd::string& materialSlotName)
|
||||
: m_originalAssetId(originalAssetId)
|
||||
, m_materialSlotName(materialSlotName)
|
||||
{}
|
||||
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
void SetExists(bool exists) { m_exists = exists; }
|
||||
void SetOverwrite(bool overwrite) { m_overwrite = overwrite; }
|
||||
void SetExportPath(const AZStd::string& exportPath) { m_exportPath = exportPath; }
|
||||
|
||||
bool GetEnabled() const { return m_enabled; }
|
||||
bool GetExists() const { return m_exists; }
|
||||
bool GetOverwrite() const { return m_overwrite; }
|
||||
const AZStd::string& GetExportPath() const { return m_exportPath; }
|
||||
|
||||
AZ::Data::AssetId GetOriginalAssetId() const { return m_originalAssetId; }
|
||||
const AZStd::string& GetMaterialSlotName() const { return m_materialSlotName; }
|
||||
|
||||
private:
|
||||
bool m_enabled = true;
|
||||
bool m_exists = false;
|
||||
bool m_overwrite = false;
|
||||
AZ::Data::AssetId m_assetId;
|
||||
AZStd::string m_exportPath;
|
||||
AZ::Data::AssetId m_originalAssetId; //!< AssetId of the original built-in material, which will be exported.
|
||||
AZStd::string m_materialSlotName;
|
||||
};
|
||||
|
||||
using ExportItemsContainer = AZStd::vector<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
|
||||
|
||||
+13
-27
@@ -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);
|
||||
}
|
||||
|
||||
@@ -80,9 +80,10 @@ namespace AZ
|
||||
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<EditorMaterialComponentSlot>()
|
||||
->Version(5, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Version(6, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Field("id", &EditorMaterialComponentSlot::m_id)
|
||||
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
|
||||
->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset)
|
||||
;
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
@@ -121,21 +122,12 @@ namespace AZ
|
||||
|
||||
AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const
|
||||
{
|
||||
return m_id.m_materialAssetId;
|
||||
return m_defaultMaterialAsset.GetId();
|
||||
}
|
||||
|
||||
AZStd::string EditorMaterialComponentSlot::GetLabel() const
|
||||
{
|
||||
// Generate the label for the material slot based on the assignment ID
|
||||
// If this is the default material assignment ID then it represents the default slot which is not contained in any other group
|
||||
if (m_id == DefaultMaterialAssignmentId)
|
||||
{
|
||||
return "Default Material";
|
||||
}
|
||||
|
||||
// Otherwise the label can be generated by parsing the source file name associated with the asset ID
|
||||
const AZStd::string& label = EditorMaterialComponentExporter::GetLabelByAssetId(m_id.m_materialAssetId);
|
||||
return !label.empty() ? label : "<unknown>";
|
||||
return m_label;
|
||||
}
|
||||
|
||||
bool EditorMaterialComponentSlot::HasSourceData() const
|
||||
@@ -183,27 +175,21 @@ namespace AZ
|
||||
OnMaterialChanged();
|
||||
}
|
||||
|
||||
void EditorMaterialComponentSlot::SetDefaultAsset()
|
||||
void EditorMaterialComponentSlot::ResetToDefaultAsset()
|
||||
{
|
||||
m_materialAsset = {};
|
||||
m_materialAsset = m_defaultMaterialAsset;
|
||||
m_propertyOverrides = {};
|
||||
m_matModUvOverrides = {};
|
||||
if (m_id.m_materialAssetId.IsValid())
|
||||
{
|
||||
// If no material is assigned to this slot, assign the default material from the slot id to edit its properties
|
||||
m_materialAsset.Create(m_id.m_materialAssetId);
|
||||
}
|
||||
OnMaterialChanged();
|
||||
}
|
||||
|
||||
void EditorMaterialComponentSlot::OpenMaterialExporter()
|
||||
{
|
||||
// Because we are generating a source material from this specific slot there is only one entry
|
||||
// But we still need to allow the user to reconfigure it using the dialogue
|
||||
// But we still need to allow the user to reconfigure it using the dialog
|
||||
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
|
||||
{
|
||||
EditorMaterialComponentExporter::ExportItem exportItem;
|
||||
exportItem.m_assetId = m_id.m_materialAssetId;
|
||||
EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label};
|
||||
exportItems.push_back(exportItem);
|
||||
}
|
||||
|
||||
@@ -218,7 +204,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Generate a new asset ID utilizing the export file path so that we can update this material slot to reference the new asset
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0);
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0);
|
||||
if (assetIdOutcome)
|
||||
{
|
||||
m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
@@ -258,7 +244,7 @@ namespace AZ
|
||||
// Treated as a special property. It will be updated together with properties.
|
||||
OnPropertyChanged();
|
||||
};
|
||||
|
||||
|
||||
if (m_materialAsset.GetId().IsValid())
|
||||
{
|
||||
if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback))
|
||||
@@ -275,7 +261,7 @@ namespace AZ
|
||||
QAction* action = nullptr;
|
||||
|
||||
action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); });
|
||||
action->setEnabled(m_id.m_materialAssetId.IsValid());
|
||||
action->setEnabled(m_defaultMaterialAsset.GetId().IsValid());
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
|
||||
+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->GetMaterialSlots();
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAssignmentId MeshComponentController::FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
|
||||
|
||||
@@ -113,6 +113,7 @@ namespace AZ
|
||||
// MaterialReceiverRequestBus::Handler overrides ...
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
|
||||
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
|
||||
MaterialAssignmentMap GetMaterialAssignments() const override;
|
||||
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
|
||||
|
||||
|
||||
@@ -96,12 +96,11 @@ 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_materialSlot = actor->GetMeshAsset()->FindMaterialSlot(modelMesh.GetMaterialSlotId());
|
||||
|
||||
// Queue the material asset - the ModelLod seems to handle delayed material loads
|
||||
skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid<RPI::MaterialAsset>(), skinnedSubMesh.m_material.GetAutoLoadBehavior());
|
||||
skinnedSubMesh.m_materialSlot.m_defaultMaterialAsset.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->GetMaterialSlots();
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAssignmentId AtomActorInstance::FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
|
||||
@@ -501,16 +514,18 @@ namespace AZ
|
||||
const AZStd::vector< SkinnedSubMeshProperties>& subMeshProperties = inputLod.GetSubMeshProperties();
|
||||
for (const SkinnedSubMeshProperties& submesh : subMeshProperties)
|
||||
{
|
||||
AZ_Error("AtomActorInstance", submesh.m_material, "Actor does not have a valid default material in lod %d", lodIndex);
|
||||
if (submesh.m_material)
|
||||
Data::Asset<RPI::MaterialAsset> materialAsset = submesh.m_materialSlot.m_defaultMaterialAsset;
|
||||
AZ_Error("AtomActorInstance", materialAsset, "Actor does not have a valid default material in lod %d", lodIndex);
|
||||
|
||||
if (materialAsset)
|
||||
{
|
||||
if (!submesh.m_material->IsReady())
|
||||
if (!materialAsset->IsReady())
|
||||
{
|
||||
// Start listening for the material's OnAssetReady event.
|
||||
// AtomActorInstance::Create is called on the main thread, so there should be no need to synchronize with the OnAssetReady event handler
|
||||
// since those events will also come from the main thread
|
||||
m_waitForMaterialLoadIds.insert(submesh.m_material->GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(submesh.m_material->GetId());
|
||||
m_waitForMaterialLoadIds.insert(materialAsset->GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(materialAsset->GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace AZ
|
||||
// MaterialReceiverRequestBus::Handler overrides...
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
|
||||
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
|
||||
MaterialAssignmentMap GetMaterialAssignments() const override;
|
||||
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
|
||||
|
||||
|
||||
@@ -112,17 +112,8 @@ namespace WhiteBox
|
||||
AddLodBuffers(modelLodCreator);
|
||||
modelLodCreator.BeginMesh();
|
||||
modelLodCreator.SetMeshAabb(meshData.GetAabb());
|
||||
|
||||
// set the default material
|
||||
if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(TexturedMaterialPath.data()))
|
||||
{
|
||||
modelLodCreator.SetMeshMaterialAsset(materialAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("CreateLodAsset", false, "Could not load material.");
|
||||
return false;
|
||||
}
|
||||
|
||||
modelLodCreator.SetMeshMaterialSlot(OneMaterialSlotId);
|
||||
|
||||
AddMeshBuffers(modelLodCreator);
|
||||
modelLodCreator.EndMesh();
|
||||
@@ -154,6 +145,20 @@ namespace WhiteBox
|
||||
modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
|
||||
modelCreator.SetName(ModelName);
|
||||
modelCreator.AddLodAsset(AZStd::move(m_lodAsset));
|
||||
|
||||
if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(TexturedMaterialPath.data()))
|
||||
{
|
||||
AZ::RPI::ModelMaterialSlot materialSlot;
|
||||
materialSlot.m_stableId = OneMaterialSlotId;
|
||||
materialSlot.m_defaultMaterialAsset = materialAsset;
|
||||
modelCreator.AddMaterialSlot(materialSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("CreateLodAsset", false, "Could not load material.");
|
||||
return;
|
||||
}
|
||||
|
||||
modelCreator.End(m_modelAsset);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace WhiteBox
|
||||
// TODO: LYN-784
|
||||
static constexpr AZStd::string_view TexturedMaterialPath = "materials/defaultpbr.azmaterial";
|
||||
static constexpr AZStd::string_view SolidMaterialPath = "materials/defaultpbr.azmaterial";
|
||||
static constexpr AZ::RPI::ModelMaterialSlot::StableId OneMaterialSlotId = 0;
|
||||
|
||||
//! White box model name.
|
||||
static constexpr AZStd::string_view ModelName = "WhiteBoxMesh";
|
||||
|
||||
Reference in New Issue
Block a user