LightCullingExampleComponent.cpp 35 KB

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