ProjectedShadowFeatureProcessor.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  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 <Shadows/ProjectedShadowFeatureProcessor.h>
  9. #include <AzCore/Math/MatrixUtils.h>
  10. #include <AzCore/Name/NameDictionary.h>
  11. #include <Math/GaussianMathFilter.h>
  12. #include <Atom/RHI/DrawPacketBuilder.h>
  13. #include <Atom/RHI/RHISystemInterface.h>
  14. #include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
  15. #include <Atom/RPI.Public/RenderPipeline.h>
  16. #include <Atom/RPI.Public/RPISystemInterface.h>
  17. #include <Atom/RPI.Public/Scene.h>
  18. #include <Atom/RPI.Public/View.h>
  19. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  20. #include <Atom/RPI.Public/Pass/PassSystem.h>
  21. #include <Atom/RPI.Public/Pass/PassFilter.h>
  22. #include <Atom/RPI.Public/Shader/Shader.h>
  23. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  24. #include <Atom/Feature/Mesh/MeshCommon.h>
  25. #include <CoreLights/Shadow.h>
  26. namespace AZ::Render
  27. {
  28. namespace
  29. {
  30. AZ_CVAR(
  31. bool,
  32. r_cullShadowmapOutsideViewFrustum,
  33. true,
  34. nullptr,
  35. AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::DontDuplicate,
  36. "If set, enables filtering of shadow maps that are outside of the view frustum.");
  37. bool IsShadowmapCullingEnabled()
  38. {
  39. bool cullShadowmapOutsideViewFrustum = true;
  40. if (auto* console = AZ::Interface<AZ::IConsole>::Get())
  41. {
  42. console->GetCvarValue("r_cullShadowmapOutsideViewFrustum", cullShadowmapOutsideViewFrustum);
  43. }
  44. return cullShadowmapOutsideViewFrustum;
  45. }
  46. bool IsLightInsideAnyViewFrustum(AZStd::span<AZ::Frustum> viewFrustums, const AZ::Vector3& lightPosition, float attenuationRadius)
  47. {
  48. return std::any_of(
  49. viewFrustums.begin(),
  50. viewFrustums.end(),
  51. [lightPosition, attenuationRadius](const AZ::Frustum& viewFrustum)
  52. {
  53. return viewFrustum.IntersectSphere(lightPosition, attenuationRadius) != AZ::IntersectResult::Exterior;
  54. });
  55. }
  56. } // namespace
  57. void ProjectedShadowFeatureProcessor::Reflect(ReflectContext* context)
  58. {
  59. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  60. {
  61. serializeContext
  62. ->Class<ProjectedShadowFeatureProcessor, FeatureProcessor>()
  63. ->Version(0);
  64. }
  65. }
  66. void ProjectedShadowFeatureProcessor::Activate()
  67. {
  68. const RHI::ShaderResourceGroupLayout* viewSrgLayout = RPI::RPISystemInterface::Get()->GetViewSrgLayout().get();
  69. GpuBufferHandler::Descriptor desc;
  70. desc.m_bufferName = "ProjectedShadowBuffer";
  71. desc.m_bufferSrgName = "m_projectedShadows";
  72. desc.m_elementCountSrgName = "";
  73. desc.m_elementSize = sizeof(ShadowData);
  74. desc.m_srgLayout = viewSrgLayout;
  75. m_shadowBufferHandler = GpuBufferHandler(desc);
  76. desc.m_bufferName = "ProjectedFilterParamsBuffer";
  77. desc.m_bufferSrgName = "m_projectedFilterParams";
  78. desc.m_elementCountSrgName = "";
  79. desc.m_elementSize = sizeof(EsmShadowmapsPass::FilterParameter);
  80. desc.m_srgLayout = viewSrgLayout;
  81. m_filterParamBufferHandler = GpuBufferHandler(desc);
  82. EnableSceneNotification();
  83. }
  84. void ProjectedShadowFeatureProcessor::Deactivate()
  85. {
  86. DisableSceneNotification();
  87. m_shadowData.Clear();
  88. m_shadowBufferHandler.Release();
  89. m_filterParamBufferHandler.Release();
  90. m_shadowProperties.Clear();
  91. m_projectedShadowmapsPasses.clear();
  92. m_esmShadowmapsPasses.clear();
  93. m_primaryProjectedShadowmapsPass = nullptr;
  94. if (m_primaryEsmShadowmapsPass)
  95. {
  96. m_primaryEsmShadowmapsPass->SetEnabledComputation(false);
  97. m_primaryEsmShadowmapsPass = nullptr;
  98. }
  99. }
  100. ProjectedShadowFeatureProcessor::ShadowId ProjectedShadowFeatureProcessor::AcquireShadow()
  101. {
  102. // Reserve a new slot in m_shadowData
  103. size_t index = m_shadowData.Reserve();
  104. if (index >= std::numeric_limits<ShadowId::IndexType>::max())
  105. {
  106. m_shadowData.Release(index);
  107. return ShadowId::Null;
  108. }
  109. ShadowId id = ShadowId(aznumeric_cast<ShadowId::IndexType>(index));
  110. InitializeShadow(id);
  111. return id;
  112. }
  113. void ProjectedShadowFeatureProcessor::ReleaseShadow(ShadowId id)
  114. {
  115. if (id.IsValid())
  116. {
  117. auto& shadowProperty = GetShadowPropertyFromShadowId(id);
  118. if (m_primaryProjectedShadowmapsPass)
  119. {
  120. m_primaryProjectedShadowmapsPass->QueueRemoveChild(shadowProperty.m_shadowmapPass);
  121. }
  122. m_shadowProperties.RemoveData(&shadowProperty);
  123. m_shadowData.Release(id.GetIndex());
  124. }
  125. m_filterParameterNeedsUpdate = true;
  126. m_shadowmapPassNeedsUpdate = true;
  127. }
  128. void ProjectedShadowFeatureProcessor::SetShadowTransform(ShadowId id, Transform transform)
  129. {
  130. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  131. shadowProperty.m_desc.m_transform = transform;
  132. UpdateShadowView(shadowProperty);
  133. }
  134. void ProjectedShadowFeatureProcessor::SetNearFarPlanes(ShadowId id, float nearPlaneDistance, float farPlaneDistance)
  135. {
  136. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetFrontBackPlanes().");
  137. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  138. shadowProperty.m_desc.m_nearPlaneDistance = GetMax(nearPlaneDistance, 0.0001f);
  139. shadowProperty.m_desc.m_farPlaneDistance = GetMax(farPlaneDistance, nearPlaneDistance + 0.0001f);
  140. UpdateShadowView(shadowProperty);
  141. }
  142. void ProjectedShadowFeatureProcessor::SetAspectRatio(ShadowId id, float aspectRatio)
  143. {
  144. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetAspectRatio().");
  145. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  146. shadowProperty.m_desc.m_aspectRatio = aspectRatio;
  147. UpdateShadowView(shadowProperty);
  148. }
  149. void ProjectedShadowFeatureProcessor::SetFieldOfViewY(ShadowId id, float fieldOfViewYRadians)
  150. {
  151. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetFieldOfViewY().");
  152. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  153. shadowProperty.m_desc.m_fieldOfViewYRadians = fieldOfViewYRadians;
  154. UpdateShadowView(shadowProperty);
  155. }
  156. void ProjectedShadowFeatureProcessor::SetShadowBias(ShadowId id, float bias)
  157. {
  158. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowBias().");
  159. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  160. shadowProperty.m_bias = bias;
  161. }
  162. void ProjectedShadowFeatureProcessor::SetNormalShadowBias(ShadowId id, float normalShadowBias)
  163. {
  164. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetNormalShadowBias().");
  165. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(id.GetIndex());
  166. shadowData.m_normalShadowBias = normalShadowBias;
  167. m_deviceBufferNeedsUpdate = true;
  168. }
  169. void ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size)
  170. {
  171. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution().");
  172. AZ_Assert(size != ShadowmapSize::None, "Shadowmap size cannot be set to None, remove the shadow instead.");
  173. FilterParameter& esmData = m_shadowData.GetElement<FilterParamIndex>(id.GetIndex());
  174. esmData.m_shadowmapSize = aznumeric_cast<uint32_t>(size);
  175. m_deviceBufferNeedsUpdate = true;
  176. m_shadowmapPassNeedsUpdate = true;
  177. m_filterParameterNeedsUpdate = true;
  178. }
  179. void ProjectedShadowFeatureProcessor::SetEsmExponent(ShadowId id, float exponent)
  180. {
  181. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetEsmExponent().");
  182. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(id.GetIndex());
  183. shadowData.m_esmExponent = exponent;
  184. m_deviceBufferNeedsUpdate = true;
  185. m_filterParameterNeedsUpdate = true;
  186. }
  187. void ProjectedShadowFeatureProcessor::SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method)
  188. {
  189. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowFilterMethod().");
  190. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  191. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(id.GetIndex());
  192. shadowData.m_shadowFilterMethod = aznumeric_cast<uint32_t>(method);
  193. UpdateShadowView(shadowProperty);
  194. m_shadowmapPassNeedsUpdate = true;
  195. m_filterParameterNeedsUpdate = true;
  196. }
  197. void ProjectedShadowFeatureProcessor::SetFilteringSampleCount(ShadowId id, uint16_t count)
  198. {
  199. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetFilteringSampleCount().");
  200. AZ_Warning("ProjectedShadowFeatureProcessor", count <= Shadow::MaxPcfSamplingCount, "Sampling count exceed the limit.");
  201. count = GetMin(count, Shadow::MaxPcfSamplingCount);
  202. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(id.GetIndex());
  203. shadowData.m_filteringSampleCount = count;
  204. m_deviceBufferNeedsUpdate = true;
  205. }
  206. void ProjectedShadowFeatureProcessor::SetUseCachedShadows(ShadowId id, bool useCachedShadows)
  207. {
  208. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetUseCachedShadows().");
  209. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  210. shadowProperty.m_useCachedShadows = useCachedShadows;
  211. m_shadowmapPassNeedsUpdate = true;
  212. }
  213. void ProjectedShadowFeatureProcessor::SetShadowProperties(ShadowId id, const ProjectedShadowDescriptor& descriptor)
  214. {
  215. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowProperties().");
  216. ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id);
  217. if (shadowProperty.m_desc != descriptor)
  218. {
  219. shadowProperty.m_desc = descriptor;
  220. UpdateShadowView(shadowProperty);
  221. // Don't set m_shadowmapPassNeedsUpdate=true here because that would cause the pass to rebuild every time a light moves
  222. // Don't set m_filterParameterNeedsUpdate=true here because that's handled by UpdateShadowView(), and only when filtering is relevant
  223. }
  224. }
  225. auto ProjectedShadowFeatureProcessor::GetShadowProperties(ShadowId id) -> const ProjectedShadowDescriptor&
  226. {
  227. AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::GetShadowProperties().");
  228. return GetShadowPropertyFromShadowId(id).m_desc;
  229. }
  230. void ProjectedShadowFeatureProcessor::UpdateShadowView(ShadowProperty& shadowProperty)
  231. {
  232. const ProjectedShadowDescriptor& desc = shadowProperty.m_desc;
  233. float nearDist = desc.m_nearPlaneDistance;
  234. float farDist = desc.m_farPlaneDistance;
  235. // Adjust the near plane if it's too close to ensure accuracy.
  236. constexpr float NearFarRatio = 1000.0f;
  237. const float minDist = desc.m_farPlaneDistance / NearFarRatio;
  238. nearDist = GetMax(minDist, nearDist);
  239. Matrix4x4 viewToClipMatrix;
  240. MakePerspectiveFovMatrixRH(
  241. viewToClipMatrix,
  242. GetMax(desc.m_fieldOfViewYRadians, MinimumFieldOfView),
  243. desc.m_aspectRatio,
  244. nearDist,
  245. farDist);
  246. RPI::ViewPtr view = shadowProperty.m_shadowmapView;
  247. view->SetViewToClipMatrix(viewToClipMatrix);
  248. view->SetCameraTransform(Matrix3x4::CreateFromTransform(desc.m_transform));
  249. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(shadowProperty.m_shadowId.GetIndex());
  250. // Adjust the manually set bias to a more appropriate range for the shader. Scale the bias by the
  251. // near plane so that the bias appears consistent as other light properties change.
  252. shadowData.m_bias = nearDist * shadowProperty.m_bias * 0.01f;
  253. FilterParameter& esmData = m_shadowData.GetElement<FilterParamIndex>(shadowProperty.m_shadowId.GetIndex());
  254. // Set parameters to calculate linear depth if ESM is used.
  255. esmData.m_nf = nearDist * farDist;
  256. esmData.m_f_n = farDist - nearDist;
  257. esmData.m_f = farDist;
  258. esmData.m_isEnabled = FilterMethodIsEsm(shadowData);
  259. m_filterParameterNeedsUpdate = m_filterParameterNeedsUpdate || esmData.m_isEnabled;
  260. // Set depth bias matrix.
  261. const Matrix4x4& worldToLightClipMatrix = view->GetWorldToClipMatrix();
  262. const Matrix4x4 depthBiasMatrix = Shadow::GetClipToShadowmapTextureMatrix() * worldToLightClipMatrix;
  263. shadowData.m_depthBiasMatrix = depthBiasMatrix;
  264. shadowData.m_unprojectConstants[0] = view->GetViewToClipMatrix().GetRow(2).GetElement(2);
  265. shadowData.m_unprojectConstants[1] = view->GetViewToClipMatrix().GetRow(2).GetElement(3);
  266. if (shadowProperty.m_useCachedShadows && m_primaryProjectedShadowmapsPass)
  267. {
  268. shadowProperty.m_shadowmapPass->ForceRenderNextFrame();
  269. }
  270. m_deviceBufferNeedsUpdate = true;
  271. }
  272. void ProjectedShadowFeatureProcessor::InitializeShadow(ShadowId shadowId)
  273. {
  274. m_deviceBufferNeedsUpdate = true;
  275. m_shadowmapPassNeedsUpdate = true;
  276. // Reserve a slot in m_shadowProperties, and store that index in m_shadowData's second vector
  277. uint16_t shadowPropertyIndex = m_shadowProperties.GetFreeSlotIndex();
  278. m_shadowData.GetElement<ShadowPropertyIdIndex>(shadowId.GetIndex()) = shadowPropertyIndex;
  279. ShadowProperty& shadowProperty = m_shadowProperties.GetData(shadowPropertyIndex);
  280. shadowProperty.m_shadowId = shadowId;
  281. Name viewName(AZStd::string::format("ProjectedShadowView (shadowId:%d)", shadowId.GetIndex()));
  282. shadowProperty.m_shadowmapView = RPI::View::CreateView(viewName, RPI::View::UsageShadow);
  283. UpdateShadowView(shadowProperty);
  284. if (m_primaryProjectedShadowmapsPass)
  285. {
  286. shadowProperty.m_shadowmapPass = CreateShadowmapPass(shadowId.GetIndex());
  287. m_primaryProjectedShadowmapsPass->QueueAddChild(shadowProperty.m_shadowmapPass);
  288. }
  289. }
  290. void ProjectedShadowFeatureProcessor::OnRenderPipelineChanged([[maybe_unused]] RPI::RenderPipeline* renderPipeline,
  291. [[maybe_unused]] RPI::SceneNotification::RenderPipelineChangeType changeType)
  292. {
  293. if (changeType == RPI::SceneNotification::RenderPipelineChangeType::Removed)
  294. {
  295. // Check for cases where the pipeline containing the primary render passes has been removed, which means the pointers
  296. // to those passes are no longer valid.
  297. CheckRemovePrimaryPasses(renderPipeline);
  298. }
  299. if (changeType == RPI::SceneNotification::RenderPipelineChangeType::Removed || changeType == RPI::SceneNotification::RenderPipelineChangeType::PassChanged)
  300. {
  301. RemoveCachedPasses(renderPipeline);
  302. }
  303. if (changeType == RPI::SceneNotification::RenderPipelineChangeType::Added || changeType == RPI::SceneNotification::RenderPipelineChangeType::PassChanged)
  304. {
  305. CachePasses(renderPipeline);
  306. }
  307. // Check to see if the primary passes have changed, and if so removes the children from the old primary pass and creates them
  308. // on the new primary pass. This is necessary if an earlier render pipeline adds or removes references to the shadow map
  309. // passes, forcing this feature processor to change which pipeline it uses to render shadows for all pipelines in the scene.
  310. UpdatePrimaryPasses();
  311. }
  312. void ProjectedShadowFeatureProcessor::CheckRemovePrimaryPasses(RPI::RenderPipeline* renderPipeline)
  313. {
  314. auto projItr = m_projectedShadowmapsPasses.find(renderPipeline);
  315. if (projItr != m_projectedShadowmapsPasses.end() && projItr->second == m_primaryProjectedShadowmapsPass)
  316. {
  317. m_primaryProjectedShadowmapsPass = nullptr;
  318. }
  319. auto esmItr = m_esmShadowmapsPasses.find(renderPipeline);
  320. if (esmItr != m_esmShadowmapsPasses.end() && esmItr->second == m_primaryEsmShadowmapsPass)
  321. {
  322. m_primaryEsmShadowmapsPass = nullptr;
  323. }
  324. }
  325. void ProjectedShadowFeatureProcessor::RemoveCachedPasses(RPI::RenderPipeline* renderPipeline)
  326. {
  327. m_projectedShadowmapsPasses.erase(renderPipeline);
  328. m_esmShadowmapsPasses.erase(renderPipeline);
  329. // Handle the case where the render pipeline containing the primary projected shadow pass is changed, and the
  330. // projected shadow pass was altered or removed as part of that change.
  331. if (renderPipeline == m_primaryShadowPipeline && m_primaryProjectedShadowmapsPass != nullptr)
  332. {
  333. RPI::PassFilter projectedPassFilter = RPI::PassFilter::CreateWithTemplateName(AZ_NAME_LITERAL("ProjectedShadowmapsTemplate"), renderPipeline);
  334. bool primaryPassChanged = true;
  335. RPI::PassSystemInterface::Get()->ForEachPass(projectedPassFilter,
  336. [&](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
  337. {
  338. primaryPassChanged = m_primaryProjectedShadowmapsPass != pass;
  339. return RPI::PassFilterExecutionFlow::StopVisitingPasses;
  340. }
  341. );
  342. if (primaryPassChanged)
  343. {
  344. m_primaryProjectedShadowmapsPass = nullptr;
  345. // Check to see if the esm pass still exists on this pipeline. If so, turn it off before setting the pointer to null.
  346. RPI::PassFilter esmPassFilter = RPI::PassFilter::CreateWithTemplateName(AZ_NAME_LITERAL("EsmShadowmapsTemplate"), renderPipeline);
  347. RPI::PassSystemInterface::Get()->ForEachPass(esmPassFilter,
  348. [&](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
  349. {
  350. if (pass == m_primaryEsmShadowmapsPass)
  351. {
  352. m_primaryEsmShadowmapsPass->SetEnabledComputation(false);
  353. }
  354. return RPI::PassFilterExecutionFlow::StopVisitingPasses;
  355. }
  356. );
  357. m_primaryEsmShadowmapsPass = nullptr;
  358. }
  359. }
  360. }
  361. void ProjectedShadowFeatureProcessor::CachePasses(RPI::RenderPipeline* renderPipeline)
  362. {
  363. // Find the Projected Shadow pass in a given render pipeline and update it.
  364. RPI::PassFilter projectedPassFilter = RPI::PassFilter::CreateWithTemplateName(AZ_NAME_LITERAL("ProjectedShadowmapsTemplate"), renderPipeline);
  365. RPI::PassSystemInterface::Get()->ForEachPass(projectedPassFilter,
  366. [&](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
  367. {
  368. if (m_projectedShadowmapsPasses.contains(renderPipeline))
  369. {
  370. AZ_Error("ProjectedShadowFeatureProcessor", false, "Found multiple projected shadowmap passes in pipeline.");
  371. return RPI::PassFilterExecutionFlow::StopVisitingPasses;
  372. }
  373. ProjectedShadowmapsPass* shadowmapPass = static_cast<ProjectedShadowmapsPass*>(pass);
  374. shadowmapPass->SetAtlasAttachmentImage(m_atlasImage);
  375. m_projectedShadowmapsPasses[renderPipeline] = shadowmapPass;
  376. return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; // continue to check for multiple (error case)
  377. }
  378. );
  379. // Find the ESM shadow pass in a given render pipeline and update it.
  380. RPI::PassFilter esmPassFilter = RPI::PassFilter::CreateWithTemplateName(AZ_NAME_LITERAL("EsmShadowmapsTemplate"), renderPipeline);
  381. RPI::PassSystemInterface::Get()->ForEachPass(esmPassFilter,
  382. [&](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
  383. {
  384. EsmShadowmapsPass* esmShadowmapsPass = static_cast<EsmShadowmapsPass*>(pass);
  385. if (esmShadowmapsPass->GetLightTypeName() == Name("projected"))
  386. {
  387. if (m_esmShadowmapsPasses.contains(renderPipeline))
  388. {
  389. AZ_Error("ProjectedShadowFeatureProcessor", false, "Found multiple esm shadowmap passes for projected shadows in pipeline.");
  390. return RPI::PassFilterExecutionFlow::StopVisitingPasses;
  391. }
  392. m_esmShadowmapsPasses[renderPipeline] = esmShadowmapsPass;
  393. if (esmShadowmapsPass != m_primaryEsmShadowmapsPass)
  394. {
  395. esmShadowmapsPass->SetEnabledComputation(false);
  396. }
  397. esmShadowmapsPass->SetAtlasAttachmentImage(m_esmAtlasImage);
  398. }
  399. return RPI::PassFilterExecutionFlow::ContinueVisitingPasses; // continue to check for multiple (error case)
  400. }
  401. );
  402. }
  403. void ProjectedShadowFeatureProcessor::UpdatePrimaryPasses()
  404. {
  405. // Find a new m_primaryProjectedShadowmapsPass. This needs to be the first ProjectedShadowmapsPass
  406. // in the list of pipelines to ensure it calculates the shadows before any other pipelines need it.
  407. bool found = false;
  408. for (RPI::RenderPipelinePtr pipeline : GetParentScene()->GetRenderPipelines())
  409. {
  410. auto itr = m_projectedShadowmapsPasses.find(pipeline.get());
  411. if (itr != m_projectedShadowmapsPasses.end())
  412. {
  413. ProjectedShadowmapsPass* pass = itr->second;
  414. if (m_primaryProjectedShadowmapsPass != pass)
  415. {
  416. if (m_primaryProjectedShadowmapsPass != nullptr)
  417. {
  418. for (RPI::Ptr<RPI::Pass> child : m_primaryProjectedShadowmapsPass->GetChildren())
  419. {
  420. child->QueueForRemoval();
  421. }
  422. }
  423. m_primaryProjectedShadowmapsPass = pass;
  424. for (auto& shadowProperty : m_shadowProperties.GetDataVector())
  425. {
  426. size_t shadowIndex = shadowProperty.m_shadowId.GetIndex();
  427. shadowProperty.m_shadowmapPass = CreateShadowmapPass(shadowIndex);
  428. m_primaryProjectedShadowmapsPass->QueueAddChild(shadowProperty.m_shadowmapPass);
  429. }
  430. }
  431. m_primaryShadowPipeline = pipeline.get();
  432. found = true;
  433. break;
  434. }
  435. }
  436. if (!found)
  437. {
  438. m_primaryProjectedShadowmapsPass = nullptr;
  439. m_primaryShadowPipeline = nullptr;
  440. }
  441. if (found && m_esmShadowmapsPasses.contains(m_primaryProjectedShadowmapsPass->GetRenderPipeline()))
  442. {
  443. // Update the primary esm pass to be the one that's on the same pipeline as the primary projected shadowmaps pass.
  444. EsmShadowmapsPass* firstEsmShadomapsPass = m_esmShadowmapsPasses.at(m_primaryProjectedShadowmapsPass->GetRenderPipeline());
  445. if (firstEsmShadomapsPass != m_primaryEsmShadowmapsPass)
  446. {
  447. if (m_primaryEsmShadowmapsPass != nullptr)
  448. {
  449. m_primaryEsmShadowmapsPass->SetEnabledComputation(false);
  450. }
  451. m_primaryEsmShadowmapsPass = firstEsmShadomapsPass;
  452. // This will enable computation of the primary esm shadow pass later if necessary.
  453. m_filterParameterNeedsUpdate = m_shadowProperties.GetDataCount() > 0;
  454. }
  455. }
  456. else if (m_primaryEsmShadowmapsPass != nullptr)
  457. {
  458. // Either there's no primary projected shadowmaps pass, or there is but there's no esm pass on the same pipeline, so disable
  459. // the primary esm pass if necessary.
  460. RPI::PassFilter esmPassFilter = RPI::PassFilter::CreateWithTemplateName(AZ_NAME_LITERAL("EsmShadowmapsTemplate"), m_primaryShadowPipeline);
  461. RPI::PassSystemInterface::Get()->ForEachPass(esmPassFilter,
  462. [&](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
  463. {
  464. if (pass == m_primaryEsmShadowmapsPass)
  465. {
  466. m_primaryEsmShadowmapsPass->SetEnabledComputation(false);
  467. }
  468. return RPI::PassFilterExecutionFlow::StopVisitingPasses;
  469. }
  470. );
  471. m_primaryEsmShadowmapsPass = nullptr;
  472. }
  473. if (m_primaryProjectedShadowmapsPass && !m_clearShadowDrawPacket)
  474. {
  475. CreateClearShadowDrawPacket();
  476. }
  477. m_shadowmapPassNeedsUpdate = true;
  478. }
  479. void ProjectedShadowFeatureProcessor::UpdateFilterParameters()
  480. {
  481. if (m_filterParameterNeedsUpdate)
  482. {
  483. UpdateEsmPassEnabled();
  484. SetFilterParameterToPass();
  485. m_filterParameterNeedsUpdate = false;
  486. }
  487. }
  488. void ProjectedShadowFeatureProcessor::UpdateEsmPassEnabled()
  489. {
  490. if (m_primaryEsmShadowmapsPass == nullptr)
  491. {
  492. return;
  493. }
  494. bool anyShadowsUseEsm = false;
  495. for (const auto& shadowProperty : m_shadowProperties.GetDataVector())
  496. {
  497. FilterParameter& esmData = m_shadowData.GetElement<FilterParamIndex>(shadowProperty.m_shadowId.GetIndex());
  498. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(shadowProperty.m_shadowId.GetIndex());
  499. if (esmData.m_isEnabled)
  500. {
  501. anyShadowsUseEsm = true;
  502. break;
  503. }
  504. // TODO: why do we set it multiple times?
  505. m_primaryEsmShadowmapsPass->SetEsmExponent(shadowData.m_esmExponent);
  506. }
  507. m_primaryEsmShadowmapsPass->SetEnabledComputation(anyShadowsUseEsm);
  508. }
  509. void ProjectedShadowFeatureProcessor::SetFilterParameterToPass()
  510. {
  511. static uint32_t nameIndex = 0;
  512. if (!m_primaryProjectedShadowmapsPass)
  513. {
  514. // If this pass doesn't exist, then do nothing.
  515. return;
  516. }
  517. // Create index table buffer.
  518. // [GFX TODO ATOM-14851] Should not be creating a new buffer here, just map the data or orphan with new data.
  519. const AZStd::string indexTableBufferName = AZStd::string::format("IndexTableBuffer(Projected) %d", nameIndex++);
  520. const Data::Instance<RPI::Buffer> indexTableBuffer = m_atlas.CreateShadowmapIndexTableBuffer(indexTableBufferName);
  521. m_filterParamBufferHandler.UpdateBuffer(m_shadowData.GetRawData<FilterParamIndex>(), static_cast<uint32_t>(m_shadowData.GetSize()));
  522. if (m_primaryEsmShadowmapsPass)
  523. {
  524. m_primaryEsmShadowmapsPass->SetShadowmapIndexTableBuffer(indexTableBuffer);
  525. m_primaryEsmShadowmapsPass->SetFilterParameterBuffer(m_filterParamBufferHandler.GetBuffer());
  526. }
  527. }
  528. void ProjectedShadowFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& /*packet*/)
  529. {
  530. AZ_PROFILE_SCOPE(RPI, "ProjectedShadowFeatureProcessor: Simulate");
  531. if (m_shadowmapPassNeedsUpdate && m_primaryProjectedShadowmapsPass)
  532. {
  533. UpdateAtlas();
  534. UpdateShadowPasses();
  535. auto& shadowProperties = m_shadowProperties.GetDataVector();
  536. for (const auto& shadowProperty : shadowProperties)
  537. {
  538. const int16_t shadowIndexInSrg = shadowProperty.m_shadowId.GetIndex();
  539. ShadowData& shadowData = m_shadowData.GetElement<ShadowDataIndex>(shadowIndexInSrg);
  540. FilterParameter& filterData = m_shadowData.GetElement<FilterParamIndex>(shadowIndexInSrg);
  541. const ShadowmapAtlas::Origin origin = m_atlas.GetOrigin(shadowIndexInSrg);
  542. shadowData.m_shadowmapArraySlice = origin.m_arraySlice;
  543. filterData.m_shadowmapOriginInSlice = origin.m_originInSlice;
  544. m_deviceBufferNeedsUpdate = true;
  545. }
  546. if (m_primaryEsmShadowmapsPass != nullptr)
  547. {
  548. m_primaryEsmShadowmapsPass->QueueForBuildAndInitialization();
  549. }
  550. m_shadowmapPassNeedsUpdate = false;
  551. }
  552. // This has to be called after UpdateShadowmapSizes().
  553. UpdateFilterParameters();
  554. if (m_deviceBufferNeedsUpdate)
  555. {
  556. m_shadowBufferHandler.UpdateBuffer(m_shadowData.GetRawData<ShadowDataIndex>(), static_cast<uint32_t>(m_shadowData.GetSize()));
  557. m_deviceBufferNeedsUpdate = false;
  558. }
  559. // Turn off cached esm shadow maps for next frame
  560. for (const auto& shadowProperty : m_shadowProperties.GetDataVector())
  561. {
  562. if (shadowProperty.m_useCachedShadows)
  563. {
  564. FilterParameter& esmData = m_shadowData.GetElement<FilterParamIndex>(shadowProperty.m_shadowId.GetIndex());
  565. if (esmData.m_isEnabled != 0)
  566. {
  567. esmData.m_isEnabled = false;
  568. m_filterParameterNeedsUpdate = true;
  569. }
  570. }
  571. }
  572. }
  573. void ProjectedShadowFeatureProcessor::PrepareViews(
  574. const PrepareViewsPacket& prepareViewsPacket, AZStd::vector<AZStd::pair<RPI::PipelineViewTag, RPI::ViewPtr>>& outViews)
  575. {
  576. if (m_primaryProjectedShadowmapsPass != nullptr)
  577. {
  578. RPI::RenderPipeline* renderPipeline = m_primaryProjectedShadowmapsPass->GetRenderPipeline();
  579. if (renderPipeline)
  580. {
  581. AZStd::vector<AZ::Frustum> mainViewFrustums;
  582. for (const auto& [view, viewTag] : prepareViewsPacket.m_persistentViews)
  583. {
  584. AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix());
  585. mainViewFrustums.push_back(viewFrustum);
  586. }
  587. bool cullShadowmapOutsideViewFrustum = IsShadowmapCullingEnabled();
  588. auto& shadowProperties = m_shadowProperties.GetDataVector();
  589. for (ShadowProperty& shadowProperty : shadowProperties)
  590. {
  591. uint16_t shadowIndex = shadowProperty.m_shadowId.GetIndex();
  592. const FilterParameter& filterData = m_shadowData.GetElement<FilterParamIndex>(shadowIndex);
  593. if (filterData.m_shadowmapSize == aznumeric_cast<uint32_t>(ShadowmapSize::None))
  594. {
  595. continue;
  596. }
  597. auto lightPosition = shadowProperty.m_desc.m_transform.GetTranslation();
  598. if (cullShadowmapOutsideViewFrustum &&
  599. !IsLightInsideAnyViewFrustum(mainViewFrustums, lightPosition, shadowProperty.m_desc.m_farPlaneDistance))
  600. {
  601. continue;
  602. }
  603. const RPI::PipelineViewTag& viewTag = shadowProperty.m_shadowmapPass->GetPipelineViewTag();
  604. const RHI::DrawListMask drawListMask = renderPipeline->GetDrawListMask(viewTag);
  605. if (shadowProperty.m_shadowmapView->GetDrawListMask() != drawListMask)
  606. {
  607. shadowProperty.m_shadowmapView->Reset();
  608. shadowProperty.m_shadowmapView->SetDrawListMask(drawListMask);
  609. }
  610. outViews.emplace_back(AZStd::make_pair(viewTag, shadowProperty.m_shadowmapView));
  611. }
  612. }
  613. }
  614. }
  615. void ProjectedShadowFeatureProcessor::Render(const FeatureProcessor::RenderPacket& packet)
  616. {
  617. AZ_PROFILE_SCOPE(RPI, "ProjectedShadowFeatureProcessor: Render");
  618. if (m_primaryProjectedShadowmapsPass != nullptr)
  619. {
  620. for (const RPI::ViewPtr& view : packet.m_views)
  621. {
  622. if (view->GetUsageFlags() & RPI::View::UsageFlags::UsageCamera)
  623. {
  624. RPI::ShaderResourceGroup* srg = view->GetShaderResourceGroup().get();
  625. float shadowMapAtlasSize = static_cast<float>(m_atlas.GetBaseShadowmapSize());
  626. srg->SetConstant(m_shadowmapAtlasSizeIndex, shadowMapAtlasSize);
  627. const float invShadowmapSize = 1.0f / shadowMapAtlasSize;
  628. srg->SetConstant(m_invShadowmapAtlasSizeIndex, invShadowmapSize);
  629. m_shadowBufferHandler.UpdateSrg(srg);
  630. m_filterParamBufferHandler.UpdateSrg(srg);
  631. }
  632. }
  633. }
  634. }
  635. bool ProjectedShadowFeatureProcessor::FilterMethodIsEsm(const ShadowData& shadowData) const
  636. {
  637. return
  638. aznumeric_cast<ShadowFilterMethod>(shadowData.m_shadowFilterMethod) == ShadowFilterMethod::Esm ||
  639. aznumeric_cast<ShadowFilterMethod>(shadowData.m_shadowFilterMethod) == ShadowFilterMethod::EsmPcf;
  640. }
  641. auto ProjectedShadowFeatureProcessor::GetShadowPropertyFromShadowId(ShadowId id) -> ShadowProperty&
  642. {
  643. AZ_Assert(id.IsValid(), "Error: Invalid ShadowId");
  644. uint16_t shadowPropertyId = m_shadowData.GetElement<ShadowPropertyIdIndex>(id.GetIndex());
  645. return m_shadowProperties.GetData(shadowPropertyId);
  646. }
  647. void ProjectedShadowFeatureProcessor::CreateClearShadowDrawPacket()
  648. {
  649. // Force load of shader to clear shadow maps.
  650. const AZStd::string clearShadowShaderFilePath = "Shaders/Shadow/ClearShadow.azshader";
  651. Data::Asset<RPI::ShaderAsset> shaderAsset = RPI::AssetUtils::LoadCriticalAsset<RPI::ShaderAsset>
  652. (clearShadowShaderFilePath, RPI::AssetUtils::TraceLevel::Assert);
  653. m_clearShadowShader = RPI::Shader::FindOrCreate(shaderAsset);
  654. const RPI::ShaderVariant& variant = m_clearShadowShader->GetRootVariant();
  655. RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
  656. variant.ConfigurePipelineState(pipelineStateDescriptor);
  657. [[maybe_unused]] bool foundPipelineState = GetParentScene()->ConfigurePipelineState(m_clearShadowShader->GetDrawListTag(), pipelineStateDescriptor);
  658. AZ_Assert(foundPipelineState, "Could not find pipeline state for ClearShadow shader's draw list '%s'", shaderAsset->GetDrawListName().GetCStr())
  659. RHI::InputStreamLayoutBuilder layoutBuilder;
  660. pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End();
  661. const RHI::PipelineState* pipelineState = m_clearShadowShader->AcquirePipelineState(pipelineStateDescriptor);
  662. if (!pipelineState)
  663. {
  664. AZ_Assert(false, "Shader '%s'. Failed to acquire default pipeline state", shaderAsset->GetName().GetCStr());
  665. return;
  666. }
  667. m_geometryView.SetDrawArguments(RHI::DrawLinear(3, 0));
  668. RHI::DrawPacketBuilder drawPacketBuilder{RHI::MultiDevice::AllDevices};
  669. drawPacketBuilder.Begin(nullptr);
  670. drawPacketBuilder.SetGeometryView(&m_geometryView);
  671. RHI::DrawPacketBuilder::DrawRequest drawRequest;
  672. drawRequest.m_listTag = m_clearShadowShader->GetDrawListTag();
  673. drawRequest.m_pipelineState = pipelineState;
  674. drawRequest.m_sortKey = AZStd::numeric_limits<RHI::DrawItemSortKey>::min();
  675. drawPacketBuilder.AddDrawItem(drawRequest);
  676. m_clearShadowDrawPacket = drawPacketBuilder.End();
  677. }
  678. void ProjectedShadowFeatureProcessor::UpdateAtlas()
  679. {
  680. // Currently when something changes, the atlas is completely reset. This is ok when most shadows are dynamic,
  681. // but isn't ideal for cached shadows which will need to re-render on the next frame.
  682. m_atlas.Initialize();
  683. auto& shadowProperties = m_shadowProperties.GetDataVector();
  684. bool needsEsm = false;
  685. for (const auto& shadowProperty : shadowProperties)
  686. {
  687. uint16_t shadowIndex = shadowProperty.m_shadowId.GetIndex();
  688. FilterParameter& filterData = m_shadowData.GetElement<FilterParamIndex>(shadowIndex);
  689. needsEsm = needsEsm || filterData.m_isEnabled;
  690. m_atlas.SetShadowmapSize(shadowIndex, static_cast<ShadowmapSize>(filterData.m_shadowmapSize));
  691. }
  692. m_atlas.Finalize();
  693. auto createAtlas = [&](RHI::Format format, RHI::ImageBindFlags bindFlags, RHI::ImageAspectFlags aspectFlags, AZStd::string name)
  694. ->Data::Instance<RPI::AttachmentImage>
  695. {
  696. RHI::ImageDescriptor imageDescriptor;
  697. const uint32_t shadowmapSize = static_cast<uint32_t>(m_atlas.GetBaseShadowmapSize());
  698. imageDescriptor.m_size = RHI::Size(shadowmapSize, shadowmapSize, 1);
  699. imageDescriptor.m_format = format;
  700. imageDescriptor.m_arraySize = m_atlas.GetArraySliceCount();
  701. imageDescriptor.m_bindFlags |= bindFlags;
  702. imageDescriptor.m_sharedQueueMask = RHI::HardwareQueueClassMask::Graphics;
  703. // The ImageViewDescriptor must be specified to make sure the frame graph compiler doesn't treat this as a transient image.
  704. RHI::ImageViewDescriptor viewDesc = RHI::ImageViewDescriptor::Create(imageDescriptor.m_format, 0, 0);
  705. viewDesc.m_aspectFlags = aspectFlags;
  706. RPI::CreateAttachmentImageRequest createImageRequest;
  707. createImageRequest.m_imagePool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool().get();
  708. createImageRequest.m_imageDescriptor = imageDescriptor;
  709. createImageRequest.m_imageName = AZStd::string::format("%s.%s", name.c_str(), GetParentScene()->GetName().GetCStr());
  710. createImageRequest.m_imageViewDescriptor = &viewDesc;
  711. return RPI::AttachmentImage::Create(createImageRequest);
  712. };
  713. m_atlasImage = createAtlas(RHI::Format::D32_FLOAT, RHI::ImageBindFlags::Depth, RHI::ImageAspectFlags::Depth, "ProjectedShadowAtlas");
  714. for (auto& [key, projectedShadowmapsPass] : m_projectedShadowmapsPasses)
  715. {
  716. projectedShadowmapsPass->SetAtlasAttachmentImage(m_atlasImage);
  717. projectedShadowmapsPass->QueueForBuildAndInitialization();
  718. }
  719. if (needsEsm)
  720. {
  721. m_esmAtlasImage = createAtlas(
  722. RHI::Format::R32_FLOAT, RHI::ImageBindFlags::ShaderReadWrite, RHI::ImageAspectFlags::Color, "ProjectedShadowAtlasESM");
  723. for (auto& [key, esmShadowmapsPass] : m_esmShadowmapsPasses)
  724. {
  725. esmShadowmapsPass->SetAtlasAttachmentImage(m_esmAtlasImage);
  726. esmShadowmapsPass->QueueForBuildAndInitialization();
  727. }
  728. }
  729. else
  730. {
  731. m_esmAtlasImage = {};
  732. }
  733. }
  734. RPI::Ptr<ShadowmapPass> ProjectedShadowFeatureProcessor::CreateShadowmapPass(size_t childIndex)
  735. {
  736. const Name passName{ AZStd::string::format("ProjectedShadowmapPass.%zu", childIndex) };
  737. RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
  738. auto passData = AZStd::make_shared<RPI::RasterPassData>();
  739. passData->m_drawListTag = rhiSystem->GetDrawListTagRegistry()->GetName(m_primaryProjectedShadowmapsPass->GetDrawListTag());
  740. passData->m_pipelineViewTag = AZStd::string::format("%s.%zu", m_primaryProjectedShadowmapsPass->GetPipelineViewTag().GetCStr(), childIndex);
  741. return ShadowmapPass::CreateWithPassRequest(passName, passData);
  742. }
  743. void ProjectedShadowFeatureProcessor::UpdateShadowPasses()
  744. {
  745. struct SliceInfo
  746. {
  747. bool m_hasStaticShadows = false;
  748. AZStd::vector<ShadowmapPass*> m_shadowPasses;
  749. };
  750. AZStd::vector<SliceInfo> sliceInfo(m_atlas.GetArraySliceCount());
  751. for (const auto& it : m_shadowProperties.GetDataVector())
  752. {
  753. // This index indicates the execution order of the passes.
  754. // The first pass to render a slice should clear the slice.
  755. size_t shadowIndex = it.m_shadowId.GetIndex();
  756. auto* pass = it.m_shadowmapPass.get();
  757. const ShadowmapAtlas::Origin origin = m_atlas.GetOrigin(shadowIndex);
  758. pass->SetArraySlice(origin.m_arraySlice);
  759. pass->SetIsStatic(it.m_useCachedShadows);
  760. pass->ForceRenderNextFrame();
  761. const auto& filterData = m_shadowData.GetElement<FilterParamIndex>(shadowIndex);
  762. if (filterData.m_shadowmapSize != static_cast<uint32_t>(ShadowmapSize::None))
  763. {
  764. const RHI::Viewport viewport(
  765. origin.m_originInSlice[0] * 1.f,
  766. (origin.m_originInSlice[0] + filterData.m_shadowmapSize) * 1.f,
  767. origin.m_originInSlice[1] * 1.f,
  768. (origin.m_originInSlice[1] + filterData.m_shadowmapSize) * 1.f);
  769. const RHI::Scissor scissor(
  770. origin.m_originInSlice[0],
  771. origin.m_originInSlice[1],
  772. origin.m_originInSlice[0] + filterData.m_shadowmapSize,
  773. origin.m_originInSlice[1] + filterData.m_shadowmapSize);
  774. pass->SetViewportScissor(viewport, scissor);
  775. pass->SetClearEnabled(false);
  776. SliceInfo& sliceInfoItem = sliceInfo.at(origin.m_arraySlice);
  777. sliceInfoItem.m_shadowPasses.push_back(pass);
  778. sliceInfoItem.m_hasStaticShadows = sliceInfoItem.m_hasStaticShadows || it.m_useCachedShadows;
  779. }
  780. }
  781. RHI::Handle<uint32_t> casterMovedBit = GetParentScene()->GetViewTagBitRegistry().FindTag(MeshCommon::MeshMovedName);
  782. for (const auto& it : sliceInfo)
  783. {
  784. if (!it.m_hasStaticShadows)
  785. {
  786. if (!it.m_shadowPasses.empty())
  787. {
  788. // no static shadows in this slice, so have the first pass clear the atlas on load.
  789. it.m_shadowPasses.at(0)->SetClearEnabled(true);
  790. }
  791. }
  792. else
  793. {
  794. // There's at least one static shadow in this slice, so passes need to clear themselves using a draw.
  795. for (auto* pass : it.m_shadowPasses)
  796. {
  797. pass->SetClearShadowDrawPacket(m_clearShadowDrawPacket);
  798. pass->SetCasterMovedBit(casterMovedBit);
  799. }
  800. }
  801. }
  802. }
  803. }