SHRender.azsl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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 <viewsrg.srgi>
  9. #include <Atom/Features/SphericalHarmonicsUtility.azsli>
  10. #include <Atom/RPI/Math.azsli>
  11. // RGB per row
  12. // first 3 bands for Grace Cathedral probe available at here:
  13. // https://www.pauldebevec.com/Probes/
  14. // calculated as c_l_m = integral{ Radiance(s) * SHBasis(l, m, s) } for all sampled direction s on sphere S
  15. // which runs for each color channel separately (c_l_m_r, c_l_m_g, c_l_m_b)
  16. // the lower hemi sphere radiances under the surface are filtered by lambertian cosine term
  17. static const float GraceCathedralSH[27] = {
  18. // R
  19. 2.53951384635872,
  20. 1.0775523857285165, 1.239229205484427 , 0.9458659067630454 ,
  21. 1.782942445353695 , 0.8193052111265798, -0.3359348352648975, 0.3709499352580087, 0.7844904694566587,
  22. // G
  23. 1.405939440477567,
  24. 0.5737359819468685, 1.0978087141208104, 0.18506274053816416,
  25. 0.6811688402789957, 0.7023431983214563, 0.3056738462214631 , 0.1753565473744576, 0.1778660985880454,
  26. // B
  27. 1.748271935016665,
  28. 0.8526870003819748, 1.9101990711004575, -0.025668773449626654,
  29. 0.4438016469839106, 1.5102998945629529, 1.093993744201323, 0.4109905263687058, -0.07419514044613942
  30. };
  31. // SH coefficients for Eucalyptus Grove probe
  32. static const float RNLSH[27] = {
  33. // R
  34. 3.766055529641367,
  35. -0.40406140148206865, 2.877179048074897, 1.0167815002471956,
  36. -0.5853821006554704, -0.09935292918366617, 0.28423834881566207, 0.6222773907470484, 0.8885327769488396,
  37. // G
  38. 4.2447416994629465,
  39. -0.3207608858594331, 3.5761459663568447, 1.014234018050483,
  40. -0.5164785598628846, 0.12420621153110219, 0.6557821044358743, 0.556331752796699, 1.0410020790843508,
  41. // B
  42. 4.491401630981911,
  43. -0.12215412094184247, 4.136091787179279, 0.8658157727284175,
  44. -0.377989948123863, 0.4461962278000395, 1.1430202258191537, 0.4006585445306217, 1.0740016349582413
  45. };
  46. // SH coefficients for St Peter's Basilica probe
  47. static const float StPeterSH[27] = {
  48. // R
  49. 3.5803230858525286,
  50. 0.266039684453623, 1.6925803298077324, -0.35934100317952267,
  51. 0.04656037807347018, 0.4358724537888737, 1.2687743552404271, -0.18570076550676343, 0.33246625615466635,
  52. // G
  53. 2.5747666746640654,
  54. 0.10749801820011659, 1.3790985820712267, -0.2352465711852622,
  55. 0.037522972624484646, 0.23694792243134288, 0.7304912926110536, -0.1382374891588147, 0.46604897024599046,
  56. // B
  57. 2.2780129003295295,
  58. 0.013810896524256528, 1.2053005811787343, -0.10786941964227027,
  59. 0.001560121273374082, 0.10741082229332044, 0.3891549382974423, -0.038006070134113876, 0.6299315230288755
  60. };
  61. ShaderResourceGroup SphericalHarmonicsInstanceSrg : SRG_PerObject
  62. {
  63. int m_presetIndex;
  64. float m_exposure;
  65. bool m_enableGammaCorrection;
  66. float3 m_rotationAngle;
  67. column_major float4x4 m_objectMatrix;
  68. row_major float4x4 m_fakeLightSH;
  69. }
  70. struct VSInput
  71. {
  72. float3 m_position : POSITION;
  73. float2 m_uv : UV0;
  74. };
  75. struct VSOutput
  76. {
  77. float4 m_position : SV_Position;
  78. float2 m_uv : UV0;
  79. };
  80. VSOutput MainVS(VSInput vsInput)
  81. {
  82. VSOutput OUT;
  83. OUT.m_position = mul(float4(vsInput.m_position, 1.0), SphericalHarmonicsInstanceSrg::m_objectMatrix);
  84. OUT.m_uv = vsInput.m_uv;
  85. return OUT;
  86. }
  87. struct PSOutput
  88. {
  89. float4 m_color : SV_Target0;
  90. };
  91. float3 CalFakeLight(float3 dir)
  92. {
  93. float3 radiance = float3(0.0, 0.0, 0.0);
  94. // RGB, band 0
  95. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][0] * SHBasisPoly3(0, 0, dir);
  96. // RGB, band 1
  97. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][1] * SHBasisPoly3(1, -1, dir);
  98. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][2] * SHBasisPoly3(1, 0, dir);
  99. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][3] * SHBasisPoly3(1, 1, dir);
  100. // RGB, band 2
  101. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][0] * SHBasisPoly3(2, -2, dir);
  102. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][1] * SHBasisPoly3(2, -1, dir);
  103. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][2] * SHBasisPoly3(2, 0, dir);
  104. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][3] * SHBasisPoly3(2, 1, dir);
  105. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[2][0] * SHBasisPoly3(2, 2, dir);
  106. // RGB, band 3
  107. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[2][1] * SHBasisPoly3(3, -3, dir);
  108. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[2][2] * SHBasisPoly3(3, -2, dir);
  109. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[2][3] * SHBasisPoly3(3, 1, dir);
  110. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[3][0] * SHBasisPoly3(3, 0, dir);
  111. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[3][1] * SHBasisPoly3(3, 1, dir);
  112. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[3][2] * SHBasisPoly3(3, 2, dir);
  113. radiance.xyz += SphericalHarmonicsInstanceSrg::m_fakeLightSH[3][3] * SHBasisPoly3(3, 3, dir);
  114. return radiance;
  115. }
  116. float3 CalFakeLightOriginal(float3 dir)
  117. {
  118. float theta = acos(dir.y);
  119. float phi = atan2(dir.z, dir.x);
  120. float3 res = float3(0.0, 0.0, 0.0);
  121. res = 5.0 * (max(0.0, 5.0 * cos(theta) - 4.0) +
  122. max(0.0, -4.0 * sin(theta - PI) * cos(phi - 2.5) - 3.0));
  123. return res;
  124. }
  125. float3x3 EulerToRotMatrix(float3 angle)
  126. {
  127. row_major float3x3 rotationMat;
  128. row_major float3x3 Rx = {
  129. 1.0, 0.0, 0.0,
  130. 0.0, cos(angle.x), -sin(angle.x),
  131. 0.0, sin(angle.x), cos(angle.x)
  132. };
  133. row_major float3x3 Ry = {
  134. cos(angle.y), 0.0, sin(angle.y),
  135. 0.0, 1.0, 0.0,
  136. -sin(angle.y), 0.0, cos(angle.y)
  137. };
  138. row_major float3x3 Rz = {
  139. cos(angle.z), -sin(angle.z), 0.0,
  140. sin(angle.z), cos(angle.z), 0.0,
  141. 0.0, 0.0, 1.0
  142. };
  143. rotationMat = mul(Rz, mul(Ry, Rx));
  144. return rotationMat;
  145. }
  146. float3 CalFakeLightRotated(float3 dir, float3 angle)
  147. {
  148. float3 radiance = float3(0.0, 0.0, 0.0);
  149. // include first 3 bands only since the rotation function only support 3,
  150. // rotation of higher band is recommended to use standard WignerD provided on CPU side
  151. row_major float3x3 Rot = EulerToRotMatrix(angle);
  152. float sh[9] = {
  153. SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][0],
  154. SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][1],
  155. SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][2],
  156. SphericalHarmonicsInstanceSrg::m_fakeLightSH[0][3],
  157. SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][0],
  158. SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][1],
  159. SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][2],
  160. SphericalHarmonicsInstanceSrg::m_fakeLightSH[1][3],
  161. SphericalHarmonicsInstanceSrg::m_fakeLightSH[2][0]
  162. };
  163. float shRot[9];
  164. SHRotationZHF3(Rot, sh, shRot);
  165. // RGB, band 0
  166. radiance.xyz += shRot[0] * SHBasisPoly3(0, 0, dir);
  167. // RGB, band 1
  168. radiance.xyz += shRot[1] * SHBasisPoly3(1, -1, dir);
  169. radiance.xyz += shRot[2] * SHBasisPoly3(1, 0, dir);
  170. radiance.xyz += shRot[3] * SHBasisPoly3(1, 1, dir);
  171. // RGB, band 2
  172. radiance.xyz += shRot[4] * SHBasisPoly3(2, -2, dir);
  173. radiance.xyz += shRot[5] * SHBasisPoly3(2, -1, dir);
  174. radiance.xyz += shRot[6] * SHBasisPoly3(2, 0, dir);
  175. radiance.xyz += shRot[7] * SHBasisPoly3(2, 1, dir);
  176. radiance.xyz += shRot[8] * SHBasisPoly3(2, 2, dir);
  177. return radiance;
  178. }
  179. // The name of variable follows following resource:
  180. // https://www.gdcvault.com/play/1012351/Uncharted-2-HDR
  181. float3 Uncharted2ToneMapping(float3 color, float exposure)
  182. {
  183. float A = 0.15;
  184. float B = 0.50;
  185. float C = 0.10;
  186. float D = 0.20;
  187. float E = 0.02;
  188. float F = 0.30;
  189. float W = 11.2;
  190. color *= exposure;
  191. color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;
  192. float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
  193. color /= white;
  194. return color;
  195. }
  196. // Ray marcher for SH visualisation, based on https://www.shadertoy.com/view/lsfXWH
  197. PSOutput MainPS(VSOutput psInput)
  198. {
  199. // all following calculations and coordinates are in world space
  200. PSOutput OUT;
  201. float3 hdrColor = float3(0.0, 0.0, 0.0);
  202. // inverse of rotation matrix is its transpose due to orthogonality
  203. // position of translation part won't affect result since it's not used here
  204. float4x4 invView = transpose(ViewSrg::m_viewMatrix);
  205. float2 recenteredUV = psInput.m_uv - float2(0.5, 0.5);
  206. // -1 because -z forward frame is used
  207. float3 viewSpaceRayDir = float3(recenteredUV.x, recenteredUV.y, -1.0);
  208. // world space ray origin & direction
  209. float3 rayDir = normalize(mul(invView, float4(viewSpaceRayDir, 0.0)).xyz);
  210. float3 rayOrigin = ViewSrg::m_worldPosition.xyz;
  211. // length of ray at intersection point, -1 if miss
  212. float t = RaySphereClosestHitWS(float3(0, 0, 0), 0.5, rayOrigin, rayDir);
  213. // only do shading if hit anything
  214. if(t > 0.0)
  215. {
  216. float3 pos = rayOrigin + rayDir * t;
  217. // the normal of points on unit sphere at the origin is its position
  218. float3 normal = normalize(pos);
  219. float3 outRadiance = float3(0.0, 0.0, 0.0);
  220. switch(SphericalHarmonicsInstanceSrg::m_presetIndex)
  221. {
  222. case 0: outRadiance = EvalSH2RadianceRGB(normal, GraceCathedralSH); break;
  223. case 1: outRadiance = EvalSH2RadianceRGB(normal, RNLSH ); break;
  224. case 2: outRadiance = EvalSH2RadianceRGB(normal, StPeterSH ); break;
  225. case 3: outRadiance = CalFakeLight(normal); break;
  226. case 4: outRadiance = CalFakeLightOriginal(normal); break;
  227. case 5: outRadiance = CalFakeLightRotated(normal, SphericalHarmonicsInstanceSrg::m_rotationAngle);
  228. }
  229. hdrColor = Uncharted2ToneMapping(max(outRadiance, 0.0), SphericalHarmonicsInstanceSrg::m_exposure);
  230. }
  231. if(SphericalHarmonicsInstanceSrg::m_enableGammaCorrection)
  232. {
  233. hdrColor = sqrt(hdrColor);
  234. }
  235. OUT.m_color = float4(hdrColor, 1.0);
  236. return OUT;
  237. }