SphericalHarmonicsExampleComponent.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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 <RHI/SphericalHarmonicsExampleComponent.h>
  9. #include <Utils/Utils.h>
  10. #include <SampleComponentManager.h>
  11. #include <SampleComponentConfig.h>
  12. #include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
  13. #include <Atom/RHI.Reflect/RenderAttachmentLayoutBuilder.h>
  14. #include <Atom/RPI.Public/Shader/Shader.h>
  15. #include <Atom/RPI.Public/View.h>
  16. #include <Atom/RPI.Public/ViewProviderBus.h>
  17. #include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
  18. #include <AzCore/Serialization/SerializeContext.h>
  19. #include <Atom/Component/DebugCamera/ArcBallControllerBus.h>
  20. #include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
  21. #include <Atom/Component/DebugCamera/CameraControllerBus.h>
  22. #include <Atom/Feature/SphericalHarmonics/SphericalHarmonicsUtility.h>
  23. #include <random>
  24. #include <sstream>
  25. namespace AtomSampleViewer
  26. {
  27. namespace SHExampleComponent
  28. {
  29. const char* imGui_solverListItem[] = { "poly3", "naive16", "naiveFull"};
  30. const char* imGui_presetItem[] = { "Grace", "RNL", "StPeter", "fakeLight", "fakeLightOriginal" , "fakeLightRotation"};
  31. enum PresetItemEnum { Grace, RNL, StPeter, FakeLight, FakeLightOriginal, FakeLightRotation};
  32. const char* sampleName = "SphericalHarmonicsExampleComponent";
  33. // demo pipeline state
  34. const char* demoShaderFilePath = "Shaders/RHI/shdemo.azshader";
  35. const char* renderShaderFilePath = "Shaders/RHI/shrender.azshader";
  36. // uniform distribution generator & sampler
  37. std::default_random_engine generator;
  38. std::uniform_real_distribution<double> distribution(0.0, 1.0);
  39. }
  40. void SphericalHarmonicsExampleComponent::Reflect(AZ::ReflectContext* context)
  41. {
  42. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  43. {
  44. serializeContext->Class<SphericalHarmonicsExampleComponent, AZ::Component>()
  45. ->Version(0)
  46. ;
  47. }
  48. }
  49. SphericalHarmonicsExampleComponent::SphericalHarmonicsExampleComponent()
  50. {
  51. m_supportRHISamplePipeline = true;
  52. }
  53. void SphericalHarmonicsExampleComponent::Activate()
  54. {
  55. using namespace AZ;
  56. const RHI::Ptr<RHI::Device> device = Utils::GetRHIDevice();
  57. BufferData bufferData;
  58. const auto positionBufSize = static_cast<uint32_t>(bufferData.m_positions.size() * sizeof(VertexPosition));
  59. const auto indexBufSize = static_cast<uint32_t>(bufferData.m_indices.size() * sizeof(uint16_t));
  60. const auto uvBufSize = static_cast<uint32_t>(bufferData.m_uvs.size() * sizeof(VertexUV));
  61. AZ::RHI::RenderAttachmentLayoutBuilder attachmentsBuilder;
  62. AZ::RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
  63. {
  64. AZ::Debug::CameraControllerRequestBus::Event(m_cameraEntityId,
  65. &AZ::Debug::CameraControllerRequestBus::Events::Enable,
  66. azrtti_typeid<AZ::Debug::ArcBallControllerComponent>());
  67. RPI::ViewPtr cameraView;
  68. // The RPI::View associated to this component can be obtained through the ViewProvider, by using Entity Id.
  69. RPI::ViewProviderBus::EventResult(cameraView, m_cameraEntityId, &RPI::ViewProvider::GetView);
  70. if (cameraView)
  71. {
  72. m_viewShaderResourceGroup = cameraView->GetShaderResourceGroup();
  73. }
  74. }
  75. {
  76. m_bufferPool = aznew RHI::BufferPool();
  77. RHI::BufferPoolDescriptor bufferPoolDesc;
  78. bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::InputAssembly;
  79. bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
  80. m_bufferPool->Init(bufferPoolDesc);
  81. SetFullScreenRect(bufferData.m_positions.data(), bufferData.m_uvs.data(), bufferData.m_indices.data());
  82. m_positionBuffer = aznew RHI::Buffer();
  83. m_indexBuffer = aznew RHI::Buffer();
  84. m_uvBuffer = aznew RHI::Buffer();
  85. RHI::ResultCode result = RHI::ResultCode::Success;
  86. RHI::BufferInitRequest request;
  87. request.m_buffer = m_positionBuffer.get();
  88. request.m_descriptor = RHI::BufferDescriptor{ RHI::BufferBindFlags::InputAssembly, positionBufSize };
  89. request.m_initialData = bufferData.m_positions.data();
  90. result = m_bufferPool->InitBuffer(request);
  91. if (result != RHI::ResultCode::Success)
  92. {
  93. AZ_Error(SHExampleComponent::sampleName, false, "Failed to initialize position buffer with error code %d", result);
  94. return;
  95. }
  96. request.m_buffer = m_indexBuffer.get();
  97. request.m_descriptor = RHI::BufferDescriptor{ RHI::BufferBindFlags::InputAssembly, indexBufSize };
  98. request.m_initialData = bufferData.m_indices.data();
  99. result = m_bufferPool->InitBuffer(request);
  100. if (result != RHI::ResultCode::Success)
  101. {
  102. AZ_Error(SHExampleComponent::sampleName, false, "Failed to initialize index buffer with error code %d", result);
  103. return;
  104. }
  105. request.m_buffer = m_uvBuffer.get();
  106. request.m_descriptor = RHI::BufferDescriptor{ RHI::BufferBindFlags::InputAssembly, uvBufSize };
  107. request.m_initialData = bufferData.m_uvs.data();
  108. result = m_bufferPool->InitBuffer(request);
  109. if (result != RHI::ResultCode::Success)
  110. {
  111. AZ_Error(SHExampleComponent::sampleName, false, "Failed to initialize uv buffer with error code %d", result);
  112. return;
  113. }
  114. m_streamBufferViews[0] = {
  115. *m_positionBuffer,
  116. 0,
  117. positionBufSize,
  118. sizeof(VertexPosition)
  119. };
  120. m_streamBufferViews[1] = {
  121. *m_uvBuffer,
  122. 0,
  123. uvBufSize,
  124. sizeof(VertexUV)
  125. };
  126. RHI::InputStreamLayoutBuilder layoutBuilder;
  127. layoutBuilder.AddBuffer()->Channel("POSITION", RHI::Format::R32G32B32_FLOAT);
  128. layoutBuilder.AddBuffer()->Channel("UV", RHI::Format::R32G32_FLOAT);
  129. pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End();
  130. RHI::ValidateStreamBufferViews(pipelineStateDescriptor.m_inputStreamLayout, m_streamBufferViews);
  131. }
  132. constexpr float objectMatrixScale = 0.8f;
  133. {
  134. auto shader = LoadShader(SHExampleComponent::demoShaderFilePath, SHExampleComponent::sampleName);
  135. if (shader == nullptr)
  136. return;
  137. auto shaderVariant = shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId);
  138. shaderVariant.ConfigurePipelineState(pipelineStateDescriptor);
  139. attachmentsBuilder.AddSubpass()
  140. ->RenderTargetAttachment(m_outputFormat);
  141. [[maybe_unused]] AZ::RHI::ResultCode result = attachmentsBuilder.End(pipelineStateDescriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout);
  142. AZ_Assert(result == AZ::RHI::ResultCode::Success, "Failed to create render attachment layout");
  143. m_demoPipelineState = shader->AcquirePipelineState(pipelineStateDescriptor);
  144. if (!m_demoPipelineState)
  145. {
  146. AZ_Error(SHExampleComponent::sampleName, false, "Failed to acquire default pipeline state for shader '%s'", SHExampleComponent::demoShaderFilePath);
  147. return;
  148. }
  149. const AZ::Name demoObjectMatrixShaderInput{ "m_objectMatrix" };
  150. const AZ::Name SHBandInput{ "m_shBand" };
  151. const AZ::Name SHOrderInput{ "m_shOrder" };
  152. const AZ::Name SHSolverInput{ "m_shSolver" };
  153. const AZ::Name enableDistortionInput{ "m_enableDistortion" };
  154. m_demoShaderResourceGroup = CreateShaderResourceGroup(shader, "SphericalHarmonicsInstanceSrg", SHExampleComponent::sampleName);
  155. FindShaderInputIndex(&m_demoObjectMatrixInputIndex, m_demoShaderResourceGroup, demoObjectMatrixShaderInput, SHExampleComponent::sampleName);
  156. FindShaderInputIndex(&m_SHBandInputIndex, m_demoShaderResourceGroup, SHBandInput, SHExampleComponent::sampleName);
  157. FindShaderInputIndex(&m_SHOrderInputIndex, m_demoShaderResourceGroup, SHOrderInput, SHExampleComponent::sampleName);
  158. FindShaderInputIndex(&m_SHSolverInputIndex, m_demoShaderResourceGroup, SHSolverInput, SHExampleComponent::sampleName);
  159. FindShaderInputIndex(&m_EnableDistortionInputIndex, m_demoShaderResourceGroup, enableDistortionInput, SHExampleComponent::sampleName);
  160. // Scale and translate the texture quad so we can fit the ImGuiSideBar with the samplers options.
  161. // x axis scale is multiplied by 9 / 16 (0.5625) to convert the output plane from rectangle to square to
  162. // maintain correct aspect ratio during screen space ray traicng
  163. AZ::Matrix4x4 matrix = AZ::Matrix4x4::CreateScale(Vector3(objectMatrixScale*0.5625f, objectMatrixScale, objectMatrixScale)) * AZ::Matrix4x4::CreateTranslation(Vector3(objectMatrixScale - 1.0f, 0, 0));
  164. m_demoShaderResourceGroup->SetConstant(m_demoObjectMatrixInputIndex, matrix);
  165. }
  166. {
  167. // render pipeline state
  168. auto shader = LoadShader(SHExampleComponent::renderShaderFilePath, SHExampleComponent::sampleName);
  169. if (shader == nullptr)
  170. return;
  171. auto shaderVariant = shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId);
  172. shaderVariant.ConfigurePipelineState(pipelineStateDescriptor);
  173. attachmentsBuilder.Reset();
  174. attachmentsBuilder.AddSubpass()
  175. ->RenderTargetAttachment(m_outputFormat);
  176. [[maybe_unused]] AZ::RHI::ResultCode result = attachmentsBuilder.End(pipelineStateDescriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout);
  177. AZ_Assert(result == AZ::RHI::ResultCode::Success, "Failed to create render attachment layout");
  178. m_renderPipelineState = shader->AcquirePipelineState(pipelineStateDescriptor);
  179. if (!m_renderPipelineState)
  180. {
  181. AZ_Error(SHExampleComponent::sampleName, false, "Failed to acquire default pipeline state for shader '%s'", SHExampleComponent::demoShaderFilePath);
  182. return;
  183. }
  184. const AZ::Name renderObjectMatrixInput{ "m_objectMatrix" };
  185. const AZ::Name presetIndexInput{ "m_presetIndex" };
  186. const AZ::Name exposureInput{ "m_exposure" };
  187. const AZ::Name enableGammaCorrectionInput{ "m_enableGammaCorrection" };
  188. const AZ::Name shFakeLightCoefficientInput{ "m_fakeLightSH" };
  189. const AZ::Name rotationAngleInput{ "m_rotationAngle" };
  190. m_renderShaderResourceGroup = CreateShaderResourceGroup(shader, "SphericalHarmonicsInstanceSrg", SHExampleComponent::sampleName);
  191. FindShaderInputIndex(&m_renderObjectMatrixInputIndex, m_renderShaderResourceGroup, renderObjectMatrixInput, SHExampleComponent::sampleName);
  192. FindShaderInputIndex(&m_presetIndexInputIndex, m_renderShaderResourceGroup, presetIndexInput, SHExampleComponent::sampleName);
  193. FindShaderInputIndex(&m_exposureInputIndex, m_renderShaderResourceGroup, exposureInput, SHExampleComponent::sampleName);
  194. FindShaderInputIndex(&m_enableGammaCorrectionInputIndex, m_renderShaderResourceGroup, enableGammaCorrectionInput, SHExampleComponent::sampleName);
  195. FindShaderInputIndex(&m_SHFakeLightCoefficientsInputIndex, m_renderShaderResourceGroup, shFakeLightCoefficientInput, SHExampleComponent::sampleName);
  196. FindShaderInputIndex(&m_rotationAngleInputIndex, m_renderShaderResourceGroup, rotationAngleInput, SHExampleComponent::sampleName);
  197. // Scale and translate the texture quad so we can fit the ImGuiSideBar with the samplers options.
  198. AZ::Matrix4x4 matrix = AZ::Matrix4x4::CreateScale(Vector3(objectMatrixScale*0.5625f, objectMatrixScale, objectMatrixScale)) * AZ::Matrix4x4::CreateTranslation(Vector3(objectMatrixScale - 1.0f, 0, 0));
  199. m_renderShaderResourceGroup->SetConstant(m_renderObjectMatrixInputIndex, matrix);
  200. m_shaderInputSHFakeLightCoefficients = AZ::Matrix4x4::CreateZero();
  201. m_renderShaderResourceGroup->SetConstant(m_SHFakeLightCoefficientsInputIndex, m_shaderInputSHFakeLightCoefficients);
  202. m_shaderInputRotationAngle = AZ::Vector3(0.0, 0.0, 0.0);
  203. m_renderShaderResourceGroup->SetConstant(m_rotationAngleInputIndex, m_shaderInputRotationAngle);
  204. }
  205. {
  206. struct ScopeData
  207. {
  208. };
  209. const auto prepareFunction = [this](RHI::FrameGraphInterface frameGraph, [[maybe_unused]] ScopeData& scopeData)
  210. {
  211. {
  212. RHI::ImageScopeAttachmentDescriptor desc;
  213. desc.m_attachmentId = m_outputAttachmentId;
  214. frameGraph.UseColorAttachment(desc);
  215. }
  216. frameGraph.SetEstimatedItemCount(1);
  217. };
  218. const auto compileFunction = [this]([[maybe_unused]] const AZ::RHI::FrameGraphCompileContext& context, [[maybe_unused]] const ScopeData& scopeData)
  219. {
  220. if (m_updateDemoSRG)
  221. {
  222. m_demoShaderResourceGroup->SetConstant(m_SHBandInputIndex, m_shaderInputSHBand);
  223. m_demoShaderResourceGroup->SetConstant(m_SHOrderInputIndex, m_shaderInputSHOrder);
  224. m_demoShaderResourceGroup->SetConstant(m_SHSolverInputIndex, m_shaderInputSHSolver);
  225. m_demoShaderResourceGroup->SetConstant(m_EnableDistortionInputIndex, m_shaderInputEnableDistortion);
  226. m_demoShaderResourceGroup->Compile();
  227. m_updateDemoSRG = false;
  228. }
  229. if (m_updateRenderSRG)
  230. {
  231. m_renderShaderResourceGroup->SetConstant(m_presetIndexInputIndex, m_shaderInputPresetIndex);
  232. m_renderShaderResourceGroup->SetConstant(m_exposureInputIndex, m_shaderInputExposure);
  233. m_renderShaderResourceGroup->SetConstant(m_enableGammaCorrectionInputIndex, m_shaderInputEnableGammaCorrection);
  234. m_renderShaderResourceGroup->SetConstant(m_SHFakeLightCoefficientsInputIndex, m_shaderInputSHFakeLightCoefficients);
  235. m_renderShaderResourceGroup->SetConstant(m_rotationAngleInputIndex, m_shaderInputRotationAngle);
  236. m_renderShaderResourceGroup->Compile();
  237. m_updateRenderSRG = false;
  238. }
  239. };
  240. const auto executeFunction = [=]([[maybe_unused]] const RHI::FrameGraphExecuteContext& context, [[maybe_unused]] const ScopeData& scopeData)
  241. {
  242. RHI::CommandList* commandList = context.GetCommandList();
  243. commandList->SetViewports(&m_viewport, 1);
  244. commandList->SetScissors(&m_scissor, 1);
  245. // Bind ViewSrg
  246. commandList->SetShaderResourceGroupForDraw(*m_viewShaderResourceGroup->GetRHIShaderResourceGroup()->GetDeviceShaderResourceGroup(context.GetDeviceIndex()).get());
  247. const RHI::DeviceIndexBufferView indexBufferView = { *m_indexBuffer->GetDeviceBuffer(context.GetDeviceIndex()), 0,
  248. indexBufSize, RHI::IndexFormat::Uint16 };
  249. RHI::DrawIndexed drawIndexed;
  250. drawIndexed.m_indexCount = 6;
  251. drawIndexed.m_instanceCount = 1;
  252. const RHI::DeviceShaderResourceGroup* shaderResourceGroups[] = {
  253. (m_mode ? m_demoShaderResourceGroup->GetRHIShaderResourceGroup()
  254. ->GetDeviceShaderResourceGroup(context.GetDeviceIndex())
  255. .get()
  256. : m_renderShaderResourceGroup->GetRHIShaderResourceGroup()
  257. ->GetDeviceShaderResourceGroup(context.GetDeviceIndex())
  258. .get()),
  259. m_viewShaderResourceGroup->GetRHIShaderResourceGroup()->GetDeviceShaderResourceGroup(context.GetDeviceIndex()).get()
  260. };
  261. RHI::DeviceDrawItem drawItem;
  262. drawItem.m_arguments = drawIndexed;
  263. drawItem.m_pipelineState = m_mode ? m_demoPipelineState->GetDevicePipelineState(context.GetDeviceIndex()).get() : m_renderPipelineState->GetDevicePipelineState(context.GetDeviceIndex()).get();
  264. drawItem.m_indexBufferView = &indexBufferView;
  265. drawItem.m_shaderResourceGroupCount = static_cast<uint8_t>(RHI::ArraySize(shaderResourceGroups));
  266. drawItem.m_shaderResourceGroups = shaderResourceGroups;
  267. drawItem.m_streamBufferViewCount = static_cast<uint8_t>(m_streamBufferViews.size());
  268. AZStd::array<AZ::RHI::DeviceStreamBufferView, 2> deviceStreamBufferViews{
  269. m_streamBufferViews[0].GetDeviceStreamBufferView(context.GetDeviceIndex()),
  270. m_streamBufferViews[1].GetDeviceStreamBufferView(context.GetDeviceIndex())
  271. };
  272. drawItem.m_streamBufferViews = deviceStreamBufferViews.data();
  273. commandList->Submit(drawItem);
  274. };
  275. m_scopeProducers.emplace_back(aznew RHI::ScopeProducerFunction<
  276. ScopeData,
  277. decltype(prepareFunction),
  278. decltype(compileFunction),
  279. decltype(executeFunction)>(
  280. RHI::ScopeId{ "SHSample" },
  281. ScopeData{},
  282. prepareFunction,
  283. compileFunction,
  284. executeFunction));
  285. }
  286. AZ::TickBus::Handler::BusConnect();
  287. AZ::RHI::RHISystemNotificationBus::Handler::BusConnect();
  288. m_imguiSidebar.Activate();
  289. }
  290. void SphericalHarmonicsExampleComponent::Deactivate()
  291. {
  292. m_positionBuffer = nullptr;
  293. m_indexBuffer = nullptr;
  294. m_uvBuffer = nullptr;
  295. m_bufferPool = nullptr;
  296. m_demoPipelineState = nullptr;
  297. m_demoShaderResourceGroup = nullptr;
  298. m_viewShaderResourceGroup = nullptr;
  299. m_shaderInputSHFakeLightCoefficients = AZ::Matrix4x4::CreateZero();
  300. m_shaderInputRotationAngle = AZ::Vector3(0.0, 0.0, 0.0);
  301. AZ::RHI::RHISystemNotificationBus::Handler::BusDisconnect();
  302. AZ::TickBus::Handler::BusDisconnect();
  303. m_windowContext = nullptr;
  304. m_scopeProducers.clear();
  305. m_imguiSidebar.Deactivate();
  306. }
  307. void SphericalHarmonicsExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
  308. {
  309. if (m_imguiSidebar.Begin())
  310. {
  311. DrawIMGui();
  312. }
  313. }
  314. //import camera configuration
  315. bool SphericalHarmonicsExampleComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  316. {
  317. BasicRHIComponent::ReadInConfig(baseConfig);
  318. const auto* config = azrtti_cast<const SampleComponentConfig*>(baseConfig);
  319. if (config && config->IsValid())
  320. {
  321. m_cameraEntityId = config->m_cameraEntityId;
  322. return true;
  323. }
  324. else
  325. {
  326. AZ_Assert(false, "SampleComponentConfig required for sample component configuration.");
  327. return false;
  328. }
  329. }
  330. float SphericalHarmonicsExampleComponent::CalFakeLightSH(bool& flag)
  331. {
  332. static const uint32_t maxSample = 200000;
  333. // distribute calculation over ticks to avoid blocking rendering pipeline
  334. static const uint32_t samplePerTick = 1000;
  335. static uint32_t sampleCount = 0;
  336. // differential of solid angle, the surface area of unit sphere is (4 pi),
  337. // and (maxSample) number of samples are drawn uniformly, thus
  338. // for each sample its solid angle is (4pi / maxSample)
  339. static double dw = (4.0 * AZ::Constants::Pi) / maxSample;
  340. if (flag)
  341. {
  342. // reinitialize state
  343. sampleCount = 0;
  344. m_shaderInputSHFakeLightCoefficients = AZ::Matrix4x4::CreateZero();
  345. flag = false;
  346. }
  347. if (sampleCount >= maxSample)
  348. {
  349. return 0.0;
  350. }
  351. // the shape of this light is demonsatrated in preset "fakeLightOriginal" in render mode
  352. // this function is a copy of fake light function in:
  353. // http://silviojemma.com/public/papers/lighting/spherical-harmonic-lighting.pdf, page 15, figure 7
  354. auto FakeLight = [](double theta, double phi)->double
  355. {
  356. // increase energy level to form hdr
  357. return 5.0 * (fmax(0.0, 5.0 * cos(theta) - 4.0) +
  358. fmax(0.0, -4.0 * sin(theta - AZ::Constants::Pi) * cos(phi - 2.5) - 3.0));
  359. };
  360. // quasi Monte Carlo sampling, without importance
  361. for (uint32_t i = 0; i < samplePerTick; ++i)
  362. {
  363. // independent identically distributed(i.i.d) samples drawn from uniform distribution [0, 1]
  364. // because importance sampling is not used here so don't need to project to target distribution
  365. double u1 = SHExampleComponent::distribution(SHExampleComponent::generator);
  366. double u2 = SHExampleComponent::distribution(SHExampleComponent::generator);
  367. // spherical coordinates (assume unit sphere radius = 1)
  368. // zenith angle between up axis (y axis in this case) and sampled direction
  369. double theta = acos(1.0 - 2.0 * u1);
  370. // azimuth angle between +x axis and sampled direction's projection on parallel plane (x-z plane in this case)
  371. double phi = 2.0 * AZ::Constants::Pi * u2;
  372. // cartesian coordinates of sample on unit sphere
  373. // here Y-up -Z-forward axis system is used
  374. float dir[3];
  375. dir[0] = static_cast<float>(sin(theta) * cos(phi));
  376. dir[1] = static_cast<float>(cos(theta));
  377. dir[2] = static_cast<float>(sin(theta) * sin(phi));
  378. // fake radiance computed from analytical light source at given direction
  379. double L = FakeLight(theta, phi);
  380. // evaluate single iteration of integral for all basises from band 0, order 0 to band 3, order 3
  381. // this coefficient set will be shared by all three color channels, thus final reconstructed output will be greylevel color
  382. // band 0
  383. m_shaderInputSHFakeLightCoefficients.SetElement(0, 0, m_shaderInputSHFakeLightCoefficients.GetElement(0, 0) +
  384. static_cast<float>(L * AZ::Render::SHBasis::Naive16(0, 0, dir) * dw));
  385. // band 1
  386. m_shaderInputSHFakeLightCoefficients.SetElement(0, 1, m_shaderInputSHFakeLightCoefficients.GetElement(0, 1) +
  387. static_cast<float>(L * AZ::Render::SHBasis::Naive16(1, -1, dir) * dw));
  388. m_shaderInputSHFakeLightCoefficients.SetElement(0, 2, m_shaderInputSHFakeLightCoefficients.GetElement(0, 2) +
  389. static_cast<float>(L * AZ::Render::SHBasis::Naive16(1, 0, dir) * dw));
  390. m_shaderInputSHFakeLightCoefficients.SetElement(0, 3, m_shaderInputSHFakeLightCoefficients.GetElement(0, 3) +
  391. static_cast<float>(L * AZ::Render::SHBasis::Naive16(1, 1, dir) * dw));
  392. // band 2
  393. m_shaderInputSHFakeLightCoefficients.SetElement(1, 0, m_shaderInputSHFakeLightCoefficients.GetElement(1, 0) +
  394. static_cast<float>(L * AZ::Render::SHBasis::Naive16(2, -2, dir) * dw));
  395. m_shaderInputSHFakeLightCoefficients.SetElement(1, 1, m_shaderInputSHFakeLightCoefficients.GetElement(1, 1) +
  396. static_cast<float>(L * AZ::Render::SHBasis::Naive16(2, -1, dir) * dw));
  397. m_shaderInputSHFakeLightCoefficients.SetElement(1, 2, m_shaderInputSHFakeLightCoefficients.GetElement(1, 2) +
  398. static_cast<float>(L * AZ::Render::SHBasis::Naive16(2, 0, dir) * dw));
  399. m_shaderInputSHFakeLightCoefficients.SetElement(1, 3, m_shaderInputSHFakeLightCoefficients.GetElement(1, 3) +
  400. static_cast<float>(L * AZ::Render::SHBasis::Naive16(2, 1, dir) * dw));
  401. m_shaderInputSHFakeLightCoefficients.SetElement(2, 0, m_shaderInputSHFakeLightCoefficients.GetElement(2, 0) +
  402. static_cast<float>(L * AZ::Render::SHBasis::Naive16(2, 2, dir) * dw));
  403. // band 3
  404. m_shaderInputSHFakeLightCoefficients.SetElement(2, 1, m_shaderInputSHFakeLightCoefficients.GetElement(2, 1) +
  405. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, -3, dir) * dw));
  406. m_shaderInputSHFakeLightCoefficients.SetElement(2, 2, m_shaderInputSHFakeLightCoefficients.GetElement(2, 2) +
  407. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, -2, dir) * dw));
  408. m_shaderInputSHFakeLightCoefficients.SetElement(2, 3, m_shaderInputSHFakeLightCoefficients.GetElement(2, 3) +
  409. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, -1, dir) * dw));
  410. m_shaderInputSHFakeLightCoefficients.SetElement(3, 0, m_shaderInputSHFakeLightCoefficients.GetElement(3, 0) +
  411. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, 0, dir) * dw));
  412. m_shaderInputSHFakeLightCoefficients.SetElement(3, 1, m_shaderInputSHFakeLightCoefficients.GetElement(3, 1) +
  413. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, 1, dir) * dw));
  414. m_shaderInputSHFakeLightCoefficients.SetElement(3, 2, m_shaderInputSHFakeLightCoefficients.GetElement(3, 2) +
  415. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, 2, dir) * dw));
  416. m_shaderInputSHFakeLightCoefficients.SetElement(3, 3, m_shaderInputSHFakeLightCoefficients.GetElement(3, 3) +
  417. static_cast<float>(L * AZ::Render::SHBasis::Naive16(3, 3, dir) * dw));
  418. }
  419. sampleCount += samplePerTick;
  420. return sampleCount / float(maxSample);
  421. }
  422. void SphericalHarmonicsExampleComponent::DrawIMGui()
  423. {
  424. ImGui::Text("\n\n\n\n\n\n\n\n\n");
  425. std::stringstream ss;
  426. ss << "Switch to " << (m_mode ? "Render" : "Demo") << " mode";
  427. if (ScriptableImGui::Button(ss.str().c_str()))
  428. {
  429. m_mode = !m_mode;
  430. }
  431. ImGui::Text("\n\n");
  432. if (m_mode)
  433. {
  434. ImGui::Text("Band L: non negative integer");
  435. int l = m_shaderInputSHBand;
  436. if (ImGui::InputInt("Band", &m_shaderInputSHBand))
  437. {
  438. if (m_shaderInputSHBand >= 0 && m_shaderInputSHBand >= m_shaderInputSHOrder)
  439. {
  440. m_updateDemoSRG = true;
  441. }
  442. else
  443. {
  444. m_shaderInputSHBand = l;
  445. }
  446. }
  447. ImGui::Text("Order M: integer within range [-L, L]");
  448. int m = m_shaderInputSHOrder;
  449. if (ImGui::InputInt("Order", &m_shaderInputSHOrder))
  450. {
  451. if (m_shaderInputSHOrder >= -m_shaderInputSHBand && m_shaderInputSHOrder <= m_shaderInputSHBand)
  452. {
  453. m_updateDemoSRG = true;
  454. }
  455. else
  456. {
  457. m_shaderInputSHOrder = m;
  458. }
  459. }
  460. if (ImGui::ListBox("Solver", &m_shaderInputSHSolver, SHExampleComponent::imGui_solverListItem,
  461. IM_ARRAYSIZE(SHExampleComponent::imGui_solverListItem), 4))
  462. {
  463. m_updateDemoSRG = true;
  464. }
  465. if (ScriptableImGui::Checkbox("Distortion", &m_shaderInputEnableDistortion))
  466. {
  467. m_updateDemoSRG = true;
  468. }
  469. }
  470. else
  471. {
  472. if (ImGui::ListBox("Coefficient set", &m_shaderInputPresetIndex, SHExampleComponent::imGui_presetItem,
  473. IM_ARRAYSIZE(SHExampleComponent::imGui_presetItem), 4))
  474. {
  475. m_updateRenderSRG = true;
  476. }
  477. static int angle[3] = {0, 0, 0};
  478. static const float deg2rad = (AZ::Constants::Pi / 180.0);
  479. if (m_shaderInputPresetIndex == SHExampleComponent::FakeLightRotation)
  480. {
  481. if (ScriptableImGui::SliderInt("Set X rotation", &angle[0], 0, 360))
  482. {
  483. m_shaderInputRotationAngle.SetX(angle[0] * deg2rad);
  484. m_updateRenderSRG = true;
  485. }
  486. if (ScriptableImGui::SliderInt("Set Y rotation", &angle[1], 0, 360))
  487. {
  488. m_shaderInputRotationAngle.SetZ(angle[1] * deg2rad);
  489. m_updateRenderSRG = true;
  490. }
  491. if (ScriptableImGui::SliderInt("Set Z rotation", &angle[2], 0, 360))
  492. {
  493. // since SH computation assume z-up frame
  494. m_shaderInputRotationAngle.SetY(angle[2] * deg2rad);
  495. m_updateRenderSRG = true;
  496. }
  497. }
  498. ss = std::stringstream();
  499. ss << (m_shaderInputEnableGammaCorrection ? "Disable" : "Enable") << " Gamma Correction";
  500. if (ScriptableImGui::Button(ss.str().c_str()))
  501. {
  502. m_shaderInputEnableGammaCorrection = !m_shaderInputEnableGammaCorrection;
  503. m_updateRenderSRG = true;
  504. }
  505. static float sliderFloatValue = 1.0f;
  506. if (ScriptableImGui::SliderFloat("set exposure", &sliderFloatValue, 0.1f, 2.0f, "ratio = %.1f"))
  507. {
  508. m_shaderInputExposure = sliderFloatValue;
  509. m_updateRenderSRG = true;
  510. }
  511. static bool fakeLightDone = false;
  512. float progress = CalFakeLightSH(m_recomputeFakeLight);
  513. if (progress)
  514. {
  515. ImGui::Text("\n\nCalculaitng fake light SH: %.2f percent \n", progress * 100);
  516. if (m_shaderInputPresetIndex == SHExampleComponent::FakeLight)
  517. {
  518. m_updateRenderSRG = true;
  519. }
  520. }
  521. else
  522. {
  523. if (!fakeLightDone)
  524. {
  525. m_updateRenderSRG = true;
  526. fakeLightDone = true;
  527. }
  528. ImGui::Text("\n\nFake light SH ready to use, result: ");
  529. for (int32_t i = 0; i < 4; ++i)
  530. {
  531. for (int32_t j = -i; j <= i; ++j)
  532. {
  533. int32_t index = i * (i + 1) + j;
  534. const float temp = m_shaderInputSHFakeLightCoefficients.GetElement(index / 4, index % 4);
  535. ImGui::Text(" Band %d, Order %d: \n %f", i, j, temp);
  536. }
  537. }
  538. if (ScriptableImGui::Button("Recompute"))
  539. {
  540. fakeLightDone = false;
  541. m_recomputeFakeLight = true;
  542. }
  543. }
  544. }
  545. m_imguiSidebar.End();
  546. }
  547. }