LightCullingExampleComponent.cpp 39 KB

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