LightCullingExampleComponent.cpp 36 KB

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