MaterialAssignment.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  9. #include <AtomLyIntegration/CommonFeatures/Material/MaterialAssignment.h>
  10. #include <AzCore/Asset/AssetSerializer.h>
  11. #include <AzCore/RTTI/BehaviorContext.h>
  12. #include <AzCore/Serialization/EditContext.h>
  13. #include <AzCore/Serialization/Json/RegistrationContext.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. namespace AZ
  16. {
  17. namespace Render
  18. {
  19. void MaterialAssignment::Reflect(ReflectContext* context)
  20. {
  21. MaterialAssignmentId::Reflect(context);
  22. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  23. {
  24. serializeContext->RegisterGenericType<MaterialAssignmentMap>();
  25. serializeContext->RegisterGenericType<MaterialPropertyOverrideMap>();
  26. serializeContext->Class<MaterialAssignment>()
  27. ->Version(2)
  28. ->Field("MaterialAsset", &MaterialAssignment::m_materialAsset)
  29. ->Field("PropertyOverrides", &MaterialAssignment::m_propertyOverrides)
  30. ->Field("ModelUvOverrides", &MaterialAssignment::m_matModUvOverrides)
  31. ;
  32. if (auto editContext = serializeContext->GetEditContext())
  33. {
  34. editContext->Class<MaterialAssignment>(
  35. "Material Assignment", "Material Assignment")
  36. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  37. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  38. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
  39. ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialAssignment::m_materialAsset, "Material Asset", "")
  40. ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialAssignment::m_propertyOverrides, "Property Overrides", "")
  41. ;
  42. }
  43. }
  44. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  45. {
  46. behaviorContext->Class<MaterialAssignment>("MaterialAssignment")
  47. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  48. ->Attribute(AZ::Script::Attributes::Category, "render")
  49. ->Attribute(AZ::Script::Attributes::Module, "render")
  50. ->Constructor()
  51. ->Constructor<const MaterialAssignment&>()
  52. ->Constructor<const AZ::Data::AssetId&>()
  53. ->Constructor<const Data::Asset<RPI::MaterialAsset>&>()
  54. ->Constructor<const Data::Asset<RPI::MaterialAsset>&, const Data::Instance<RPI::Material>&>()
  55. ->Method("ToString", &MaterialAssignment::ToString)
  56. ->Property("materialAsset", BehaviorValueProperty(&MaterialAssignment::m_materialAsset))
  57. ->Property("propertyOverrides", BehaviorValueProperty(&MaterialAssignment::m_propertyOverrides));
  58. behaviorContext->ConstantProperty("DefaultMaterialAssignment", BehaviorConstant(DefaultMaterialAssignment))
  59. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  60. ->Attribute(AZ::Script::Attributes::Category, "render")
  61. ->Attribute(AZ::Script::Attributes::Module, "render");
  62. behaviorContext->ConstantProperty("DefaultMaterialAssignmentId", BehaviorConstant(DefaultMaterialAssignmentId))
  63. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  64. ->Attribute(AZ::Script::Attributes::Category, "render")
  65. ->Attribute(AZ::Script::Attributes::Module, "render");
  66. behaviorContext->ConstantProperty("DefaultMaterialAssignmentMap", BehaviorConstant(DefaultMaterialAssignmentMap))
  67. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  68. ->Attribute(AZ::Script::Attributes::Category, "render")
  69. ->Attribute(AZ::Script::Attributes::Module, "render");
  70. }
  71. }
  72. MaterialAssignment::MaterialAssignment(const AZ::Data::AssetId& materialAssetId)
  73. : MaterialAssignment(Data::Asset<RPI::MaterialAsset>(materialAssetId, AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid()))
  74. {
  75. }
  76. MaterialAssignment::MaterialAssignment(const Data::Asset<RPI::MaterialAsset>& asset)
  77. : MaterialAssignment(asset, Data::Instance<RPI::Material>())
  78. {
  79. }
  80. MaterialAssignment::MaterialAssignment(const Data::Asset<RPI::MaterialAsset>& asset, const Data::Instance<RPI::Material>& instance)
  81. : m_defaultMaterialAsset(asset)
  82. , m_materialAsset(asset)
  83. , m_materialInstance(instance)
  84. {
  85. }
  86. void MaterialAssignment::RebuildInstance()
  87. {
  88. if (m_materialInstancePreCreated)
  89. {
  90. return;
  91. }
  92. if (m_materialAsset.GetId().IsValid() && m_materialAsset.IsReady())
  93. {
  94. const bool createUniqueInstance = m_materialInstanceMustBeUnique || !m_propertyOverrides.empty();
  95. m_materialInstance = createUniqueInstance ? RPI::Material::Create(m_materialAsset) : RPI::Material::FindOrCreate(m_materialAsset);
  96. AZ_Error("MaterialAssignment", m_materialInstance, "Material instance not initialized");
  97. return;
  98. }
  99. if (m_defaultMaterialAsset.GetId().IsValid() && m_defaultMaterialAsset.IsReady())
  100. {
  101. const bool createUniqueInstance = m_materialInstanceMustBeUnique || !m_propertyOverrides.empty();
  102. m_materialInstance = createUniqueInstance ? RPI::Material::Create(m_defaultMaterialAsset) : RPI::Material::FindOrCreate(m_defaultMaterialAsset);
  103. AZ_Error("MaterialAssignment", m_materialInstance, "Material instance not initialized");
  104. return;
  105. }
  106. // Clear the existing material instance if no asset was ready
  107. m_materialInstance = nullptr;
  108. }
  109. void MaterialAssignment::Release()
  110. {
  111. if (!m_materialInstancePreCreated)
  112. {
  113. m_materialInstance = nullptr;
  114. }
  115. m_materialAsset.Release();
  116. m_defaultMaterialAsset.Release();
  117. }
  118. bool MaterialAssignment::RequiresLoading() const
  119. {
  120. return
  121. !m_materialInstancePreCreated &&
  122. !m_materialAsset.IsReady() &&
  123. !m_materialAsset.IsLoading() &&
  124. !m_defaultMaterialAsset.IsReady() &&
  125. !m_defaultMaterialAsset.IsLoading();
  126. }
  127. bool MaterialAssignment::ApplyProperties()
  128. {
  129. // Immediately return true, skipping this material, if there is no instance or no properties to apply
  130. if (!m_materialInstance || m_propertyOverrides.empty())
  131. {
  132. return true;
  133. }
  134. for (const auto& propertyPair : m_propertyOverrides)
  135. {
  136. if (!propertyPair.second.empty())
  137. {
  138. bool wasRenamed = false;
  139. Name newName;
  140. RPI::MaterialPropertyIndex materialPropertyIndex =
  141. m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName);
  142. // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add
  143. // some extra info to help the user resolve it.
  144. AZ_Warning(
  145. "MaterialAssignment", !wasRenamed,
  146. "Consider running \"Apply Automatic Property Updates\" to use the latest property names.",
  147. propertyPair.first.GetCStr(), newName.GetCStr());
  148. if (wasRenamed && m_propertyOverrides.find(newName) != m_propertyOverrides.end())
  149. {
  150. materialPropertyIndex.Reset();
  151. AZ_Warning(
  152. "MaterialAssignment", false,
  153. "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with "
  154. "the old name will be ignored.",
  155. propertyPair.first.GetCStr(), newName.GetCStr());
  156. }
  157. if (!materialPropertyIndex.IsNull())
  158. {
  159. const auto propertyDescriptor =
  160. m_materialInstance->GetMaterialPropertiesLayout()->GetPropertyDescriptor(materialPropertyIndex);
  161. m_materialInstance->SetPropertyValue(
  162. materialPropertyIndex, ConvertMaterialPropertyValueFromScript(propertyDescriptor, propertyPair.second));
  163. }
  164. }
  165. }
  166. // Return true if there is nothing to compile, meaning no properties changed, or the compile succeeded
  167. return !m_materialInstance->NeedsCompile() || m_materialInstance->Compile();
  168. }
  169. AZStd::string MaterialAssignment::ToString() const
  170. {
  171. AZStd::string assetPathString;
  172. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  173. assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_materialAsset.GetId());
  174. return assetPathString;
  175. }
  176. const MaterialAssignment& GetMaterialAssignmentFromMap(const MaterialAssignmentMap& materials, const MaterialAssignmentId& id)
  177. {
  178. const auto& materialItr = materials.find(id);
  179. return materialItr != materials.end() ? materialItr->second : DefaultMaterialAssignment;
  180. }
  181. const MaterialAssignment& GetMaterialAssignmentFromMapWithFallback(
  182. const MaterialAssignmentMap& materials, const MaterialAssignmentId& id)
  183. {
  184. const MaterialAssignment& lodAssignment = GetMaterialAssignmentFromMap(materials, id);
  185. if (lodAssignment.m_materialInstance.get())
  186. {
  187. return lodAssignment;
  188. }
  189. // GCC is incorrectly flagging the const reference returned from GetMaterialAssignmentFromMap
  190. // as a dangling reference, despite the function returning a const reference.
  191. AZ_PUSH_DISABLE_WARNING_GCC("-Wdangling-reference")
  192. const MaterialAssignment& assetAssignment =
  193. GetMaterialAssignmentFromMap(materials, MaterialAssignmentId::CreateFromStableIdOnly(id.m_materialSlotStableId));
  194. AZ_POP_DISABLE_WARNING_GCC
  195. if (assetAssignment.m_materialInstance.get())
  196. {
  197. return assetAssignment;
  198. }
  199. const MaterialAssignment& defaultAssignment = GetMaterialAssignmentFromMap(materials, DefaultMaterialAssignmentId);
  200. if (defaultAssignment.m_materialInstance.get())
  201. {
  202. return defaultAssignment;
  203. }
  204. return DefaultMaterialAssignment;
  205. }
  206. MaterialAssignmentMap GetDefaultMaterialMapFromModelAsset(const Data::Asset<AZ::RPI::ModelAsset> modelAsset)
  207. {
  208. MaterialAssignmentMap materials;
  209. materials[DefaultMaterialAssignmentId] = MaterialAssignment();
  210. if (modelAsset.IsReady())
  211. {
  212. MaterialAssignmentLodIndex lodIndex = 0;
  213. for (const auto& lod : modelAsset->GetLodAssets())
  214. {
  215. for (const auto& mesh : lod->GetMeshes())
  216. {
  217. const auto slotId = mesh.GetMaterialSlotId();
  218. const auto& slot = modelAsset->FindMaterialSlot(slotId);
  219. const auto generalId = MaterialAssignmentId::CreateFromStableIdOnly(slotId);
  220. materials[generalId] = MaterialAssignment(slot.m_defaultMaterialAsset);
  221. const auto specificId = MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, slotId);
  222. materials[specificId] = MaterialAssignment(slot.m_defaultMaterialAsset);
  223. }
  224. ++lodIndex;
  225. }
  226. }
  227. return materials;
  228. }
  229. MaterialAssignmentLabelMap GetMaterialSlotLabelsFromModelAsset(const Data::Asset<AZ::RPI::ModelAsset> modelAsset)
  230. {
  231. MaterialAssignmentLabelMap labels;
  232. labels[DefaultMaterialAssignmentId] = "Default Material";
  233. if (modelAsset.IsReady())
  234. {
  235. MaterialAssignmentLodIndex lodIndex = 0;
  236. for (const Data::Asset<RPI::ModelLodAsset>& lod : modelAsset->GetLodAssets())
  237. {
  238. for (const RPI::ModelLodAsset::Mesh& mesh : lod->GetMeshes())
  239. {
  240. const RPI::ModelMaterialSlot::StableId slotId = mesh.GetMaterialSlotId();
  241. const RPI::ModelMaterialSlot& slot = modelAsset->FindMaterialSlot(slotId);
  242. const MaterialAssignmentId generalId = MaterialAssignmentId::CreateFromStableIdOnly(slotId);
  243. labels[generalId] = slot.m_displayName.GetStringView();
  244. const MaterialAssignmentId specificId = MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, slotId);
  245. labels[specificId] = slot.m_displayName.GetStringView();
  246. }
  247. ++lodIndex;
  248. }
  249. }
  250. return labels;
  251. }
  252. MaterialAssignmentId GetMaterialSlotIdFromLodAsset(
  253. const Data::Asset<AZ::RPI::ModelAsset> modelAsset,
  254. const Data::Asset<AZ::RPI::ModelLodAsset>& lodAsset,
  255. const MaterialAssignmentLodIndex lodIndex,
  256. const AZStd::string& labelFilter)
  257. {
  258. for (const AZ::RPI::ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes())
  259. {
  260. const auto slotId = mesh.GetMaterialSlotId();
  261. const auto& slot = modelAsset->FindMaterialSlot(slotId);
  262. if (AZ::StringFunc::Contains(slot.m_displayName.GetCStr(), labelFilter, true))
  263. {
  264. return MaterialAssignmentId::CreateFromLodAndStableId(lodIndex, slotId);
  265. }
  266. }
  267. return MaterialAssignmentId();
  268. }
  269. MaterialAssignmentId GetMaterialSlotIdFromModelAsset(
  270. const Data::Asset<AZ::RPI::ModelAsset> modelAsset, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter)
  271. {
  272. if (modelAsset.IsReady() && !labelFilter.empty())
  273. {
  274. if (lodFilter < modelAsset->GetLodCount())
  275. {
  276. return GetMaterialSlotIdFromLodAsset(modelAsset, modelAsset->GetLodAssets()[lodFilter], lodFilter, labelFilter);
  277. }
  278. for (size_t lodIndex = 0; lodIndex < modelAsset->GetLodCount(); ++lodIndex)
  279. {
  280. const MaterialAssignmentId result = GetMaterialSlotIdFromLodAsset(
  281. modelAsset, modelAsset->GetLodAssets()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter);
  282. if (!result.IsDefault())
  283. {
  284. return result;
  285. }
  286. }
  287. }
  288. return MaterialAssignmentId();
  289. }
  290. template<typename T>
  291. AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueNumericType(const AZStd::any& value)
  292. {
  293. if (value.is<int32_t>())
  294. {
  295. return aznumeric_cast<T>(AZStd::any_cast<int32_t>(value));
  296. }
  297. if (value.is<uint32_t>())
  298. {
  299. return aznumeric_cast<T>(AZStd::any_cast<uint32_t>(value));
  300. }
  301. if (value.is<float>())
  302. {
  303. return aznumeric_cast<T>(AZStd::any_cast<float>(value));
  304. }
  305. if (value.is<double>())
  306. {
  307. return aznumeric_cast<T>(AZStd::any_cast<double>(value));
  308. }
  309. return AZ::RPI::MaterialPropertyValue::FromAny(value);
  310. }
  311. AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript(
  312. const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value)
  313. {
  314. switch (propertyDescriptor->GetDataType())
  315. {
  316. case AZ::RPI::MaterialPropertyDataType::Enum:
  317. if (value.is<AZ::Name>())
  318. {
  319. return propertyDescriptor->GetEnumValue(AZStd::any_cast<AZ::Name>(value));
  320. }
  321. if (value.is<AZStd::string>())
  322. {
  323. return propertyDescriptor->GetEnumValue(AZ::Name(AZStd::any_cast<AZStd::string>(value)));
  324. }
  325. return ConvertMaterialPropertyValueNumericType<uint32_t>(value);
  326. case AZ::RPI::MaterialPropertyDataType::Int:
  327. return ConvertMaterialPropertyValueNumericType<int32_t>(value);
  328. case AZ::RPI::MaterialPropertyDataType::UInt:
  329. return ConvertMaterialPropertyValueNumericType<uint32_t>(value);
  330. case AZ::RPI::MaterialPropertyDataType::Float:
  331. return ConvertMaterialPropertyValueNumericType<float>(value);
  332. case AZ::RPI::MaterialPropertyDataType::Bool:
  333. return ConvertMaterialPropertyValueNumericType<bool>(value);
  334. default:
  335. break;
  336. }
  337. return AZ::RPI::MaterialPropertyValue::FromAny(value);
  338. }
  339. AZ::Render::CustomMaterialMap ConvertToCustomMaterialMap(const AZ::Render::MaterialAssignmentMap& materials)
  340. {
  341. AZ::Render::CustomMaterialMap customMaterials;
  342. customMaterials.reserve(materials.size());
  343. for (const auto& materialAssignment : materials)
  344. {
  345. customMaterials.emplace(
  346. AZ::Render::CustomMaterialId{ materialAssignment.first.m_lodIndex, materialAssignment.first.m_materialSlotStableId },
  347. AZ::Render::CustomMaterialInfo{ materialAssignment.second.m_materialInstance, materialAssignment.second.m_matModUvOverrides });
  348. }
  349. return customMaterials;
  350. }
  351. } // namespace Render
  352. } // namespace AZ