LuminanceHeatmap.azsl 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. // This is a fullscreen debug pass that draws luminance/exposure debugging information.
  9. #include <Atom/Features/SrgSemantics.azsli>
  10. #include <viewsrg_all.srgi>
  11. #include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
  12. #include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
  13. #include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
  14. #include <Atom/Features/PostProcessing/GlyphRender.azsli>
  15. #include <Atom/Features/ColorManagement/TransformColor.azsli>
  16. #include <Atom/Features/CoreLights/PhotometricValue.azsli>
  17. #include <Atom/Features/Math/IntersectionTests.azsli>
  18. #include "LuminanceHistogramCommon.azsli"
  19. #include "EyeAdaptationUtil.azsli"
  20. ShaderResourceGroup PassSrg : SRG_PerPass
  21. {
  22. Texture2D<float4> m_framebuffer;
  23. // This is a mip chain containing the scene luminance info
  24. // x = min luminance
  25. // y = average luminance
  26. // z = max luminance
  27. Texture2D<float4> m_sceneLuminance;
  28. // This should be of size NUM_HISTOGRAM_BINS.
  29. StructuredBuffer<uint> m_histogram;
  30. Sampler LinearSampler
  31. {
  32. MinFilter = Linear;
  33. MagFilter = Linear;
  34. MipFilter = Linear;
  35. AddressU = Clamp;
  36. AddressV = Clamp;
  37. AddressW = Clamp;
  38. };
  39. }
  40. static const float HistogramHeightScale = 10;
  41. static const float HistogramGlyphPositionY = 0.965;
  42. static const float HistogramLeft = 0.0;
  43. static const float HistogramRight = 1.0;
  44. static const float HistogramTop = 0.7;
  45. static const float HistogramBottom = 0.92;
  46. static const float HistogramAlpha = 0.75;
  47. static const float3 UnderMinLuminanceColor = float3(0,0,1);
  48. static const float3 OverMaxLuminanceColor = float3(1,0,0);
  49. static const float3 HistogramBarChartColor = float3(1,1,1);
  50. static const float4 AvgLuminanceMarkerColor = float4(1,1,1,1);
  51. static const float4 BottomBackgroundColor = float4(0.4, 0.4, 0.4, HistogramAlpha);
  52. static const float AvgLuminanceMarkerHeight = 0.025;
  53. static const float AvgLuminanceMarkerHalfWidth = 0.015;
  54. static const int MaxNumGlyphs = 33;
  55. static const float GlyphSize = 0.22;
  56. float3 GetSceneLuminanceMinAvgMax()
  57. {
  58. // Get the dimensions from the luminance mip chain
  59. uint2 inputDimensions;
  60. uint numMipLevels = 1;
  61. PassSrg::m_sceneLuminance.GetDimensions(0, inputDimensions.x, inputDimensions.y, numMipLevels);
  62. // Use smallest 1x1 mip to get scene average luminance.
  63. float3 luminanceMinAvgMax = PassSrg::m_sceneLuminance.SampleLevel(PassSrg::LinearSampler, float2(0.5f, 0.5f), numMipLevels - 1).xyz;
  64. return luminanceMinAvgMax;
  65. }
  66. uint2 GetBackbufferResolution()
  67. {
  68. uint numLevels;
  69. uint2 dimensions;
  70. PassSrg::m_framebuffer.GetDimensions(0, dimensions.x, dimensions.y, numLevels);
  71. return dimensions;
  72. }
  73. uint GetNumPixelsInBackBuffer()
  74. {
  75. uint2 dimensions = GetBackbufferResolution();
  76. return dimensions.x * dimensions.y;
  77. }
  78. uint GetHistogramBinCount(const int bin)
  79. {
  80. return PassSrg::m_histogram[bin];
  81. }
  82. float GetHistogramHeight(const int bin)
  83. {
  84. uint numPixels = GetNumPixelsInBackBuffer();
  85. return (float)GetHistogramBinCount(bin) / (float)numPixels * HistogramHeightScale;
  86. }
  87. float2 GetEvRangeForBin(const int bin)
  88. {
  89. float2 evDisplayRange = GetEvDisplayRangeMinMax();
  90. float binWidth = (evDisplayRange.y - evDisplayRange.x) / (float)NUM_HISTOGRAM_BINS;
  91. float2 evRange;
  92. evRange.x = bin * binWidth + evDisplayRange.x;
  93. evRange.y = evRange.x + binWidth;
  94. return evRange;
  95. }
  96. // Returns the color for the given bin
  97. // This will highlight the bins that are over or under the max/min luminance with a different color
  98. float3 GetHistogramColor(const int bin)
  99. {
  100. const float exposureMinLog2 = ViewSrg::m_exposureControl.m_exposureMinLog2;
  101. const float exposureMaxLog2 = ViewSrg::m_exposureControl.m_exposureMaxLog2;
  102. const float minLuminance = EV100LuminanceToNits(exposureMinLog2);
  103. const float maxLuminance = EV100LuminanceToNits(exposureMaxLog2);
  104. const float2 evRangeForBin = GetEvRangeForBin(bin);
  105. if (evRangeForBin.x < exposureMinLog2)
  106. {
  107. return UnderMinLuminanceColor;
  108. }
  109. if (evRangeForBin.y > exposureMaxLog2)
  110. {
  111. return OverMaxLuminanceColor;
  112. }
  113. return HistogramBarChartColor;
  114. }
  115. float2 NormalizeUv(float2 uv, float2 origin, float2 dim)
  116. {
  117. float2 uvResult = uv - float2(origin.x, origin.y);
  118. uvResult *= rcp(dim);
  119. return uvResult;
  120. }
  121. float2 NormalizeHistogramUV(float2 uv)
  122. {
  123. const float2 origin = float2(HistogramLeft, HistogramTop);
  124. const float2 dim = float2(HistogramRight - HistogramLeft, HistogramBottom - HistogramTop);
  125. return NormalizeUv(uv, origin, dim);
  126. }
  127. int GetHistogramBinFromUvCoord(float2 uv)
  128. {
  129. if (uv.x < 0 || uv.y < 0 || uv.x > 1 || uv.y > 1)
  130. {
  131. return -1;
  132. }
  133. return uv.x * NUM_HISTOGRAM_BINS;
  134. }
  135. float4 DrawLuminanceHistogram(const float2 screenUv)
  136. {
  137. const float2 histogramUv = NormalizeHistogramUV(screenUv);
  138. const int bin = GetHistogramBinFromUvCoord(histogramUv);
  139. if (bin == -1)
  140. {
  141. return 0;
  142. }
  143. const bool histogramHeightCheck = (1 - histogramUv.y) > GetHistogramHeight(bin);
  144. if (histogramHeightCheck)
  145. {
  146. return 0;
  147. }
  148. return float4(GetHistogramColor(bin), HistogramAlpha);
  149. }
  150. float4 FramebufferColorToHeatmapColor(const float3 color)
  151. {
  152. const float luminanceHere = CalculateLuminance(color.rgb, ColorSpaceId::ACEScg);
  153. const float exposureMinLog2 = ViewSrg::m_exposureControl.m_exposureMinLog2;
  154. const float exposureMaxLog2 = ViewSrg::m_exposureControl.m_exposureMaxLog2;
  155. const float minLuminance = EV100LuminanceToNits(exposureMinLog2);
  156. const float maxLuminance = EV100LuminanceToNits(exposureMaxLog2);
  157. if (luminanceHere < minLuminance)
  158. {
  159. return float4(UnderMinLuminanceColor, 1);
  160. }
  161. else if (luminanceHere > maxLuminance)
  162. {
  163. return float4(OverMaxLuminanceColor, 1);
  164. }
  165. else
  166. {
  167. return 0;
  168. }
  169. }
  170. float4 DrawBottomBackground(float2 uv)
  171. {
  172. if (uv.y < HistogramBottom)
  173. {
  174. return 0;
  175. }
  176. else
  177. {
  178. return BottomBackgroundColor;
  179. }
  180. }
  181. float4 DrawAvgLuminanceMarker(const float2 uv)
  182. {
  183. const float triangleTopY = HistogramBottom;
  184. const float triangleBottomY = HistogramBottom + AvgLuminanceMarkerHeight;
  185. if (uv.y < triangleTopY || uv.y > triangleBottomY)
  186. {
  187. return 0;
  188. }
  189. const float lumAvg = GetSceneLuminanceMinAvgMax().y;
  190. const float evAvg = NitsToEv100Luminance(lumAvg);
  191. const float triangleTopX = (evAvg - GetEvDisplayRangeMinMax().x) / (GetEvDisplayRangeMinMax().y - GetEvDisplayRangeMinMax().x);
  192. const float triangleLeft = triangleTopX - AvgLuminanceMarkerHalfWidth;
  193. const float triangleRight = triangleTopX + AvgLuminanceMarkerHalfWidth;
  194. if (uv.x < triangleLeft || uv.x > triangleRight)
  195. {
  196. return 0;
  197. }
  198. const float2 triangleTop = float2(triangleTopX, triangleTopY);
  199. const float2 triangleBottomLeft = float2(triangleLeft, triangleBottomY);
  200. const float2 triangleBottomRight = float2(triangleRight, triangleBottomY);
  201. return IsPointInsideTriangle(uv, triangleTop, triangleBottomLeft, triangleBottomRight) ? AvgLuminanceMarkerColor : 0;
  202. }
  203. int NumDigits(int value)
  204. {
  205. int numDigits = value < 0 ? 1 : 0;
  206. value = abs(value);
  207. do
  208. {
  209. numDigits++;
  210. value /= 10;
  211. } while (value);
  212. return numDigits;
  213. }
  214. // Note that the glyph system draws from right to left in screenspace
  215. // Given a number that we want to print, this function will return the offset needed to make sure the text is better centered
  216. // e.g. printing "1" will need a tiny offset to center the digit, but printing "-1234.9" will require a larger offset to center this text
  217. float CalculateOffsetToCenterGlyph(const int number)
  218. {
  219. static const float centeringOffset = 0.006;
  220. return NumDigits(number) * 0.5 * centeringOffset;
  221. }
  222. int CalculateNumGlyphs()
  223. {
  224. const float displayRange = GetEvDisplayRangeMinMax().y - GetEvDisplayRangeMinMax().x;
  225. int numGlyphs = floor(displayRange);
  226. numGlyphs = min(numGlyphs, MaxNumGlyphs);
  227. return numGlyphs;
  228. }
  229. float4 DrawEvNumbers(const float2 uv)
  230. {
  231. GlyphRender glyphRender;
  232. glyphRender.Init(GetBackbufferResolution());
  233. const int numGlyphs = CalculateNumGlyphs();
  234. const float2 displayRange = GetEvDisplayRangeMinMax();
  235. const float evSceneIncrement = (displayRange.y - displayRange.x) / (numGlyphs - 1);
  236. float evMarker = displayRange.x;
  237. float4 color = 0;
  238. for(int i = 0 ; i < numGlyphs ; ++i)
  239. {
  240. const int number = round(evMarker);
  241. const float glyphPositionX = i / float(numGlyphs - 1) + CalculateOffsetToCenterGlyph(number);
  242. color += glyphRender.DrawNumberUvSpace(number, uv, float2(glyphPositionX, HistogramGlyphPositionY), GlyphSize);
  243. if (color.a > 0)
  244. {
  245. break;
  246. }
  247. evMarker += evSceneIncrement;
  248. }
  249. return color;
  250. }
  251. float4 DrawHeatmap(const float2 uv)
  252. {
  253. const float4 frameBufferColor = PassSrg::m_framebuffer.Sample(PassSrg::LinearSampler, uv);
  254. return FramebufferColorToHeatmapColor(frameBufferColor.rgb);
  255. }
  256. PSOutput MainPS(VSOutput IN)
  257. {
  258. PSOutput OUT;
  259. OUT.m_color = DrawAvgLuminanceMarker(IN.m_texCoord);
  260. if (OUT.m_color.a > 0)
  261. {
  262. return OUT;
  263. }
  264. OUT.m_color = DrawLuminanceHistogram(IN.m_texCoord);
  265. if (OUT.m_color.a > 0)
  266. {
  267. return OUT;
  268. }
  269. OUT.m_color = DrawEvNumbers(IN.m_texCoord);
  270. if (OUT.m_color.a > 0)
  271. {
  272. return OUT;
  273. }
  274. OUT.m_color = DrawBottomBackground(IN.m_texCoord);
  275. if (OUT.m_color.a > 0)
  276. {
  277. return OUT;
  278. }
  279. OUT.m_color = DrawHeatmap(IN.m_texCoord);
  280. return OUT;
  281. }