TaaPass.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 <Atom/RHI.Reflect/Format.h>
  9. #include <PostProcessing/TaaPass.h>
  10. #include <AzCore/Math/Random.h>
  11. #include <Atom/RPI.Public/Image/AttachmentImagePool.h>
  12. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  13. #include <Atom/RPI.Public/Pass/PassUtils.h>
  14. #include <Atom/RPI.Public/RenderPipeline.h>
  15. #include <Atom/RPI.Public/View.h>
  16. #include <Atom/RPI.Reflect/Pass/PassName.h>
  17. namespace AZ::Render
  18. {
  19. RPI::Ptr<TaaPass> TaaPass::Create(const RPI::PassDescriptor& descriptor)
  20. {
  21. RPI::Ptr<TaaPass> pass = aznew TaaPass(descriptor);
  22. return pass;
  23. }
  24. TaaPass::TaaPass(const RPI::PassDescriptor& descriptor)
  25. : Base(descriptor)
  26. {
  27. uint32_t numJitterPositions = 8;
  28. const TaaPassData* taaPassData = RPI::PassUtils::GetPassData<TaaPassData>(descriptor);
  29. if (taaPassData)
  30. {
  31. numJitterPositions = taaPassData->m_numJitterPositions;
  32. }
  33. // The coprimes 2, 3 are commonly used for halton sequences because they have an even distribution even for
  34. // few samples. With larger primes you need to offset by some amount between each prime to have the same
  35. // effect. We could allow this to be configurable in the future.
  36. SetupSubPixelOffsets(2, 3, numJitterPositions);
  37. }
  38. void TaaPass::CompileResources(const RHI::FrameGraphCompileContext& context)
  39. {
  40. struct TaaConstants
  41. {
  42. AZStd::array<uint32_t, 2> m_size = { 1, 1 };
  43. AZStd::array<float, 2> m_rcpSize = { 0.0, 0.0 };
  44. AZStd::array<float, 4> m_weights1 = { 0.0 };
  45. AZStd::array<float, 4> m_weights2 = { 0.0 };
  46. AZStd::array<float, 4> m_weights3 = { 0.0 };
  47. };
  48. TaaConstants cb;
  49. RHI::Size inputSize = m_lastFrameAccumulationBinding->GetAttachment()->m_descriptor.m_image.m_size;
  50. cb.m_size[0] = inputSize.m_width;
  51. cb.m_size[1] = inputSize.m_height;
  52. cb.m_rcpSize[0] = 1.0f / inputSize.m_width;
  53. cb.m_rcpSize[1] = 1.0f / inputSize.m_height;
  54. Offset jitterOffset = m_subPixelOffsets.at(m_offsetIndex);
  55. GenerateFilterWeights(Vector2(jitterOffset.m_xOffset, jitterOffset.m_yOffset));
  56. cb.m_weights1 = { m_filterWeights[0], m_filterWeights[1], m_filterWeights[2], m_filterWeights[3] };
  57. cb.m_weights2 = { m_filterWeights[4], m_filterWeights[5], m_filterWeights[6], m_filterWeights[7] };
  58. cb.m_weights3 = { m_filterWeights[8], 0.0f, 0.0f, 0.0f };
  59. m_shaderResourceGroup->SetConstant(m_constantDataIndex, cb);
  60. Base::CompileResources(context);
  61. }
  62. void TaaPass::FrameBeginInternal(FramePrepareParams params)
  63. {
  64. RHI::Size inputSize = m_inputColorBinding->GetAttachment()->m_descriptor.m_image.m_size;
  65. Vector2 rcpInputSize = Vector2(1.0f / inputSize.m_width, 1.0f / inputSize.m_height);
  66. RPI::ViewPtr view = GetRenderPipeline()->GetFirstView(GetPipelineViewTag());
  67. if (view)
  68. {
  69. m_offsetIndex = (m_offsetIndex + 1) % m_subPixelOffsets.size();
  70. Offset offset = m_subPixelOffsets.at(m_offsetIndex);
  71. view->SetClipSpaceOffset(offset.m_xOffset * rcpInputSize.GetX(), offset.m_yOffset * rcpInputSize.GetY());
  72. }
  73. if (!ShouldCopyHistoryBuffer)
  74. {
  75. m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
  76. m_accumulationOuptutIndex ^= 1; // swap which attachment is the output and last frame
  77. UpdateAttachmentImage(m_accumulationOuptutIndex);
  78. m_outputColorBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
  79. }
  80. Base::FrameBeginInternal(params);
  81. }
  82. void TaaPass::ResetInternal()
  83. {
  84. m_accumulationAttachments[0].reset();
  85. m_accumulationAttachments[1].reset();
  86. m_inputColorBinding = nullptr;
  87. m_lastFrameAccumulationBinding = nullptr;
  88. m_outputColorBinding = nullptr;
  89. Base::ResetInternal();
  90. }
  91. void TaaPass::BuildInternal()
  92. {
  93. m_accumulationAttachments[0] = FindAttachment(Name("Accumulation1"));
  94. m_accumulationAttachments[1] = FindAttachment(Name("Accumulation2"));
  95. bool attachmentsValid = true;
  96. // Make sure the attachments have images when the pass first loads.
  97. for (auto i : { 0, 1 })
  98. {
  99. if (m_accumulationAttachments[i])
  100. {
  101. attachmentsValid = UpdateAttachmentImage(i);
  102. if (!attachmentsValid)
  103. {
  104. break;
  105. }
  106. }
  107. else
  108. {
  109. attachmentsValid = false;
  110. break;
  111. }
  112. }
  113. if (!attachmentsValid)
  114. {
  115. this->SetEnabled(false);
  116. AZ_Error(
  117. "TaaPass", attachmentsValid, "TaaPass disabled because the ImageAttachments Accumulation1 and Accumulation2 are invalid.");
  118. return;
  119. }
  120. m_inputColorBinding = FindAttachmentBinding(Name("InputColor"));
  121. AZ_Error("TaaPass", m_inputColorBinding, "TaaPass requires a slot for InputColor.");
  122. m_lastFrameAccumulationBinding = FindAttachmentBinding(Name("LastFrameAccumulation"));
  123. AZ_Error("TaaPass", m_lastFrameAccumulationBinding, "TaaPass requires a slot for LastFrameAccumulation.");
  124. m_outputColorBinding = FindAttachmentBinding(Name("OutputColor"));
  125. AZ_Error("TaaPass", m_outputColorBinding, "TaaPass requires a slot for OutputColor.");
  126. // Set up the attachment for last frame accumulation and output color if it's never been done to
  127. // ensure SRG indices are set up correctly by the pass system.
  128. if (m_lastFrameAccumulationBinding->GetAttachment() == nullptr)
  129. {
  130. m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[0]);
  131. m_outputColorBinding->SetAttachment(m_accumulationAttachments[1]);
  132. }
  133. Base::BuildInternal();
  134. }
  135. bool TaaPass::UpdateAttachmentImage(uint32_t attachmentIndex)
  136. {
  137. RPI::Ptr<RPI::PassAttachment>& attachment = m_accumulationAttachments[attachmentIndex];
  138. return UpdateImportedAttachmentImage(attachment);
  139. }
  140. void TaaPass::SetupSubPixelOffsets(uint32_t haltonX, uint32_t haltonY, uint32_t length)
  141. {
  142. m_subPixelOffsets.resize(length);
  143. HaltonSequence<2> sequence = HaltonSequence<2>({haltonX, haltonY});
  144. sequence.FillHaltonSequence(m_subPixelOffsets.begin(), m_subPixelOffsets.end());
  145. // Adjust to the -1.0 to 1.0 range. This is done because the view needs offsets in clip
  146. // space and is one less calculation that would need to be done in FrameBeginInternal()
  147. AZStd::for_each(m_subPixelOffsets.begin(), m_subPixelOffsets.end(),
  148. [](Offset& offset)
  149. {
  150. offset.m_xOffset = 2.0f * offset.m_xOffset - 1.0f;
  151. offset.m_yOffset = 2.0f * offset.m_yOffset - 1.0f;
  152. }
  153. );
  154. }
  155. // Approximation of a Blackman Harris window function of width 3.3.
  156. // https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window
  157. static float BlackmanHarris(AZ::Vector2 uv)
  158. {
  159. return expf(-2.29f * (uv.GetX() * uv.GetX() + uv.GetY() * uv.GetY()));
  160. }
  161. // Generates filter weights for the 3x3 neighborhood of a pixel. Since jitter positions are the
  162. // same for every pixel we can calculate this once here and upload to the SRG.
  163. // Jitter weights are based on a window function centered at the pixel center (we use Blackman-Harris).
  164. // As the jitter position moves around, some neighborhood locations decrease in weight, and others
  165. // increase in weight based on their distance from the center of the pixel.
  166. void TaaPass::GenerateFilterWeights(AZ::Vector2 jitterOffset)
  167. {
  168. static const AZStd::array<Vector2, 9> pixelOffsets =
  169. {
  170. // Center
  171. Vector2(0.0f, 0.0f),
  172. // Cross
  173. Vector2( 1.0f, 0.0f),
  174. Vector2( 0.0f, 1.0f),
  175. Vector2(-1.0f, 0.0f),
  176. Vector2( 0.0f, -1.0f),
  177. // Diagonals
  178. Vector2( 1.0f, 1.0f),
  179. Vector2( 1.0f, -1.0f),
  180. Vector2(-1.0f, 1.0f),
  181. Vector2(-1.0f, -1.0f),
  182. };
  183. float sum = 0.0f;
  184. for (uint32_t i = 0; i < 9; ++i)
  185. {
  186. m_filterWeights[i] = BlackmanHarris(pixelOffsets[i] + jitterOffset);
  187. sum += m_filterWeights[i];
  188. }
  189. // Normalize the weight so the sum of all weights is 1.0.
  190. float normalization = 1.0f / sum;
  191. for (uint32_t i = 0; i < 9; ++i)
  192. {
  193. m_filterWeights[i] *= normalization;
  194. }
  195. }
  196. } // namespace AZ::Render