LightCullingExampleComponent.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. /*
  2. * 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.
  3. *
  4. * SPDX-License-Identifier: Apache-2.0 OR MIT
  5. *
  6. */
  7. #include <LightCullingExampleComponent.h>
  8. #include <SampleComponentConfig.h>
  9. #include <Automation/ScriptableImGui.h>
  10. #include <Automation/ScriptRunnerBus.h>
  11. #include <Atom/Component/DebugCamera/CameraControllerBus.h>
  12. #include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
  13. #include <imgui/imgui.h>
  14. #include <Atom/Feature/CoreLights/PhotometricValue.h>
  15. #include <Atom/Feature/CoreLights/CoreLightsConstants.h>
  16. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  17. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  18. #include <AzCore/Asset/AssetManagerBus.h>
  19. #include <AzCore/Asset/AssetCommon.h>
  20. #include <AzCore/Component/TransformBus.h>
  21. #include <AzCore/Math/Transform.h>
  22. #include <AzCore/Math/Obb.h>
  23. #include <AzCore/Math/Matrix3x4.h>
  24. #include <AzFramework/Components/CameraBus.h>
  25. #include <Atom/RPI.Public/RPISystemInterface.h>
  26. #include <Atom/RPI.Public/Scene.h>
  27. #include <Atom/RPI.Public/RenderPipeline.h>
  28. #include <Atom/RPI.Public/View.h>
  29. #include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
  30. #include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
  31. #include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
  32. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  33. #include <RHI/BasicRHIComponent.h>
  34. namespace AtomSampleViewer
  35. {
  36. using namespace AZ;
  37. using namespace AZ::Render;
  38. using namespace AZ::RPI;
  39. static const char* WorldModelName = "Objects/Sponza.azmodel";
  40. static const char* TransparentModelName = "Objects/ShaderBall_simple.azmodel";
  41. static const char* TransparentMaterialName = "materials/DefaultPBRTransparent.azmaterial";
  42. static const char* DecalMaterialPath = "materials/Decal/airship_tail_01_decal.azmaterial";
  43. static const float TimingSmoothingFactor = 0.9f;
  44. static const size_t MaxNumLights = 1024;
  45. static const float AuxGeomDebugAlpha = 0.5f;
  46. static const AZ::Vector3 CameraStartPosition = AZ::Vector3(-12.f, -35.5f, 0.7438f);
  47. AZ::Color LightCullingExampleComponent::GetRandomColor()
  48. {
  49. static const AZStd::vector<AZ::Color> colors = {
  50. AZ::Colors::Red,
  51. AZ::Colors::Blue,
  52. AZ::Colors::Green,
  53. AZ::Colors::White,
  54. AZ::Colors::Purple,
  55. AZ::Colors::MediumAquamarine,
  56. AZ::Colors::Fuchsia,
  57. AZ::Colors::Thistle,
  58. AZ::Colors::LightGoldenrodYellow,
  59. AZ::Colors::BlanchedAlmond,
  60. AZ::Colors::PapayaWhip,
  61. AZ::Colors::Bisque,
  62. AZ::Colors::Chocolate,
  63. AZ::Colors::MintCream,
  64. AZ::Colors::LemonChiffon,
  65. AZ::Colors::Plum
  66. };
  67. int randomNumber = m_random.GetRandom() % colors.size();
  68. AZ::Color color = colors[randomNumber];
  69. color.SetA(AuxGeomDebugAlpha);
  70. return color;
  71. }
  72. static AZ::Render::MaterialAssignmentMap CreateMaterialAssignmentMap(const char* materialPath)
  73. {
  74. Render::MaterialAssignmentMap materials;
  75. Render::MaterialAssignment& defaultMaterialAssignment = materials[AZ::Render::DefaultMaterialAssignmentId];
  76. defaultMaterialAssignment.m_materialAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::MaterialAsset>(materialPath, RPI::AssetUtils::TraceLevel::Assert);
  77. defaultMaterialAssignment.m_materialInstance = RPI::Material::FindOrCreate(defaultMaterialAssignment.m_materialAsset);
  78. return materials;
  79. }
  80. LightCullingExampleComponent::LightCullingExampleComponent()
  81. {
  82. m_sampleName = "LightCullingExampleComponent";
  83. // Add some initial lights to illuminate the scene
  84. m_settings[(int)LightType::Point].m_numActive = 150;
  85. m_settings[(int)LightType::Disk].m_intensity = 40.0f;
  86. m_settings[(int)LightType::Capsule].m_intensity = 10.0f;
  87. }
  88. void LightCullingExampleComponent::Reflect(AZ::ReflectContext* context)
  89. {
  90. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  91. {
  92. serializeContext->Class<LightCullingExampleComponent, AZ::Component>()
  93. ->Version(0)
  94. ;
  95. }
  96. }
  97. void LightCullingExampleComponent::Activate()
  98. {
  99. GetFeatureProcessors();
  100. // Don't continue the script until after the models have loaded and lights have been created.
  101. // Use a large timeout because of how slow this level loads in debug mode.
  102. ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScriptWithTimeout, 120.0f);
  103. // preload assets
  104. AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo> assetList = {
  105. {WorldModelName, azrtti_typeid<RPI::ModelAsset>()},
  106. {TransparentModelName, azrtti_typeid<RPI::ModelAsset>()},
  107. {TransparentMaterialName, azrtti_typeid<RPI::MaterialAsset>()},
  108. {DecalMaterialPath, azrtti_typeid<RPI::MaterialAsset>()}
  109. };
  110. PreloadAssets(assetList);
  111. }
  112. void LightCullingExampleComponent::OnAllAssetsReadyActivate()
  113. {
  114. LoadDecalMaterial();
  115. SetupScene();
  116. SetupCamera();
  117. m_imguiSidebar.Activate();
  118. AZ::TickBus::Handler::BusConnect();
  119. // Now that the model and all the lights are initialized, we can allow the script to continue.
  120. ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
  121. }
  122. void LightCullingExampleComponent::SetupCamera()
  123. {
  124. AZ::Debug::CameraControllerRequestBus::Event(
  125. GetCameraEntityId(),
  126. &AZ::Debug::CameraControllerRequestBus::Events::Enable,
  127. azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
  128. SaveCameraConfiguration();
  129. const float FarClipDistance = 16384.0f;
  130. Camera::CameraRequestBus::Event(
  131. GetCameraEntityId(),
  132. &Camera::CameraRequestBus::Events::SetFarClipDistance,
  133. FarClipDistance);
  134. Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, 200.0f);
  135. AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetPosition, Vector3(5.0f, 0.0f, 5.0f));
  136. AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetHeading, AZ::DegToRad(90.0f));
  137. AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetPitch, AZ::DegToRad(-11.936623));
  138. }
  139. void LightCullingExampleComponent::Deactivate()
  140. {
  141. m_decalMaterial = {};
  142. DisableHeatmap();
  143. AZ::TickBus::Handler::BusDisconnect();
  144. m_imguiSidebar.Deactivate();
  145. RestoreCameraConfiguration();
  146. AZ::Debug::CameraControllerRequestBus::Event(
  147. GetCameraEntityId(),
  148. &AZ::Debug::CameraControllerRequestBus::Events::Disable);
  149. DestroyOpaqueModels();
  150. DestroyTransparentModels();
  151. DestroyLightsAndDecals();
  152. }
  153. void LightCullingExampleComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
  154. {
  155. if (m_worldModelAssetLoaded)
  156. {
  157. CalculateSmoothedFPS(deltaTime);
  158. DrawSidebar();
  159. DrawDebuggingHelpers();
  160. UpdateLights();
  161. }
  162. }
  163. void LightCullingExampleComponent::UpdateLights()
  164. {
  165. if (m_refreshLights)
  166. {
  167. DestroyLightsAndDecals();
  168. CreateLightsAndDecals();
  169. m_refreshLights = false;
  170. }
  171. }
  172. void LightCullingExampleComponent::OnModelReady(AZ::Data::Instance<AZ::RPI::Model> model)
  173. {
  174. m_meshChangedHandler.Disconnect();
  175. m_worldModelAssetLoaded = true;
  176. m_worldModelAABB = model->GetModelAsset()->GetAabb();
  177. InitLightArrays();
  178. CreateLightsAndDecals();
  179. MoveCameraToStartPosition();
  180. }
  181. void LightCullingExampleComponent::SaveCameraConfiguration()
  182. {
  183. Camera::CameraRequestBus::EventResult(
  184. m_originalFarClipDistance,
  185. GetCameraEntityId(),
  186. &Camera::CameraRequestBus::Events::GetFarClipDistance);
  187. }
  188. void LightCullingExampleComponent::RestoreCameraConfiguration()
  189. {
  190. Camera::CameraRequestBus::Event(
  191. GetCameraEntityId(),
  192. &Camera::CameraRequestBus::Events::SetFarClipDistance,
  193. m_originalFarClipDistance);
  194. }
  195. void LightCullingExampleComponent::SetupScene()
  196. {
  197. using namespace AZ;
  198. CreateTransparentModels();
  199. CreateOpaqueModels();
  200. }
  201. AZ::Vector3 LightCullingExampleComponent::GetRandomPositionInsideWorldModel()
  202. {
  203. AZ::Vector3 randomPosition;
  204. float x = m_random.GetRandomFloat() * m_worldModelAABB.GetXExtent();
  205. float y = m_random.GetRandomFloat() * m_worldModelAABB.GetYExtent();
  206. float z = m_random.GetRandomFloat() * m_worldModelAABB.GetZExtent();
  207. randomPosition.SetX(x);
  208. randomPosition.SetY(y);
  209. randomPosition.SetZ(z);
  210. randomPosition += m_worldModelAABB.GetMin();
  211. return randomPosition;
  212. }
  213. AZ::Vector3 LightCullingExampleComponent::GetRandomDirection()
  214. {
  215. float x = m_random.GetRandomFloat() - 0.5f;
  216. float y = m_random.GetRandomFloat() - 0.5f;
  217. float z = m_random.GetRandomFloat() - 0.5f;
  218. AZ::Vector3 direction(x, y, z);
  219. direction.NormalizeSafe();
  220. return direction;
  221. }
  222. float LightCullingExampleComponent::GetRandomNumber(float low, float high)
  223. {
  224. float r = m_random.GetRandomFloat();
  225. return r * (high - low) + low;
  226. }
  227. void LightCullingExampleComponent::CreatePointLights()
  228. {
  229. for (int i = 0; i < m_settings[(int)LightType::Point].m_numActive; ++i)
  230. {
  231. CreatePointLight(i);
  232. }
  233. }
  234. void LightCullingExampleComponent::CreateDiskLights()
  235. {
  236. for (int i = 0; i < m_settings[(int)LightType::Disk].m_numActive; ++i)
  237. {
  238. CreateDiskLight(i);
  239. }
  240. }
  241. void LightCullingExampleComponent::CreateCapsuleLights()
  242. {
  243. for (int i = 0; i < m_settings[(int)LightType::Capsule].m_numActive; ++i)
  244. {
  245. CreateCapsuleLight(i);
  246. }
  247. }
  248. void LightCullingExampleComponent::CreateQuadLights()
  249. {
  250. for (int i = 0; i < m_settings[(int)LightType::Quad].m_numActive; ++i)
  251. {
  252. CreateQuadLight(i);
  253. }
  254. }
  255. void LightCullingExampleComponent::CreateDecals()
  256. {
  257. for (int i = 0; i < m_settings[(int)LightType::Decal].m_numActive; ++i)
  258. {
  259. CreateDecal(i);
  260. }
  261. }
  262. void LightCullingExampleComponent::DestroyDecals()
  263. {
  264. for (size_t i = 0; i < m_decals.size(); ++i)
  265. {
  266. m_decalFeatureProcessor->ReleaseDecal(m_decals[i].m_decalHandle);
  267. m_decals[i].m_decalHandle = DecalHandle::Null;
  268. }
  269. }
  270. void LightCullingExampleComponent::DrawSidebar()
  271. {
  272. if (!m_imguiSidebar.Begin())
  273. {
  274. return;
  275. }
  276. if (!m_worldModelAssetLoaded)
  277. {
  278. const ImGuiWindowFlags windowFlags =
  279. ImGuiWindowFlags_NoCollapse |
  280. ImGuiWindowFlags_NoResize |
  281. ImGuiWindowFlags_NoMove;
  282. if (ImGui::Begin("Asset", nullptr, windowFlags))
  283. {
  284. ImGui::Text("World Model: %s", m_worldModelAssetLoaded ? "Loaded" : "Loading...");
  285. ImGui::End();
  286. }
  287. }
  288. DrawSidebarTimingSection();
  289. ImGui::Spacing();
  290. ImGui::Separator();
  291. DrawSidebarPointLightsSection(&m_settings[(int)LightType::Point]);
  292. DrawSidebarDiskLightsSection(&m_settings[(int)LightType::Disk]);
  293. DrawSidebarCapsuleLightSection(&m_settings[(int)LightType::Capsule]);
  294. DrawSidebarQuadLightsSections(&m_settings[(int)LightType::Quad]);
  295. DrawSidebarDecalSection(&m_settings[(int)LightType::Decal]);
  296. DrawSidebarHeatmapOpacity();
  297. m_imguiSidebar.End();
  298. }
  299. void LightCullingExampleComponent::DrawSidebarPointLightsSection(LightSettings* lightSettings)
  300. {
  301. ScriptableImGui::ScopedNameContext context{ "Point Lights" };
  302. if (ImGui::CollapsingHeader("Point Lights", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  303. {
  304. m_refreshLights |= ScriptableImGui::SliderInt("Point light count", &lightSettings->m_numActive, 0, MaxNumLights);
  305. m_refreshLights |= ScriptableImGui::SliderFloat("Bulb Radius", &m_bulbRadius, 0.0f, 20.0f);
  306. m_refreshLights |= ScriptableImGui::SliderFloat("Point Intensity", &lightSettings->m_intensity, 0.0f, 200.0f);
  307. m_refreshLights |= ScriptableImGui::Checkbox("Enable automatic light falloff (Point)", &lightSettings->m_enableAutomaticFalloff);
  308. m_refreshLights |= ScriptableImGui::SliderFloat("Point Attenuation Radius", &lightSettings->m_attenuationRadius, 0.0f, 20.0f);
  309. ScriptableImGui::Checkbox("Draw Debug Spheres", &lightSettings->m_enableDebugDraws);
  310. }
  311. }
  312. void LightCullingExampleComponent::DrawSidebarDiskLightsSection(LightSettings* lightSettings)
  313. {
  314. ScriptableImGui::ScopedNameContext context{"Disk Lights"};
  315. if (ImGui::CollapsingHeader("Disk Lights", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  316. {
  317. m_refreshLights |= ScriptableImGui::SliderInt("Disk light count", &lightSettings->m_numActive, 0, MaxNumLights);
  318. m_refreshLights |= ScriptableImGui::SliderFloat("Disk Radius", &m_diskRadius, 0.0f, 20.0f);
  319. m_refreshLights |= ScriptableImGui::SliderFloat("Disk Attenuation Radius", &lightSettings->m_attenuationRadius, 0.0f, 20.0f);
  320. m_refreshLights |= ScriptableImGui::SliderFloat("Disk Intensity", &lightSettings->m_intensity, 0.0f, 200.0f);
  321. m_refreshLights |= ScriptableImGui::Checkbox("Enable Disk Cone", &m_diskConesEnabled);
  322. if (m_diskConesEnabled)
  323. {
  324. m_refreshLights |= ScriptableImGui::SliderFloat("Inner Cone (degrees)", &m_diskInnerConeDegrees, 0.0f, 180.0f);
  325. m_refreshLights |= ScriptableImGui::SliderFloat("Outer Cone (degrees)", &m_diskOuterConeDegrees, 0.0f, 180.0f);
  326. ScriptableImGui::Checkbox("Draw Debug Cones", &lightSettings->m_enableDebugDraws);
  327. }
  328. else
  329. {
  330. ScriptableImGui::Checkbox("Draw disk lights", &lightSettings->m_enableDebugDraws);
  331. }
  332. }
  333. }
  334. void LightCullingExampleComponent::DrawSidebarCapsuleLightSection(LightSettings* lightSettings)
  335. {
  336. ScriptableImGui::ScopedNameContext context{"Capsule Lights"};
  337. if (ImGui::CollapsingHeader("Capsule Lights", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  338. {
  339. m_refreshLights |= ScriptableImGui::SliderInt("Capsule light count", &lightSettings->m_numActive, 0, MaxNumLights);
  340. m_refreshLights |= ScriptableImGui::SliderFloat("Capsule Intensity", &lightSettings->m_intensity, 0.0f, 200.0f);
  341. m_refreshLights |= ScriptableImGui::SliderFloat("Capsule Radius", &m_capsuleRadius, 0.0f, 5.0f);
  342. m_refreshLights |= ScriptableImGui::SliderFloat("Capsule Length", &m_capsuleLength, 0.0f, 20.0f);
  343. ScriptableImGui::Checkbox("Draw capsule lights", &lightSettings->m_enableDebugDraws);
  344. }
  345. }
  346. void LightCullingExampleComponent::DrawSidebarQuadLightsSections(LightSettings* lightSettings)
  347. {
  348. ScriptableImGui::ScopedNameContext context{ "Quad Lights" };
  349. if (ImGui::CollapsingHeader("Quad Lights", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  350. {
  351. m_refreshLights |= ScriptableImGui::SliderInt("Quad light count", &lightSettings->m_numActive, 0, MaxNumLights);
  352. m_refreshLights |= ScriptableImGui::SliderFloat("Quad Attenuation Radius", &lightSettings->m_attenuationRadius, 0.0f, 20.0f);
  353. m_refreshLights |= ScriptableImGui::SliderFloat("Quad light width", &m_quadLightSize[0], 0.0f, 10.0f);
  354. m_refreshLights |= ScriptableImGui::SliderFloat("Quad light height", &m_quadLightSize[1], 0.0f, 10.0f);
  355. m_refreshLights |= ScriptableImGui::Checkbox("Double sided quad", &m_isQuadLightDoubleSided);
  356. m_refreshLights |= ScriptableImGui::Checkbox("Use fast approximation", &m_quadLightsUseFastApproximation);
  357. ScriptableImGui::Checkbox("Draw quad lights", &lightSettings->m_enableDebugDraws);
  358. }
  359. }
  360. void LightCullingExampleComponent::DrawSidebarHeatmapOpacity()
  361. {
  362. ScriptableImGui::ScopedNameContext context{"Heatmap"};
  363. ImGui::Text("Heatmap");
  364. ImGui::Indent();
  365. bool opacityChanged = ScriptableImGui::SliderFloat("Opacity", &m_heatmapOpacity, 0, 1);
  366. ImGui::Unindent();
  367. if (opacityChanged)
  368. {
  369. UpdateHeatmapOpacity();
  370. }
  371. }
  372. void LightCullingExampleComponent::DrawSidebarDecalSection(LightSettings* lightSettings)
  373. {
  374. ScriptableImGui::ScopedNameContext context{"Decals"};
  375. if (ImGui::CollapsingHeader("Decals", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  376. {
  377. m_refreshLights |= ScriptableImGui::SliderInt("Decal count", &lightSettings->m_numActive, 0, MaxNumLights);
  378. ScriptableImGui::Checkbox("Draw decals", &lightSettings->m_enableDebugDraws);
  379. m_refreshLights |= ScriptableImGui::SliderFloat3("Decal Size", m_decalSize.data(), 0.0f, 10.0f);
  380. m_refreshLights |= ScriptableImGui::SliderFloat("Decal Opacity", &m_decalOpacity, 0.0f, 1.0f);
  381. m_refreshLights |= ScriptableImGui::SliderFloat("Decal Angle Attenuation", &m_decalAngleAttenuation, 0.0f, 1.0f);
  382. }
  383. }
  384. void LightCullingExampleComponent::CreatePointLight(int index)
  385. {
  386. auto& light = m_pointLights[index];
  387. AZ_Assert(light.m_lightHandle.IsNull(), "CreatePointLight called on a light that was already created previously");
  388. light.m_lightHandle = m_pointLightFeatureProcessor->AcquireLight();
  389. const LightSettings& settings = m_settings[(int)LightType::Point];
  390. m_pointLightFeatureProcessor->SetPosition(light.m_lightHandle, light.m_position);
  391. m_pointLightFeatureProcessor->SetRgbIntensity(light.m_lightHandle, PhotometricColor<PhotometricUnit::Candela>(settings.m_intensity * light.m_color));
  392. m_pointLightFeatureProcessor->SetBulbRadius(light.m_lightHandle, m_bulbRadius);
  393. float attenuationRadius = settings.m_enableAutomaticFalloff ? AutoCalculateAttenuationRadius(light.m_color, settings.m_intensity) : settings.m_attenuationRadius;
  394. m_pointLightFeatureProcessor->SetAttenuationRadius(light.m_lightHandle, attenuationRadius);
  395. }
  396. void LightCullingExampleComponent::CreateDiskLight(int index)
  397. {
  398. auto& light = m_diskLights[index];
  399. light.m_lightHandle = m_diskLightFeatureProcessor->AcquireLight();
  400. const LightSettings& settings = m_settings[(int)LightType::Disk];
  401. m_diskLightFeatureProcessor->SetDiskRadius(light.m_lightHandle, m_diskRadius);
  402. m_diskLightFeatureProcessor->SetPosition(light.m_lightHandle, light.m_position);
  403. m_diskLightFeatureProcessor->SetRgbIntensity(light.m_lightHandle, PhotometricColor<PhotometricUnit::Candela>(settings.m_intensity * light.m_color));
  404. m_diskLightFeatureProcessor->SetDirection(light.m_lightHandle, light.m_direction);
  405. m_diskLightFeatureProcessor->SetConstrainToConeLight(light.m_lightHandle, m_diskConesEnabled);
  406. if (m_diskConesEnabled)
  407. {
  408. m_diskLightFeatureProcessor->SetConeAngles(light.m_lightHandle, DegToRad(m_diskInnerConeDegrees), DegToRad(m_diskOuterConeDegrees));
  409. }
  410. m_diskLightFeatureProcessor->SetAttenuationRadius(light.m_lightHandle, m_settings[(int)LightType::Disk].m_attenuationRadius);
  411. }
  412. void LightCullingExampleComponent::CreateCapsuleLight(int index)
  413. {
  414. auto& light = m_capsuleLights[index];
  415. AZ_Assert(light.m_lightHandle.IsNull(), "CreateCapsuleLight called on a light that was already created previously");
  416. light.m_lightHandle = m_capsuleLightFeatureProcessor->AcquireLight();
  417. const LightSettings& settings = m_settings[(int)LightType::Capsule];
  418. m_capsuleLightFeatureProcessor->SetAttenuationRadius(light.m_lightHandle, m_settings[(int)LightType::Capsule].m_attenuationRadius);
  419. m_capsuleLightFeatureProcessor->SetRgbIntensity(light.m_lightHandle, PhotometricColor<PhotometricUnit::Candela>(settings.m_intensity * light.m_color));
  420. m_capsuleLightFeatureProcessor->SetCapsuleRadius(light.m_lightHandle, m_capsuleRadius);
  421. AZ::Vector3 startPoint = light.m_position - light.m_direction * m_capsuleLength * 0.5f;
  422. AZ::Vector3 endPoint = light.m_position + light.m_direction * m_capsuleLength * 0.5f;
  423. m_capsuleLightFeatureProcessor->SetCapsuleLineSegment(light.m_lightHandle, startPoint, endPoint);
  424. }
  425. void LightCullingExampleComponent::CreateQuadLight(int index)
  426. {
  427. auto& light = m_quadLights[index];
  428. AZ_Assert(light.m_lightHandle.IsNull(), "CreateQuadLight called on a light that was already created previously");
  429. light.m_lightHandle = m_quadLightFeatureProcessor->AcquireLight();
  430. const LightSettings& settings = m_settings[(int)LightType::Quad];
  431. m_quadLightFeatureProcessor->SetRgbIntensity(light.m_lightHandle, PhotometricColor<PhotometricUnit::Nit>(settings.m_intensity * light.m_color));
  432. const auto orientation = AZ::Quaternion::CreateFromVector3(light.m_direction);
  433. m_quadLightFeatureProcessor->SetOrientation(light.m_lightHandle, orientation);
  434. m_quadLightFeatureProcessor->SetQuadDimensions(light.m_lightHandle, m_quadLightSize[0], m_quadLightSize[1]);
  435. m_quadLightFeatureProcessor->SetLightEmitsBothDirections(light.m_lightHandle, m_isQuadLightDoubleSided);
  436. m_quadLightFeatureProcessor->SetUseFastApproximation(light.m_lightHandle, m_quadLightsUseFastApproximation);
  437. m_quadLightFeatureProcessor->SetAttenuationRadius(light.m_lightHandle, m_settings[(int)LightType::Quad].m_attenuationRadius);
  438. m_quadLightFeatureProcessor->SetPosition(light.m_lightHandle, light.m_position);
  439. }
  440. void LightCullingExampleComponent::CreateDecal(int index)
  441. {
  442. Decal& decal = m_decals[index];
  443. decal.m_decalHandle = m_decalFeatureProcessor->AcquireDecal();
  444. AZ::Render::DecalData decalData;
  445. decalData.m_position = {
  446. { decal.m_position.GetX(), decal.m_position.GetY(), decal.m_position.GetZ() }
  447. };
  448. decalData.m_halfSize = {
  449. {m_decalSize[0] * 0.5f, m_decalSize[1] * 0.5f, m_decalSize[2] * 0.5f}
  450. };
  451. decalData.m_quaternion = {
  452. { decal.m_quaternion.GetX(), decal.m_quaternion.GetY(), decal.m_quaternion.GetZ(), decal.m_quaternion.GetW() }
  453. };
  454. decalData.m_angleAttenuation = m_decalAngleAttenuation;
  455. decalData.m_opacity = m_decalOpacity;
  456. m_decalFeatureProcessor->SetDecalData(decal.m_decalHandle, decalData);
  457. m_decalFeatureProcessor->SetDecalMaterial(decal.m_decalHandle, m_decalMaterial.GetId());
  458. }
  459. void LightCullingExampleComponent::DrawPointLightDebugSpheres(AZ::RPI::AuxGeomDrawPtr auxGeom)
  460. {
  461. const LightSettings& settings = m_settings[(int)LightType::Point];
  462. int numToDraw = AZStd::min(m_settings[(int)LightType::Point].m_numActive, aznumeric_cast<int>(m_pointLights.size()));
  463. for (int i = 0; i < numToDraw; ++i)
  464. {
  465. const auto& light = m_pointLights[i];
  466. if (light.m_lightHandle.IsNull())
  467. {
  468. continue;
  469. }
  470. float radius = settings.m_enableAutomaticFalloff ? AutoCalculateAttenuationRadius(light.m_color, settings.m_intensity) : settings.m_attenuationRadius;
  471. auxGeom->DrawSphere(light.m_position, radius, light.m_color, AZ::RPI::AuxGeomDraw::DrawStyle::Shaded);
  472. }
  473. }
  474. void LightCullingExampleComponent::DrawDecalDebugBoxes(AZ::RPI::AuxGeomDrawPtr auxGeom)
  475. {
  476. int numToDraw = AZStd::min(m_settings[(int)LightType::Decal].m_numActive, aznumeric_cast<int>(m_decals.size()));
  477. for (int i = 0; i < numToDraw; ++i)
  478. {
  479. const Decal& decal = m_decals[i];
  480. if (decal.m_decalHandle.IsValid())
  481. {
  482. AZ::Aabb aabb = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3::CreateZero(), AZ::Vector3::CreateFromFloat3(m_decalSize.data()) * 0.5f);
  483. AZ::Matrix3x4 transform = AZ::Matrix3x4::CreateFromQuaternionAndTranslation(decal.m_quaternion, decal.m_position);
  484. auxGeom->DrawObb(AZ::Obb::CreateFromAabb(aabb), transform, AZ::Colors::White, AZ::RPI::AuxGeomDraw::DrawStyle::Line);
  485. }
  486. }
  487. }
  488. void LightCullingExampleComponent::DrawDiskLightDebugObjects(AZ::RPI::AuxGeomDrawPtr auxGeom)
  489. {
  490. int numToDraw = AZStd::min(m_settings[(int)LightType::Disk].m_numActive, aznumeric_cast<int>(m_diskLights.size()));
  491. for (int i = 0; i < numToDraw; ++i)
  492. {
  493. const auto& light = m_diskLights[i];
  494. if (light.m_lightHandle.IsValid())
  495. {
  496. auxGeom->DrawDisk(light.m_position, light.m_direction, m_diskRadius, light.m_color, AZ::RPI::AuxGeomDraw::DrawStyle::Shaded);
  497. }
  498. }
  499. }
  500. void LightCullingExampleComponent::DrawCapsuleLightDebugObjects(AZ::RPI::AuxGeomDrawPtr auxGeom)
  501. {
  502. int numToDraw = AZStd::min(m_settings[(int)LightType::Capsule].m_numActive, aznumeric_cast<int>(m_capsuleLights.size()));
  503. for (int i = 0; i < numToDraw; ++i)
  504. {
  505. const auto& light = m_capsuleLights[i];
  506. if (light.m_lightHandle.IsValid())
  507. {
  508. auxGeom->DrawCylinder(light.m_position, light.m_direction, m_capsuleRadius, m_capsuleLength, light.m_color);
  509. }
  510. }
  511. }
  512. void LightCullingExampleComponent::DrawQuadLightDebugObjects(AZ::RPI::AuxGeomDrawPtr auxGeom)
  513. {
  514. int numToDraw = AZStd::min(m_settings[(int)LightType::Quad].m_numActive, aznumeric_cast<int>(m_quadLights.size()));
  515. for (int i = 0; i < numToDraw; ++i)
  516. {
  517. const auto& light = m_quadLights[i];
  518. if (light.m_lightHandle.IsValid())
  519. {
  520. auto transform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateFromVector3(light.m_direction), light.m_position);
  521. // Rotate 90 degrees so that the debug draw is aligned properly with the quad light
  522. transform *= AZ::Transform::CreateFromQuaternion(AZ::ConvertEulerRadiansToQuaternion(AZ::Vector3(AZ::Constants::HalfPi, 0.0f, 0.0f)));
  523. auxGeom->DrawQuad(m_quadLightSize[0], m_quadLightSize[1], AZ::Matrix3x4::CreateFromTransform(transform), light.m_color);
  524. }
  525. }
  526. }
  527. void LightCullingExampleComponent::DrawDebuggingHelpers()
  528. {
  529. auto defaultScene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
  530. if (auto auxGeom = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(defaultScene))
  531. {
  532. if (m_settings[(int)LightType::Point].m_enableDebugDraws)
  533. {
  534. DrawPointLightDebugSpheres(auxGeom);
  535. }
  536. if (m_settings[(int)LightType::Disk].m_enableDebugDraws)
  537. {
  538. DrawDiskLightDebugObjects(auxGeom);
  539. }
  540. if (m_settings[(int)LightType::Capsule].m_enableDebugDraws)
  541. {
  542. DrawCapsuleLightDebugObjects(auxGeom);
  543. }
  544. if (m_settings[(int)LightType::Quad].m_enableDebugDraws)
  545. {
  546. DrawQuadLightDebugObjects(auxGeom);
  547. }
  548. if (m_settings[(int)LightType::Decal].m_enableDebugDraws)
  549. {
  550. DrawDecalDebugBoxes(auxGeom);
  551. }
  552. }
  553. }
  554. void LightCullingExampleComponent::DrawSidebarTimingSection()
  555. {
  556. ImGui::Text("Timing");
  557. DrawSidebarTimingSectionCPU();
  558. }
  559. void LightCullingExampleComponent::CalculateSmoothedFPS(float deltaTimeSeconds)
  560. {
  561. float currFPS = 1.0f / deltaTimeSeconds;
  562. m_smoothedFPS = TimingSmoothingFactor * m_smoothedFPS + (1.0f - TimingSmoothingFactor) * currFPS;
  563. }
  564. void LightCullingExampleComponent::DrawSidebarTimingSectionCPU()
  565. {
  566. ImGui::Text("CPU (ms)");
  567. ImGui::Indent();
  568. ImGui::Text("Total: %5.1f", 1000.0f / m_smoothedFPS);
  569. ImGui::Unindent();
  570. }
  571. void LightCullingExampleComponent::InitLightArrays()
  572. {
  573. const auto InitLight = [this](auto& light)
  574. {
  575. light.m_color = GetRandomColor();
  576. light.m_position = GetRandomPositionInsideWorldModel();
  577. light.m_direction = GetRandomDirection();
  578. };
  579. // Set seed to a specific value for each light type so values are consistent between multiple app runs
  580. // And changes to one type don't polute the random numbers for another type.
  581. // Intended for use with the screenshot comparison tool
  582. m_random.SetSeed(0);
  583. m_pointLights.resize(MaxNumLights);
  584. AZStd::for_each(m_pointLights.begin(), m_pointLights.end(), InitLight);
  585. m_random.SetSeed(1);
  586. m_diskLights.resize(MaxNumLights);
  587. AZStd::for_each(m_diskLights.begin(), m_diskLights.end(), InitLight);
  588. m_random.SetSeed(2);
  589. m_capsuleLights.resize(MaxNumLights);
  590. AZStd::for_each(m_capsuleLights.begin(), m_capsuleLights.end(), InitLight);
  591. m_random.SetSeed(3);
  592. m_decals.resize(MaxNumLights);
  593. AZStd::for_each(m_decals.begin(), m_decals.end(), [&](Decal& decal)
  594. {
  595. decal.m_position = GetRandomPositionInsideWorldModel();
  596. decal.m_quaternion = AZ::Quaternion::CreateFromAxisAngle(GetRandomDirection(), GetRandomNumber(0.0f, AZ::Constants::TwoPi));
  597. });
  598. m_random.SetSeed(4);
  599. m_quadLights.resize(MaxNumLights);
  600. AZStd::for_each(m_quadLights.begin(), m_quadLights.end(), InitLight);
  601. }
  602. void LightCullingExampleComponent::GetFeatureProcessors()
  603. {
  604. AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get();
  605. m_pointLightFeatureProcessor = scene->GetFeatureProcessor<AZ::Render::PointLightFeatureProcessorInterface>();
  606. m_diskLightFeatureProcessor = scene->GetFeatureProcessor<AZ::Render::DiskLightFeatureProcessorInterface>();
  607. m_capsuleLightFeatureProcessor = scene->GetFeatureProcessor<AZ::Render::CapsuleLightFeatureProcessorInterface>();
  608. m_quadLightFeatureProcessor = scene->GetFeatureProcessor<AZ::Render::QuadLightFeatureProcessorInterface>();
  609. m_decalFeatureProcessor = scene->GetFeatureProcessor<AZ::Render::DecalFeatureProcessorInterface>();
  610. }
  611. float LightCullingExampleComponent::AutoCalculateAttenuationRadius(const AZ::Color& color, float intensity)
  612. {
  613. // Get combined intensity luma from m_photometricValue, then calculate the radius at which the irradiance will be equal to cutoffIntensity.
  614. static const float CutoffIntensity = 0.1f; // Make this configurable later.
  615. float luminance = AZ::Render::PhotometricValue::GetPerceptualLuminance(color * intensity);
  616. return sqrt(luminance / CutoffIntensity);
  617. }
  618. void LightCullingExampleComponent::MoveCameraToStartPosition()
  619. {
  620. const AZ::Vector3 target = AZ::Vector3::CreateAxisZ();
  621. const AZ::Transform transform = AZ::Transform::CreateLookAt(CameraStartPosition, target, AZ::Transform::Axis::YPositive);
  622. AZ::TransformBus::Event(GetCameraEntityId(), &AZ::TransformBus::Events::SetWorldTM, transform);
  623. }
  624. void LightCullingExampleComponent::UpdateHeatmapOpacity()
  625. {
  626. if (const ScenePtr scene = RPISystemInterface::Get()->GetDefaultScene())
  627. {
  628. if (const RenderPipelinePtr pipeline = scene->GetDefaultRenderPipeline())
  629. {
  630. if (const Ptr<Pass> pass = pipeline->GetRootPass()->FindPassByNameRecursive(AZ::Name("LightCullingHeatmapPass")))
  631. {
  632. if (const Ptr<RenderPass> trianglePass = azrtti_cast<RenderPass*>(pass))
  633. {
  634. trianglePass->SetEnabled(m_heatmapOpacity > 0.0f);
  635. Data::Instance<ShaderResourceGroup> srg = trianglePass->GetShaderResourceGroup();
  636. RHI::ShaderInputConstantIndex opacityIndex = srg->FindShaderInputConstantIndex(AZ::Name("m_heatmapOpacity"));
  637. [[maybe_unused]] bool setOk = srg->SetConstant<float>(opacityIndex, m_heatmapOpacity);
  638. AZ_Warning("LightCullingExampleComponent", setOk, "Unable to set heatmap opacity");
  639. }
  640. }
  641. }
  642. }
  643. }
  644. void LightCullingExampleComponent::DisableHeatmap()
  645. {
  646. m_heatmapOpacity = 0.0f;
  647. UpdateHeatmapOpacity();
  648. }
  649. void LightCullingExampleComponent::CreateOpaqueModels()
  650. {
  651. Data::Asset<RPI::ModelAsset> modelAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::ModelAsset>(WorldModelName, RPI::AssetUtils::TraceLevel::Assert);
  652. auto meshFeatureProcessor = GetMeshFeatureProcessor();
  653. m_meshHandle = meshFeatureProcessor->AcquireMesh(MeshHandleDescriptor{ modelAsset });
  654. meshFeatureProcessor->SetTransform(m_meshHandle, Transform::CreateIdentity());
  655. Data::Instance<RPI::Model> model = meshFeatureProcessor->GetModel(m_meshHandle);
  656. // Loading in the world will probably take a while and I want to grab the AABB afterwards, so hook it up to a a ModelChangeEventHandler
  657. if (model)
  658. {
  659. OnModelReady(model);
  660. }
  661. else
  662. {
  663. meshFeatureProcessor->ConnectModelChangeEventHandler(m_meshHandle, m_meshChangedHandler);
  664. }
  665. }
  666. void LightCullingExampleComponent::CreateTransparentModels()
  667. {
  668. const AZStd::vector<AZ::Vector3 > TransparentModelPositions = { AZ::Vector3(-6.f, -20, 1),
  669. AZ::Vector3(7.5f, 0, 1)
  670. };
  671. Data::Asset<RPI::ModelAsset> transparentModelAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::ModelAsset>(TransparentModelName, RPI::AssetUtils::TraceLevel::Assert);
  672. // Override the shader ball material with a transparent material
  673. Render::MaterialAssignmentMap materialAssignmentMap = CreateMaterialAssignmentMap(TransparentMaterialName);
  674. for (const AZ::Vector3& position : TransparentModelPositions)
  675. {
  676. AZ::Render::MeshFeatureProcessorInterface::MeshHandle meshHandle = GetMeshFeatureProcessor()->AcquireMesh(MeshHandleDescriptor{ transparentModelAsset }, materialAssignmentMap);
  677. GetMeshFeatureProcessor()->SetTransform(meshHandle, Transform::CreateTranslation(position));
  678. m_transparentMeshHandles.push_back(std::move(meshHandle));
  679. }
  680. }
  681. void LightCullingExampleComponent::DestroyOpaqueModels()
  682. {
  683. GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
  684. }
  685. void LightCullingExampleComponent::DestroyTransparentModels()
  686. {
  687. for (auto& elem : m_transparentMeshHandles)
  688. {
  689. GetMeshFeatureProcessor()->ReleaseMesh(elem);
  690. }
  691. m_transparentMeshHandles.clear();
  692. }
  693. void LightCullingExampleComponent::DestroyLightsAndDecals()
  694. {
  695. DestroyLights(m_pointLightFeatureProcessor, m_pointLights);
  696. DestroyLights(m_diskLightFeatureProcessor, m_diskLights);
  697. DestroyLights(m_capsuleLightFeatureProcessor, m_capsuleLights);
  698. DestroyLights(m_quadLightFeatureProcessor, m_quadLights);
  699. DestroyDecals();
  700. }
  701. void LightCullingExampleComponent::CreateLightsAndDecals()
  702. {
  703. CreatePointLights();
  704. CreateDiskLights();
  705. CreateCapsuleLights();
  706. CreateQuadLights();
  707. CreateDecals();
  708. }
  709. void LightCullingExampleComponent::LoadDecalMaterial()
  710. {
  711. const AZ::Data::AssetId id = AZ::RPI::AssetUtils::GetAssetIdForProductPath(DecalMaterialPath);
  712. m_decalMaterial = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::MaterialAsset>(
  713. id, AZ::Data::AssetLoadBehavior::PreLoad);
  714. m_decalMaterial.BlockUntilLoadComplete();
  715. }
  716. } // namespace AtomSampleViewer