DynamicMaterialTestComponent.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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 <DynamicMaterialTestComponent.h>
  9. #include <SampleComponentManager.h>
  10. #include <SampleComponentConfig.h>
  11. #include <Automation/ScriptableImGui.h>
  12. #include <Automation/ScriptRunnerBus.h>
  13. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  14. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  15. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  16. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  17. #include <AzCore/Component/Entity.h>
  18. #include <AzCore/Debug/Timer.h>
  19. #include <AzCore/Math/Random.h>
  20. #include <RHI/BasicRHIComponent.h>
  21. AZ_DECLARE_BUDGET(AtomSampleViewer);
  22. namespace AtomSampleViewer
  23. {
  24. using namespace AZ;
  25. using namespace RPI;
  26. void DynamicMaterialTestComponent::Reflect(ReflectContext* context)
  27. {
  28. if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
  29. {
  30. serializeContext->Class<DynamicMaterialTestComponent, EntityLatticeTestComponent>()
  31. ->Version(0)
  32. ;
  33. }
  34. }
  35. DynamicMaterialTestComponent::DynamicMaterialTestComponent()
  36. : m_imguiSidebar("@user@/DynamicMaterialTestComponent/sidebar.xml")
  37. , m_compileTimer(CompileTimerQueueSize, CompileTimerQueueSize)
  38. {
  39. }
  40. void DynamicMaterialTestComponent::Activate()
  41. {
  42. TickBus::Handler::BusConnect();
  43. m_imguiSidebar.Activate();
  44. InitMaterialConfigs();
  45. // This was the original max before some changes that increased ENTITY_LATTEST_TEST_COMPONENT_MAX to 100.
  46. // DynamicMaterialTest was crashing (out of descriptors) at 50x50x9 so we put the limit back to 25^3 until that's addressed.
  47. Base::SetLatticeMaxDimension(25);
  48. Base::Activate();
  49. m_currentTime = 0.0f;
  50. }
  51. void DynamicMaterialTestComponent::Deactivate()
  52. {
  53. TickBus::Handler::BusDisconnect();
  54. m_imguiSidebar.Deactivate();
  55. Base::Deactivate();
  56. }
  57. void DynamicMaterialTestComponent::PrepareCreateLatticeInstances(uint32_t instanceCount)
  58. {
  59. const char* modelPath = "objects/shaderball_simple.azmodel";
  60. Data::AssetId modelAssetId;
  61. Data::AssetCatalogRequestBus::BroadcastResult(
  62. modelAssetId, &Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  63. modelPath, azrtti_typeid<ModelAsset>(), false);
  64. AZ_Assert(modelAssetId.IsValid(), "Failed to get model asset id: %s", modelPath);
  65. m_modelAsset.Create(modelAssetId);
  66. m_meshHandles.reserve(instanceCount);
  67. m_materials.reserve(instanceCount);
  68. // Give the models time to load before continuing scripts
  69. ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScript);
  70. m_waitingForMeshes = true;
  71. }
  72. void DynamicMaterialTestComponent::CreateLatticeInstance(const Transform& transform)
  73. {
  74. AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset = m_materialConfigs[m_currentMaterialConfig].m_materialAsset;
  75. AZ::Data::Instance<AZ::RPI::Material> material = Material::Create(materialAsset);
  76. Render::MaterialAssignmentMap materialMap;
  77. Render::MaterialAssignment& defaultMaterial = materialMap[Render::DefaultMaterialAssignmentId];
  78. defaultMaterial.m_materialAsset = materialAsset;
  79. defaultMaterial.m_materialInstance = material;
  80. Render::MeshHandleDescriptor meshDescriptor;
  81. meshDescriptor.m_modelAsset = m_modelAsset;
  82. meshDescriptor.m_isRayTracingEnabled = false;
  83. auto meshHandle = GetMeshFeatureProcessor()->AcquireMesh(meshDescriptor, materialMap);
  84. GetMeshFeatureProcessor()->SetTransform(meshHandle, transform);
  85. Data::Instance<RPI::Model> model = GetMeshFeatureProcessor()->GetModel(meshHandle);
  86. if (model)
  87. {
  88. m_loadedMeshCounter++;
  89. }
  90. else
  91. {
  92. m_meshLoadEventHandlers.push_back(AZ::Render::MeshFeatureProcessorInterface::ModelChangedEvent::Handler
  93. {
  94. [this](AZ::Data::Instance<AZ::RPI::Model> model) { m_loadedMeshCounter++; }
  95. });
  96. GetMeshFeatureProcessor()->ConnectModelChangeEventHandler(meshHandle, m_meshLoadEventHandlers.back());
  97. }
  98. m_meshHandles.push_back(AZStd::move(meshHandle));
  99. m_materials.push_back(material);
  100. }
  101. void DynamicMaterialTestComponent::DestroyLatticeInstances()
  102. {
  103. for (auto& meshHandle : m_meshHandles)
  104. {
  105. GetMeshFeatureProcessor()->ReleaseMesh(meshHandle);
  106. }
  107. m_meshHandles.clear();
  108. m_materials.clear();
  109. m_loadedMeshCounter = 0;
  110. m_waitingForMeshes = false;
  111. m_meshLoadEventHandlers.clear();
  112. }
  113. void DynamicMaterialTestComponent::InitMaterialConfigs()
  114. {
  115. using namespace AZ::RPI;
  116. MaterialConfig config;
  117. config.m_name = "Default StandardPBR Material";
  118. config.m_materialAsset = AssetUtils::GetAssetByProductPath<MaterialAsset>(DefaultPbrMaterialPath, AssetUtils::TraceLevel::Assert);
  119. config.m_updateLatticeMaterials = [this]() { UpdateStandardPbrColors(); };
  120. m_materialConfigs.push_back(config);
  121. config.m_name = "C++ Functor Test Material";
  122. config.m_materialAsset = AssetUtils::GetAssetByProductPath<MaterialAsset>("materials/dynamicmaterialtest/emissivewithcppfunctors.azmaterial", AssetUtils::TraceLevel::Assert);
  123. config.m_updateLatticeMaterials = [this]() { UpdateEmissiveMaterialIntensity(); };
  124. m_materialConfigs.push_back(config);
  125. config.m_name = "Lua Functor Test Material";
  126. config.m_materialAsset = AssetUtils::GetAssetByProductPath<MaterialAsset>("materials/dynamicmaterialtest/emissivewithluafunctors.azmaterial", AssetUtils::TraceLevel::Assert);
  127. config.m_updateLatticeMaterials = [this]() { UpdateEmissiveMaterialIntensity(); };
  128. m_materialConfigs.push_back(config);
  129. m_currentMaterialConfig = 0;
  130. }
  131. void DynamicMaterialTestComponent::UpdateStandardPbrColors()
  132. {
  133. static const Color colorOptions[] =
  134. {
  135. Color(1.0f, 0.0f, 0.0f, 1.0f),
  136. Color(0.0f, 1.0f, 0.0f, 1.0f),
  137. Color(0.0f, 0.0f, 1.0f, 1.0f),
  138. Color(1.0f, 1.0f, 0.0f, 1.0f),
  139. Color(0.0f, 1.0f, 1.0f, 1.0f),
  140. Color(1.0f, 0.0f, 1.0f, 1.0f),
  141. };
  142. // Create a new SimpleLcgRandom every time to keep a consistent seed and consistent color selection.
  143. SimpleLcgRandom random;
  144. for (int i = 0; i < m_meshHandles.size(); ++i)
  145. {
  146. auto& material = m_materials[i];
  147. static const float CylesPerSecond = 0.5f;
  148. const float t = aznumeric_cast<float>(sin(m_currentTime * CylesPerSecond * AZ::Constants::TwoPi) * 0.5f + 0.5f);
  149. const int colorIndexA = random.GetRandom() % AZ_ARRAY_SIZE(colorOptions);
  150. int colorIndexB = colorIndexA;
  151. while (colorIndexA == colorIndexB)
  152. {
  153. colorIndexB = random.GetRandom() % AZ_ARRAY_SIZE(colorOptions);
  154. }
  155. const Color color = colorOptions[colorIndexA] * t + colorOptions[colorIndexB] * (1.0f - t);
  156. MaterialPropertyIndex colorProperty = material->FindPropertyIndex(AZ::Name{"baseColor.color"});
  157. material->SetPropertyValue(colorProperty, color);
  158. }
  159. }
  160. void DynamicMaterialTestComponent::UpdateEmissiveMaterialIntensity()
  161. {
  162. for (int i = 0; i < m_meshHandles.size(); ++i)
  163. {
  164. auto& meshHandle = m_meshHandles[i];
  165. auto& material = m_materials[i];
  166. const float distance = GetMeshFeatureProcessor()->GetTransform(meshHandle).GetTranslation().GetLengthEstimate();
  167. static const float DistanceScale = 0.02f;
  168. static const float CylesPerSecond = 0.5f;
  169. const float t = aznumeric_cast<float>(sin((DistanceScale * distance + m_currentTime * CylesPerSecond) * AZ::Constants::TwoPi) * 0.5f + 0.5f);
  170. static const float MinIntensity = 1.0f;
  171. static const float MaxIntensity = 4.0f;
  172. const float intensity = AZ::Lerp(MinIntensity, MaxIntensity, t);
  173. MaterialPropertyIndex intensityProperty = material->FindPropertyIndex(AZ::Name{"emissive.intensity"});
  174. material->SetPropertyValue(intensityProperty, intensity);
  175. }
  176. }
  177. void DynamicMaterialTestComponent::CompileMaterials()
  178. {
  179. AZ::Debug::Timer timer;
  180. timer.Stamp();
  181. for (auto& material : m_materials)
  182. {
  183. material->Compile();
  184. }
  185. m_compileTimer.PushValue(timer.GetDeltaTimeInSeconds() * 1'000'000);
  186. }
  187. void DynamicMaterialTestComponent::OnTick(float deltaTime, ScriptTimePoint /*scriptTime*/)
  188. {
  189. AZ_PROFILE_FUNCTION(AtomSampleViewer);
  190. if (m_waitingForMeshes)
  191. {
  192. if(m_loadedMeshCounter == m_meshHandles.size())
  193. {
  194. m_waitingForMeshes = false;
  195. ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
  196. // Reset the clock so we get consistent animation for scripts
  197. m_currentTime = 0;
  198. }
  199. }
  200. AZ_Assert(m_loadedMeshCounter <= m_meshHandles.size(), "Mesh load handlers were called multiple times?");
  201. bool updateMaterials = false;
  202. if (!m_pause)
  203. {
  204. m_currentTime += deltaTime;
  205. updateMaterials = true;
  206. }
  207. if (m_imguiSidebar.Begin())
  208. {
  209. RenderImGuiLatticeControls();
  210. ImGui::Separator();
  211. bool configChanged = false;
  212. for (int i = 0; i < m_materialConfigs.size(); ++i)
  213. {
  214. configChanged = configChanged || ScriptableImGui::RadioButton(m_materialConfigs[i].m_name.c_str(), &m_currentMaterialConfig, i);
  215. }
  216. if (configChanged)
  217. {
  218. RebuildLattice();
  219. }
  220. ImGui::Separator();
  221. ScriptableImGui::Checkbox("Pause", &m_pause);
  222. if (ScriptableImGui::Button("Reset Clock"))
  223. {
  224. m_currentTime = 0;
  225. updateMaterials = true;
  226. }
  227. ImGui::Separator();
  228. ImGui::Text("%d unique objects", aznumeric_cast<int32_t>(m_meshHandles.size()));
  229. ImGui::Text("Total Material Compile Time:");
  230. ImGuiHistogramQueue::WidgetSettings settings;
  231. settings.m_units = "microseconds";
  232. m_compileTimer.Tick(deltaTime, settings);
  233. ImGui::Text("Average per Material: %4.2f", m_compileTimer.GetDisplayedAverage() / m_materials.size());
  234. ImGui::Separator();
  235. m_imguiSidebar.End();
  236. }
  237. if (updateMaterials)
  238. {
  239. m_materialConfigs[m_currentMaterialConfig].m_updateLatticeMaterials();
  240. }
  241. // Even if materials weren't changed on this frame, they still might need to be compiled to apply changes
  242. // from a previous frame. This could be the result of a material that was just loaded or reinitialized on
  243. // the previous frame, possibly caused by a shader variant hot-loading.
  244. CompileMaterials();
  245. }
  246. } // namespace AtomSampleViewer