PPEyeAdaptation.bsl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #include "$ENGINE$\PPBase.bslinc"
  2. Parameters =
  3. {
  4. Texture2D gHistogramTex;
  5. };
  6. Blocks =
  7. {
  8. Block Input;
  9. };
  10. Technique : inherits("PPBase") =
  11. {
  12. Language = "HLSL11";
  13. Pass =
  14. {
  15. Fragment =
  16. {
  17. #define NUM_BUCKETS (THREADGROUP_SIZE_X * THREADGROUP_SIZE_Y)
  18. cbuffer Input
  19. {
  20. // [0]: x - histogram scale, y - histogram offset, z - histogram percent low, w - histogram percent high
  21. // [1]: x - min adaptation, y - max adaptation, z - adaptation speed up, w - adaptation speed down
  22. // [2]: x - exposure scale, y - frame time delta, zw - nothing
  23. float4 gEyeAdaptationParams[3];
  24. }
  25. Texture2D gHistogramTex;
  26. /**
  27. * Returns luminance of the histogram bucket.
  28. *
  29. * @param pos Position of the histogram bucket in range [0, 1].
  30. * @return Luminance of the bucket.
  31. */
  32. float calcHistogramLuminance(float pos)
  33. {
  34. return exp2((pos - gEyeAdaptationParams[0].y) / gEyeAdaptationParams[0].x);
  35. }
  36. /**
  37. * Returns value of the histogram bucket.
  38. *
  39. * @param histogram Texture containing the histogram buckets in the first row.
  40. * @param bucketIdx Index of the bucket. Caller must ensure it is in valid range.
  41. * @return Value of the needed histogram bucket.
  42. */
  43. float getHistogramValue(Texture2D histogram, uint bucketIdx)
  44. {
  45. uint texelIdx = bucketIdx / 4;
  46. float4 packedValue = histogram.Load(int3(texelIdx, 0, 0));
  47. float4 mask = float4(
  48. (bucketIdx % 4) == 0,
  49. (bucketIdx % 4) == 1,
  50. (bucketIdx % 4) == 2,
  51. (bucketIdx % 4) == 3);
  52. return dot(packedValue, mask);
  53. }
  54. /**
  55. * Calculates the sum of all values in the histogram.
  56. *
  57. * @param histogram Texture containing the histogram buckets in the first row.
  58. * @return Sum of all the values in the histogram.
  59. */
  60. float calcHistogramSum(Texture2D histogram)
  61. {
  62. float sum = 0;
  63. for(uint i = 0; i < NUM_BUCKETS; i++)
  64. sum += getHistogramValue(histogram, i);
  65. return sum;
  66. }
  67. /**
  68. * Calculates the average luminance in the histogram, while ignoring the outlier values that may skew the result.
  69. *
  70. * @param histogram Texture containing the histogram buckets in the first row.
  71. * @param low Sum below which to ignore values (removing lower end outliers), in range [0, histogramSum].
  72. * @param high Sum above which to ignore values (removing higher end outliers), in range [0, histogramSum].
  73. * Must be higher than @low.
  74. * @return Average luminance in the histogram.
  75. */
  76. float calcHistogramAverageLuminance(Texture2D histogram, float low, float high)
  77. {
  78. float2 sumAndWeight = float2(0.0f, 0.0f);
  79. for(uint i = 0; i < NUM_BUCKETS; i++)
  80. {
  81. float value = getHistogramValue(histogram, i);
  82. // Ignore any values below the @low parameter, and then shift the valid range
  83. // by the amount we ignored. Eventually the low end of the range reaches zero
  84. // and values are no longer ignored.
  85. float offset = min(value, low);
  86. value = value - offset;
  87. low -= offset;
  88. high -= offset;
  89. // Ignore any values above the @high parameter, and then shift the valid range.
  90. value = min(value, high);
  91. high -= value;
  92. float histogramPos = i / (float)NUM_BUCKETS;
  93. float luminance = calcHistogramLuminance(histogramPos);
  94. sumAndWeight += float2(luminance, 1) * value;
  95. }
  96. return sumAndWeight.x / max(0.0001f, sumAndWeight.y);
  97. }
  98. /**
  99. * Calculates the eye adaptation from the luminance in the provided histogram. Eye adaptation value will be
  100. * used for automatically scaling expsure based on scene brightness.
  101. *
  102. * @param histogram Texture containing the histogram buckets in the first row.
  103. * @return Ideal eye adaptation value for the provided luminance.
  104. */
  105. float calcEyeAdaptation(Texture2D histogram)
  106. {
  107. float sum = calcHistogramSum(histogram);
  108. float lowRange = gEyeAdaptationParams[0].z * sum;
  109. float highRange = gEyeAdaptationParams[0].w * sum;
  110. float avgLuminance = calcHistogramAverageLuminance(histogram, lowRange, highRange);
  111. avgLuminance = clamp(avgLuminance, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
  112. return avgLuminance;
  113. }
  114. /**
  115. * Smooths out eye adaptation changes over multiple frames so they aren't as jarring.
  116. *
  117. * @param old Eye adaptation value from the previous frame.
  118. * @param target Ideal eye adaptation value for this frame.
  119. * @param frameDelta Time difference between this and last frame, in seconds.
  120. * @return Smoothed eye adaptation.
  121. */
  122. float smoothEyeAdaptation(float old, float target, float frameDelta)
  123. {
  124. float diff = target - old;
  125. float speedUp = gEyeAdaptationParams[1].z;
  126. float speedDown = gEyeAdaptationParams[1].w;
  127. float adaptionSpeed = (diff > 0) ? speedUp : speedDown;
  128. float scale = 1.0f - exp2(-frameDelta * adaptionSpeed);
  129. return clamp(old + diff * scale, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
  130. }
  131. float4 main(VStoFS input) : SV_Target0
  132. {
  133. float exposureScale = gEyeAdaptationParams[2].x;
  134. float targetAdaptation = calcEyeAdaptation(gHistogramTex);
  135. float oldExposure = gHistogramTex.Load(int3(0, 1, 0)).x;
  136. float oldAdaptation = exposureScale / oldExposure; // Assuming same exposure scale as last frame
  137. float frameDelta = gEyeAdaptationParams[2].y;
  138. float smoothAdaptation = smoothEyeAdaptation(oldAdaptation, targetAdaptation, frameDelta);
  139. return exposureScale / smoothAdaptation; // Returns exposure
  140. }
  141. };
  142. };
  143. };
  144. Technique : inherits("PPBase") =
  145. {
  146. Language = "GLSL";
  147. Pass =
  148. {
  149. Fragment =
  150. {
  151. #define NUM_BUCKETS (THREADGROUP_SIZE_X * THREADGROUP_SIZE_Y)
  152. uniform Input
  153. {
  154. // [0]: x - histogram scale, y - histogram offset, z - histogram percent low, w - histogram percent high
  155. // [1]: x - min adaptation, y - max adaptation, z - adaptation speed up, w - adaptation speed down
  156. // [2]: x - exposure scale, y - frame time delta, zw - nothing
  157. vec4 gEyeAdaptationParams[3];
  158. };
  159. uniform sampler2D gHistogramTex;
  160. /**
  161. * Returns luminance of the histogram bucket.
  162. *
  163. * @param pos Position of the histogram bucket in range [0, 1].
  164. * @return Luminance of the bucket.
  165. */
  166. void calcHistogramLuminance(float pos, out float result)
  167. {
  168. result = exp2((pos - gEyeAdaptationParams[0].y) / gEyeAdaptationParams[0].x);
  169. }
  170. /**
  171. * Returns value of the histogram bucket.
  172. *
  173. * @param histogram Texture containing the histogram buckets in the first row.
  174. * @param bucketIdx Index of the bucket. Caller must ensure it is in valid range.
  175. * @return Value of the needed histogram bucket.
  176. */
  177. void getHistogramValue(sampler2D histogram, uint bucketIdx, out float result)
  178. {
  179. uint texelIdx = bucketIdx / 4;
  180. vec4 packedValue = texelFetch(histogram, ivec2(texelIdx, 0), 0);
  181. vec4 mask = vec4(
  182. (bucketIdx % 4) == 0,
  183. (bucketIdx % 4) == 1,
  184. (bucketIdx % 4) == 2,
  185. (bucketIdx % 4) == 3);
  186. result = dot(packedValue, mask);
  187. }
  188. /**
  189. * Calculates the sum of all values in the histogram.
  190. *
  191. * @param histogram Texture containing the histogram buckets in the first row.
  192. * @return Sum of all the values in the histogram.
  193. */
  194. void calcHistogramSum(sampler2D histogram, out float result)
  195. {
  196. float sum = 0;
  197. for(uint i = 0; i < NUM_BUCKETS; i++)
  198. {
  199. float histogramValue;
  200. getHistogramValue(histogram, i, histogramValue);
  201. sum += histogramValue;
  202. }
  203. result = sum;
  204. }
  205. /**
  206. * Calculates the average luminance in the histogram, while ignoring the outlier values that may skew the result.
  207. *
  208. * @param histogram Texture containing the histogram buckets in the first row.
  209. * @param low Sum below which to ignore values (removing lower end outliers), in range [0, histogramSum].
  210. * @param high Sum above which to ignore values (removing higher end outliers), in range [0, histogramSum].
  211. * Must be higher than @low.
  212. * @return Average luminance in the histogram.
  213. */
  214. void calcHistogramAverageLuminance(sampler2D histogram, float low, float high, out float result)
  215. {
  216. vec2 sumAndWeight = vec2(0.0f, 0.0f);
  217. for(uint i = 0; i < NUM_BUCKETS; i++)
  218. {
  219. float value;
  220. getHistogramValue(histogram, i, value);
  221. // Ignore any values below the @low parameter, and then shift the valid range
  222. // by the amount we ignored. Eventually the low end of the range reaches zero
  223. // and values are no longer ignored.
  224. float offset = min(value, low);
  225. value = value - offset;
  226. low -= offset;
  227. high -= offset;
  228. // Ignore any values above the @high parameter, and then shift the valid range.
  229. value = min(value, high);
  230. high -= value;
  231. float histogramPos = i / float(NUM_BUCKETS);
  232. float luminance;
  233. calcHistogramLuminance(histogramPos, luminance);
  234. sumAndWeight += vec2(luminance, 1) * value;
  235. }
  236. result = sumAndWeight.x / max(0.0001f, sumAndWeight.y);
  237. }
  238. /**
  239. * Calculates the eye adaptation from the luminance in the provided histogram. Eye adaptation value will be
  240. * used for automatically scaling expsure based on scene brightness.
  241. *
  242. * @param histogram Texture containing the histogram buckets in the first row.
  243. * @return Ideal eye adaptation value for the provided luminance.
  244. */
  245. void calcEyeAdaptation(sampler2D histogram, out float result)
  246. {
  247. float sum;
  248. calcHistogramSum(histogram, sum);
  249. float lowRange = gEyeAdaptationParams[0].z * sum;
  250. float highRange = gEyeAdaptationParams[0].w * sum;
  251. float avgLuminance;
  252. calcHistogramAverageLuminance(histogram, lowRange, highRange, avgLuminance);
  253. avgLuminance = clamp(avgLuminance, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
  254. result = avgLuminance;
  255. }
  256. /**
  257. * Smooths out eye adaptation changes over multiple frames so they aren't as jarring.
  258. *
  259. * @param old Eye adaptation value from the previous frame.
  260. * @param target Ideal eye adaptation value for this frame.
  261. * @param frameDelta Time difference between this and last frame, in seconds.
  262. * @return Smoothed eye adaptation.
  263. */
  264. void smoothEyeAdaptation(float old, float target, float frameDelta, out float result)
  265. {
  266. float diff = target - old;
  267. float speedUp = gEyeAdaptationParams[1].z;
  268. float speedDown = gEyeAdaptationParams[1].w;
  269. float adaptionSpeed = (diff > 0) ? speedUp : speedDown;
  270. float scale = 1.0f - exp2(-frameDelta * adaptionSpeed);
  271. result = clamp(old + diff * scale, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
  272. }
  273. in VStoFS
  274. {
  275. vec2 uv0;
  276. } VSInput;
  277. out vec4 fragColor;
  278. void main()
  279. {
  280. float exposureScale = gEyeAdaptationParams[2].x;
  281. float targetAdaptation;
  282. calcEyeAdaptation(gHistogramTex, targetAdaptation);
  283. float oldExposure = texelFetch(gHistogramTex, ivec2(0, 1), 0).x;
  284. float oldAdaptation = exposureScale / oldExposure; // Assuming same exposure scale as last frame
  285. float frameDelta = gEyeAdaptationParams[2].y;
  286. float smoothAdaptation;
  287. smoothEyeAdaptation(oldAdaptation, targetAdaptation, frameDelta, smoothAdaptation);
  288. fragColor = vec4(exposureScale / smoothAdaptation); // Returns exposure
  289. }
  290. };
  291. };
  292. };