MaterialAssetBuilderComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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 <Model/MaterialAssetBuilderComponent.h>
  9. #include <Material/MaterialBuilderUtils.h>
  10. #include <AzCore/Asset/AssetCommon.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/Math/Color.h>
  13. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  14. #include <SceneAPI/SceneCore/Containers/Scene.h>
  15. #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
  16. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  17. #include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
  18. #include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
  19. #include <SceneAPI/SceneCore/Events/ExportEventContext.h>
  20. #include <SceneAPI/SceneCore/Events/ExportProductList.h>
  21. #include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
  22. #include <SceneAPI/SceneCore/DataTypes/GraphData/IMaterialData.h>
  23. #include <Atom/RPI.Edit/Material/MaterialSourceData.h>
  24. #include <Atom/RPI.Edit/Material/MaterialConverterBus.h>
  25. #include <Atom/RPI.Edit/Material/MaterialUtils.h>
  26. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  27. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  28. #include <AzCore/Settings/SettingsRegistry.h>
  29. namespace AZ
  30. {
  31. namespace RPI
  32. {
  33. [[maybe_unused]] static const char* MaterialExporterName = "Scene Material Builder";
  34. void MaterialAssetDependenciesComponent::Reflect(ReflectContext* context)
  35. {
  36. if (auto* serialize = azrtti_cast<SerializeContext*>(context))
  37. {
  38. serialize->Class<MaterialAssetDependenciesComponent, Component>()
  39. ->Version(5) // <<<<< If you have made changes to material code and need to force scene files to be reprocessed, this probably is
  40. // NOT the version number you want to bump . What you're looking for is MaterialAssetBuilderComponent::Reflect below.
  41. ->Attribute(Edit::Attributes::SystemComponentTags, AZStd::vector<Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }));
  42. }
  43. }
  44. void MaterialAssetDependenciesComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  45. {
  46. provided.push_back(AZ_CRC_CE("MaterialAssetDependenciesService"));
  47. }
  48. void MaterialAssetDependenciesComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  49. {
  50. incompatible.push_back(AZ_CRC_CE("MaterialAssetDependenciesService"));
  51. }
  52. void MaterialAssetDependenciesComponent::Activate()
  53. {
  54. SceneAPI::SceneBuilderDependencyBus::Handler::BusConnect();
  55. }
  56. void MaterialAssetDependenciesComponent::Deactivate()
  57. {
  58. SceneAPI::SceneBuilderDependencyBus::Handler::BusDisconnect();
  59. }
  60. void MaterialAssetDependenciesComponent::ReportJobDependencies(SceneAPI::JobDependencyList& jobDependencyList, const char* platformIdentifier)
  61. {
  62. bool conversionEnabled = false;
  63. RPI::MaterialConverterBus::BroadcastResult(conversionEnabled, &RPI::MaterialConverterBus::Events::IsEnabled);
  64. // Right now, scene file importing only supports a single material type, once that changes, this will have to be re-designed, see ATOM-3554
  65. AZStd::string materialTypePath;
  66. RPI::MaterialConverterBus::BroadcastResult(materialTypePath, &RPI::MaterialConverterBus::Events::GetMaterialTypePath);
  67. if (conversionEnabled && !materialTypePath.empty())
  68. {
  69. AssetBuilderSDK::SourceFileDependency materialTypeSource;
  70. materialTypeSource.m_sourceFileDependencyPath = materialTypePath;
  71. AssetBuilderSDK::JobDependency jobDependency;
  72. jobDependency.m_jobKey = "Material Type Builder (Final Stage)";
  73. jobDependency.m_sourceFile = materialTypeSource;
  74. jobDependency.m_platformIdentifier = platformIdentifier;
  75. jobDependency.m_productSubIds.push_back(0);
  76. jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
  77. jobDependencyList.push_back(jobDependency);
  78. }
  79. }
  80. void MaterialAssetDependenciesComponent::AddFingerprintInfo(AZStd::set<AZStd::string>& fingerprintInfo)
  81. {
  82. // This will cause scene files to be reprocessed whenever the global MaterialConverter settings change.
  83. AZStd::string conversionInfo = "[Material conversion info missing]";
  84. RPI::MaterialConverterBus::BroadcastResult(conversionInfo, &RPI::MaterialConverterBus::Events::GetFingerprintInfo);
  85. fingerprintInfo.insert(conversionInfo);
  86. }
  87. void MaterialAssetBuilderComponent::Reflect(ReflectContext* context)
  88. {
  89. if (auto* serialize = azrtti_cast<SerializeContext*>(context))
  90. {
  91. serialize->Class<MaterialAssetBuilderComponent, SceneAPI::SceneCore::ExportingComponent>()
  92. ->Version(26); // Add productSubId dependency for materialtype
  93. }
  94. }
  95. Data::Asset<MaterialAsset> MaterialAssetBuilderComponent::GetDefaultMaterialAsset() const
  96. {
  97. AZStd::string defaultMaterialPath;
  98. RPI::MaterialConverterBus::BroadcastResult(defaultMaterialPath, &RPI::MaterialConverterBus::Events::GetDefaultMaterialPath);
  99. if (defaultMaterialPath.empty())
  100. {
  101. return {};
  102. }
  103. else
  104. {
  105. auto defaultMaterialAssetId = RPI::AssetUtils::MakeAssetId(defaultMaterialPath, 0);
  106. if (!defaultMaterialAssetId.IsSuccess())
  107. {
  108. AZ_Error("MaterialAssetBuilderComponent", false, "Could not find asset '%s'", defaultMaterialPath.c_str());
  109. return {};
  110. }
  111. else
  112. {
  113. return Data::AssetManager::Instance().CreateAsset<RPI::MaterialAsset>(defaultMaterialAssetId.GetValue(), Data::AssetLoadBehaviorNamespace::PreLoad);
  114. }
  115. }
  116. }
  117. uint32_t MaterialAssetBuilderComponent::GetMaterialAssetSubId(uint64_t materialUid)
  118. {
  119. // [GFX TODO] I am suggesting we use the first two 16bits for different kind of assets generated from a Scene
  120. // For example, 0x10000 for mesh, 0x20000 for material, 0x30000 for animation, 0x40000 for scene graph and etc.
  121. // so the subid can be evaluated for reference across different assets generate within this scene file.
  122. /*const uint32_t materialPrefix = 0x20000;
  123. AZ_Assert(materialPrefix > materialId, "materialId should be smaller than materialPrefix");
  124. return materialPrefix + materialId;*/
  125. return static_cast<uint32_t>(materialUid);
  126. }
  127. MaterialAssetBuilderComponent::MaterialAssetBuilderComponent()
  128. {
  129. // This setting disables material output (for automated testing purposes) to allow an FBX file to be processed without including
  130. // the dozens of dependencies required to process a material.
  131. auto settingsRegistry = AZ::SettingsRegistry::Get();
  132. bool skipAtomOutput = false;
  133. if (settingsRegistry && settingsRegistry->Get(skipAtomOutput, "/O3DE/SceneAPI/AssetImporter/SkipAtomOutput") && skipAtomOutput)
  134. {
  135. return;
  136. }
  137. BindToCall(&MaterialAssetBuilderComponent::BuildMaterials);
  138. }
  139. SceneAPI::Events::ProcessingResult MaterialAssetBuilderComponent::ConvertMaterials(MaterialAssetBuilderContext& context) const
  140. {
  141. const auto& scene = context.m_scene;
  142. const Uuid sourceSceneUuid = scene.GetSourceGuid();
  143. const auto& sceneGraph = scene.GetGraph();
  144. auto names = sceneGraph.GetNameStorage();
  145. auto content = sceneGraph.GetContentStorage();
  146. auto pairView = SceneAPI::Containers::Views::MakePairView(names, content);
  147. auto view = SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<
  148. SceneAPI::Containers::Views::BreadthFirst>(
  149. sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true);
  150. struct NamedMaterialSourceData
  151. {
  152. MaterialSourceData m_data;
  153. AZStd::string m_name;
  154. };
  155. AZStd::unordered_map<uint64_t, NamedMaterialSourceData> materialSourceDataByUid;
  156. for (const auto& viewIt : view)
  157. {
  158. if (viewIt.second == nullptr)
  159. {
  160. continue;
  161. }
  162. if (azrtti_istypeof<SceneAPI::DataTypes::IMaterialData>(viewIt.second.get()))
  163. {
  164. // Convert MaterialData to MaterialSourceData and add it to materialSourceDataByUid
  165. auto materialData = AZStd::static_pointer_cast<const SceneAPI::DataTypes::IMaterialData>(viewIt.second);
  166. uint64_t materialUid = materialData->GetUniqueId();
  167. if (materialSourceDataByUid.find(materialUid) != materialSourceDataByUid.end())
  168. {
  169. continue;
  170. }
  171. // The source data for generating material asset
  172. MaterialSourceData sourceData;
  173. // User hook to create their materials based on the data from the scene pipeline
  174. bool result = false;
  175. RPI::MaterialConverterBus::BroadcastResult(result, &RPI::MaterialConverterBus::Events::ConvertMaterial, *materialData, sourceData);
  176. if (result)
  177. {
  178. materialSourceDataByUid[materialUid] = { AZStd::move(sourceData), materialData->GetMaterialName() };
  179. }
  180. }
  181. }
  182. // Build material assets.
  183. for (auto& itr : materialSourceDataByUid)
  184. {
  185. const uint64_t materialUid = itr.first;
  186. Data::AssetId assetId(sourceSceneUuid, GetMaterialAssetSubId(materialUid));
  187. auto materialSourceData = itr.second;
  188. Outcome<Data::Asset<MaterialAsset>> result = materialSourceData.m_data.CreateMaterialAsset(assetId, "", false);
  189. if (result.IsSuccess())
  190. {
  191. context.m_outputMaterialsByUid[materialUid] = { result.GetValue(), materialSourceData.m_name };
  192. }
  193. else
  194. {
  195. AZ_Error(MaterialExporterName, false, "Create material failed");
  196. return SceneAPI::Events::ProcessingResult::Failure;
  197. }
  198. }
  199. return SceneAPI::Events::ProcessingResult::Success;
  200. }
  201. SceneAPI::Events::ProcessingResult MaterialAssetBuilderComponent::AssignDefaultMaterials(MaterialAssetBuilderContext& context) const
  202. {
  203. Data::Asset<MaterialAsset> defaultMaterialAsset = GetDefaultMaterialAsset();
  204. if (!defaultMaterialAsset.GetId().IsValid())
  205. {
  206. AZ_Warning("MaterialAssetBuilderComponent", false, "Material conversion is disabled but no default material was provided. The model will likely be invisible by default.");
  207. // Return success because it's just a warning.
  208. return SceneAPI::Events::ProcessingResult::Success;
  209. }
  210. const auto& scene = context.m_scene;
  211. const auto& sceneGraph = scene.GetGraph();
  212. auto names = sceneGraph.GetNameStorage();
  213. auto content = sceneGraph.GetContentStorage();
  214. auto pairView = SceneAPI::Containers::Views::MakePairView(names, content);
  215. auto view = SceneAPI::Containers::Views::MakeSceneGraphDownwardsView<
  216. SceneAPI::Containers::Views::BreadthFirst>(
  217. sceneGraph, sceneGraph.GetRoot(), pairView.cbegin(), true);
  218. for (const auto& viewIt : view)
  219. {
  220. if (viewIt.second == nullptr)
  221. {
  222. continue;
  223. }
  224. if (azrtti_istypeof<SceneAPI::DataTypes::IMaterialData>(viewIt.second.get()))
  225. {
  226. auto materialData = AZStd::static_pointer_cast<const SceneAPI::DataTypes::IMaterialData>(viewIt.second);
  227. uint64_t materialUid = materialData->GetUniqueId();
  228. context.m_outputMaterialsByUid[materialUid] = { defaultMaterialAsset, materialData->GetMaterialName() };
  229. }
  230. }
  231. return SceneAPI::Events::ProcessingResult::Success;
  232. }
  233. SceneAPI::Events::ProcessingResult MaterialAssetBuilderComponent::BuildMaterials(MaterialAssetBuilderContext& context) const
  234. {
  235. bool conversionEnabled = false;
  236. RPI::MaterialConverterBus::BroadcastResult(conversionEnabled, &RPI::MaterialConverterBus::Events::IsEnabled);
  237. if (conversionEnabled)
  238. {
  239. return ConvertMaterials(context);
  240. }
  241. else
  242. {
  243. return AssignDefaultMaterials(context);
  244. }
  245. }
  246. } // namespace RPI
  247. } // namespace AZ