diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp index e26a0fb274..fd1c836b96 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.cpp @@ -26,9 +26,10 @@ namespace AZ if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("Enable", &MaterialConverterSettings::m_enable) - ->Field("DefaultMaterial", &MaterialConverterSettings::m_defaultMaterial); + ->Field("DefaultMaterial", &MaterialConverterSettings::m_defaultMaterial) + ->Field("IncludeMaterialPropertyNames", &MaterialConverterSettings::m_includeMaterialPropertyNames); } } @@ -69,6 +70,11 @@ namespace AZ return m_settings.m_enable; } + bool MaterialConverterSystemComponent::ShouldIncludeMaterialPropertyNames() const + { + return m_settings.m_includeMaterialPropertyNames; + } + bool MaterialConverterSystemComponent::ConvertMaterial( const AZ::SceneAPI::DataTypes::IMaterialData& materialData, RPI::MaterialSourceData& sourceData) { diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.h b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.h index 7d95024759..150529b474 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.h +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialConverterSystemComponent.h @@ -26,6 +26,11 @@ namespace AZ bool m_enable = true; AZStd::string m_defaultMaterial; + //! Sets whether to include material property names when generating material assets. If this + //! setting is true, material property name resolution and validation is deferred into load + //! time rather than at build time, allowing to break some dependencies (e.g. fbx files will no + //! longer need to be dependent on materialtype files). + bool m_includeMaterialPropertyNames = true; }; //! Atom's implementation of converting SceneAPI data into Atom's default material: StandardPBR @@ -45,6 +50,7 @@ namespace AZ // MaterialConverterBus overrides ... bool IsEnabled() const override; + bool ShouldIncludeMaterialPropertyNames() const override; bool ConvertMaterial(const AZ::SceneAPI::DataTypes::IMaterialData& materialData, RPI::MaterialSourceData& out) override; AZStd::string GetMaterialTypePath() const override; AZStd::string GetDefaultMaterialPath() const override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialConverterBus.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialConverterBus.h index 8052fc4feb..1807fca15e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialConverterBus.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialConverterBus.h @@ -32,6 +32,10 @@ namespace AZ virtual bool IsEnabled() const = 0; + //! Returns true if material property names should be included in azmaterials. This allows unlinking of dependencies for some + //! file types to materialtype files (e.g. fbx). + virtual bool ShouldIncludeMaterialPropertyNames() const = 0; + //! Converts data from a IMaterialData object to an Atom MaterialSourceData. //! Only works when IsEnabled() is true. //! @return true if the MaterialSourceData output was populated with converted material data. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h index 1daec3bacd..02607a7954 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h @@ -68,12 +68,12 @@ namespace AZ //! @param assetId ID for the MaterialAsset //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths. //! @param elevateWarnings Indicates whether to treat warnings as errors - //! @param materialTypeSourceData The function sometimes needs metadata from the .materialtype file. - //! It will either load the .materialtype file from disk, or use this MaterialTypeSourceData if it's provided. + //! @param includeMaterialPropertyNames Indicates whether to save material property names into the material asset file Outcome> CreateMaterialAsset( Data::AssetId assetId, AZStd::string_view materialSourceFilePath = "", - bool elevateWarnings = true + bool elevateWarnings = true, + bool includeMaterialPropertyNames = true ) const; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h index 49ef839331..52af56ba0e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h @@ -108,6 +108,11 @@ namespace AZ private: bool PostLoadInit() override; + //! Realigns property value and name indices with MaterialProperiesLayout by using m_propertyNames. Property names not found in the + //! MaterialPropertiesLayout are discarded, while property names not included in m_propertyNames will use the default value + //! from m_materialTypeAsset. + void RealignPropertyValuesAndNames(); + //! Called by asset creators to assign the asset to a ready state. void SetReady(); @@ -121,11 +126,20 @@ namespace AZ // MaterialReloadNotificationBus overrides... void OnMaterialTypeAssetReinitialized(const Data::Asset& materialTypeAsset) override; + static const char* s_debugTraceName; + Data::Asset m_materialTypeAsset; //! Holds values for each material property, used to initialize Material instances. //! This is indexed by MaterialPropertyIndex and aligns with entries in m_materialPropertiesLayout. AZStd::vector m_propertyValues; + //! This is used to realign m_propertyValues as well as itself with MaterialPropertiesLayout when not empty. + //! If empty, this implies that m_propertyValues is aligned with the entries in m_materialPropertiesLayout. + AZStd::vector m_propertyNames; + + //! A flag to determine if m_propertyValues needs to be aligned with MaterialPropertiesLayout. Set to true whenever + //! m_materialTypeAsset is reinitializing. + bool m_isDirty = true; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h index 44520cb549..862d98ce0c 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h @@ -26,11 +26,13 @@ namespace AZ public: friend class MaterialSourceData; - void Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial); - void Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType); + void Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial, bool includeMaterialPropertyNames = true); + void Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType, bool includeMaterialPropertyNames = true); bool End(Data::Asset& result); private: + void PopulatePropertyNameList(); + const MaterialPropertiesLayout* m_materialPropertiesLayout = nullptr; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp index ccbdc47f7e..4c2876dc0c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/MaterialAssetBuilderComponent.cpp @@ -78,7 +78,9 @@ namespace AZ AZStd::string materialTypePath; RPI::MaterialConverterBus::BroadcastResult(materialTypePath, &RPI::MaterialConverterBus::Events::GetMaterialTypePath); - if (conversionEnabled && !materialTypePath.empty()) + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); + if (conversionEnabled && !materialTypePath.empty() && !includeMaterialPropertyNames) { AssetBuilderSDK::SourceFileDependency materialTypeSource; materialTypeSource.m_sourceFileDependencyPath = materialTypePath; @@ -101,6 +103,10 @@ namespace AZ RPI::MaterialConverterBus::BroadcastResult(conversionEnabled, &RPI::MaterialConverterBus::Events::IsEnabled); fingerprintInfo.insert(AZStd::string::format("[MaterialConverter enabled=%d]", conversionEnabled)); + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); + fingerprintInfo.insert(AZStd::string::format("[MaterialConverter includeMaterialPropertyNames=%d]", includeMaterialPropertyNames)); + if (!conversionEnabled) { AZStd::string defaultMaterialPath; @@ -219,6 +225,8 @@ namespace AZ } } + bool includeMaterialPropertyNames = true; + RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames); // Build material assets. for (auto& itr : materialSourceDataByUid) { @@ -226,7 +234,7 @@ namespace AZ Data::AssetId assetId(sourceSceneUuid, GetMaterialAssetSubId(materialUid)); auto materialSourceData = itr.second; - Outcome> result = materialSourceData.m_data.CreateMaterialAsset(assetId, "", false); + Outcome> result = materialSourceData.m_data.CreateMaterialAsset(assetId, "", false, includeMaterialPropertyNames); if (result.IsSuccess()) { context.m_outputMaterialsByUid[materialUid] = { result.GetValue(), materialSourceData.m_name }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index 9f980287cf..f697f33a3f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -72,7 +73,7 @@ namespace AZ } } - Outcome > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings) const + Outcome > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const { MaterialAssetCreator materialAssetCreator; materialAssetCreator.SetElevateWarnings(elevateWarnings); @@ -85,7 +86,7 @@ namespace AZ return Failure(); } - materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get()); + materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get(), includeMaterialPropertyNames); } else { @@ -115,7 +116,7 @@ namespace AZ } } - materialAssetCreator.Begin(assetId, *parentMaterialAsset.GetValue().Get()); + materialAssetCreator.Begin(assetId, *parentMaterialAsset.GetValue().Get(), includeMaterialPropertyNames); } for (auto& group : m_properties) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp index 1acb1c69a1..c3e0221192 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp @@ -20,6 +20,8 @@ namespace AZ { namespace RPI { + const char* MaterialAsset::s_debugTraceName = "MaterialAsset"; + const char* MaterialAsset::DisplayName = "MaterialAsset"; const char* MaterialAsset::Group = "Material"; const char* MaterialAsset::Extension = "azmaterial"; @@ -29,9 +31,10 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(9) + ->Version(10) ->Field("materialTypeAsset", &MaterialAsset::m_materialTypeAsset) ->Field("propertyValues", &MaterialAsset::m_propertyValues) + ->Field("propertyNames", &MaterialAsset::m_propertyNames) ; } } @@ -99,6 +102,11 @@ namespace AZ AZStd::array_view MaterialAsset::GetPropertyValues() const { + if (!m_propertyNames.empty() && m_isDirty) + { + const_cast(this)->RealignPropertyValuesAndNames(); + } + return m_propertyValues; } @@ -146,6 +154,34 @@ namespace AZ } } + void MaterialAsset::RealignPropertyValuesAndNames() + { + const MaterialPropertiesLayout* propertyLayout = GetMaterialPropertiesLayout(); + AZStd::vector alignedPropertyValues(m_materialTypeAsset->GetDefaultPropertyValues().begin(), m_materialTypeAsset->GetDefaultPropertyValues().end()); + for (size_t i = 0; i < m_propertyNames.size(); ++i) + { + const MaterialPropertyIndex propertyIndex = propertyLayout->FindPropertyIndex(m_propertyNames[i]); + if (propertyIndex.IsValid()) + { + alignedPropertyValues[propertyIndex.GetIndex()] = m_propertyValues[i]; + } + else + { + AZ_Warning(s_debugTraceName, false, "Material property name \"%s\" is not found in the material properties layout and will not be used.", m_propertyNames[i].GetCStr()); + } + } + m_propertyValues.swap(alignedPropertyValues); + + const size_t propertyCount = propertyLayout->GetPropertyCount(); + m_propertyNames.resize(propertyCount); + for (size_t i = 0; i < propertyCount; ++i) + { + m_propertyNames[i] = propertyLayout->GetPropertyDescriptor(MaterialPropertyIndex{ i })->GetName(); + } + + m_isDirty = false; + } + void MaterialAsset::ReinitializeMaterialTypeAsset(Data::Asset asset) { Data::Asset newMaterialTypeAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; @@ -157,6 +193,8 @@ namespace AZ // This also covers the case where just the MaterialTypeAsset is reloaded and not the MaterialAsset. m_materialTypeAsset = newMaterialTypeAsset; + m_isDirty = true; + // Notify interested parties that this MaterialAsset is changed and may require other data to reinitialize as well MaterialReloadNotificationBus::Event(GetId(), &MaterialReloadNotifications::OnMaterialAssetReinitialized, Data::Asset{this, AZ::Data::AssetLoadBehavior::PreLoad}); } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp index 8e401d2ee8..79d19c15a2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp @@ -16,7 +16,7 @@ namespace AZ { namespace RPI { - void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial) + void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialAsset& parentMaterial, bool includeMaterialPropertyNames) { BeginCommon(assetId); @@ -36,6 +36,10 @@ namespace AZ ReportError("MaterialPropertiesLayout is null"); return; } + if (includeMaterialPropertyNames) + { + PopulatePropertyNameList(); + } // Note we don't have to check the validity of these property values because the parent material's AssetCreator already did that. m_asset->m_propertyValues.assign(parentMaterial.GetPropertyValues().begin(), parentMaterial.GetPropertyValues().end()); @@ -52,14 +56,14 @@ namespace AZ } } - void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType) + void MaterialAssetCreator::Begin(const Data::AssetId& assetId, MaterialTypeAsset& materialType, bool includeMaterialPropertyNames) { BeginCommon(assetId); if (ValidateIsReady()) { m_asset->m_materialTypeAsset = { &materialType, AZ::Data::AssetLoadBehavior::PreLoad }; - + if (!m_asset->m_materialTypeAsset) { ReportError("MaterialTypeAsset is null"); @@ -67,6 +71,11 @@ namespace AZ } m_materialPropertiesLayout = m_asset->GetMaterialPropertiesLayout(); + if (includeMaterialPropertyNames) + { + PopulatePropertyNameList(); + } + if (!m_materialPropertiesLayout) { ReportError("MaterialPropertiesLayout is null"); @@ -101,5 +110,16 @@ namespace AZ m_asset->SetReady(); return EndCommon(result); } + + void MaterialAssetCreator::PopulatePropertyNameList() + { + for (int i = 0; i < m_materialPropertiesLayout->GetPropertyCount(); ++i) + { + MaterialPropertyIndex propertyIndex{ i }; + auto& propertyName = m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex)->GetName(); + m_asset->m_propertyNames.emplace_back(propertyName); + } + } + } // namespace RPI } // namespace AZ