AutoExposure.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Color from "../Core/Color.js";
  3. import defined from "../Core/defined.js";
  4. import destroyObject from "../Core/destroyObject.js";
  5. import ClearCommand from "../Renderer/ClearCommand.js";
  6. import FramebufferManager from "../Renderer/FramebufferManager.js";
  7. import PixelDatatype from "../Renderer/PixelDatatype.js";
  8. /**
  9. * A post process stage that will get the luminance value at each pixel and
  10. * uses parallel reduction to compute the average luminance in a 1x1 texture.
  11. * This texture can be used as input for tone mapping.
  12. *
  13. * @constructor
  14. * @private
  15. */
  16. function AutoExposure() {
  17. this._uniformMap = undefined;
  18. this._command = undefined;
  19. this._colorTexture = undefined;
  20. this._depthTexture = undefined;
  21. this._ready = false;
  22. this._name = "czm_autoexposure";
  23. this._logDepthChanged = undefined;
  24. this._useLogDepth = undefined;
  25. this._framebuffers = undefined;
  26. this._previousLuminance = new FramebufferManager();
  27. this._commands = undefined;
  28. this._clearCommand = undefined;
  29. this._minMaxLuminance = new Cartesian2();
  30. /**
  31. * Whether or not to execute this post-process stage when ready.
  32. *
  33. * @type {boolean}
  34. */
  35. this.enabled = true;
  36. this._enabled = true;
  37. /**
  38. * The minimum value used to clamp the luminance.
  39. *
  40. * @type {number}
  41. * @default 0.1
  42. */
  43. this.minimumLuminance = 0.1;
  44. /**
  45. * The maximum value used to clamp the luminance.
  46. *
  47. * @type {number}
  48. * @default 10.0
  49. */
  50. this.maximumLuminance = 10.0;
  51. }
  52. Object.defineProperties(AutoExposure.prototype, {
  53. /**
  54. * Determines if this post-process stage is ready to be executed. A stage is only executed when both <code>ready</code>
  55. * and {@link AutoExposure#enabled} are <code>true</code>. A stage will not be ready while it is waiting on textures
  56. * to load.
  57. *
  58. * @memberof AutoExposure.prototype
  59. * @type {boolean}
  60. * @readonly
  61. */
  62. ready: {
  63. get: function () {
  64. return this._ready;
  65. },
  66. },
  67. /**
  68. * The unique name of this post-process stage for reference by other stages.
  69. *
  70. * @memberof AutoExposure.prototype
  71. * @type {string}
  72. * @readonly
  73. */
  74. name: {
  75. get: function () {
  76. return this._name;
  77. },
  78. },
  79. /**
  80. * A reference to the texture written to when executing this post process stage.
  81. *
  82. * @memberof AutoExposure.prototype
  83. * @type {Texture}
  84. * @readonly
  85. * @private
  86. */
  87. outputTexture: {
  88. get: function () {
  89. const framebuffers = this._framebuffers;
  90. if (!defined(framebuffers)) {
  91. return undefined;
  92. }
  93. return framebuffers[framebuffers.length - 1].getColorTexture(0);
  94. },
  95. },
  96. });
  97. function destroyFramebuffers(autoexposure) {
  98. const framebuffers = autoexposure._framebuffers;
  99. if (!defined(framebuffers)) {
  100. return;
  101. }
  102. const length = framebuffers.length;
  103. for (let i = 0; i < length; ++i) {
  104. framebuffers[i].destroy();
  105. }
  106. autoexposure._framebuffers = undefined;
  107. autoexposure._previousLuminance.destroy();
  108. autoexposure._previousLuminance = undefined;
  109. }
  110. function createFramebuffers(autoexposure, context) {
  111. destroyFramebuffers(autoexposure);
  112. let width = autoexposure._width;
  113. let height = autoexposure._height;
  114. const pixelDatatype = context.halfFloatingPointTexture
  115. ? PixelDatatype.HALF_FLOAT
  116. : PixelDatatype.FLOAT;
  117. const length = Math.ceil(Math.log(Math.max(width, height)) / Math.log(3.0));
  118. const framebuffers = new Array(length);
  119. for (let i = 0; i < length; ++i) {
  120. width = Math.max(Math.ceil(width / 3.0), 1.0);
  121. height = Math.max(Math.ceil(height / 3.0), 1.0);
  122. framebuffers[i] = new FramebufferManager();
  123. framebuffers[i].update(context, width, height, 1, pixelDatatype);
  124. }
  125. const lastTexture = framebuffers[length - 1].getColorTexture(0);
  126. autoexposure._previousLuminance.update(
  127. context,
  128. lastTexture.width,
  129. lastTexture.height,
  130. 1,
  131. pixelDatatype
  132. );
  133. autoexposure._framebuffers = framebuffers;
  134. }
  135. function destroyCommands(autoexposure) {
  136. const commands = autoexposure._commands;
  137. if (!defined(commands)) {
  138. return;
  139. }
  140. const length = commands.length;
  141. for (let i = 0; i < length; ++i) {
  142. commands[i].shaderProgram.destroy();
  143. }
  144. autoexposure._commands = undefined;
  145. }
  146. function createUniformMap(autoexposure, index) {
  147. let uniforms;
  148. if (index === 0) {
  149. uniforms = {
  150. colorTexture: function () {
  151. return autoexposure._colorTexture;
  152. },
  153. colorTextureDimensions: function () {
  154. return autoexposure._colorTexture.dimensions;
  155. },
  156. };
  157. } else {
  158. const texture = autoexposure._framebuffers[index - 1].getColorTexture(0);
  159. uniforms = {
  160. colorTexture: function () {
  161. return texture;
  162. },
  163. colorTextureDimensions: function () {
  164. return texture.dimensions;
  165. },
  166. };
  167. }
  168. uniforms.minMaxLuminance = function () {
  169. return autoexposure._minMaxLuminance;
  170. };
  171. uniforms.previousLuminance = function () {
  172. return autoexposure._previousLuminance.getColorTexture(0);
  173. };
  174. return uniforms;
  175. }
  176. function getShaderSource(index, length) {
  177. let source =
  178. "uniform sampler2D colorTexture; \n" +
  179. "in vec2 v_textureCoordinates; \n" +
  180. "float sampleTexture(vec2 offset) { \n";
  181. if (index === 0) {
  182. source +=
  183. " vec4 color = texture(colorTexture, v_textureCoordinates + offset); \n" +
  184. " return czm_luminance(color.rgb); \n";
  185. } else {
  186. source +=
  187. " return texture(colorTexture, v_textureCoordinates + offset).r; \n";
  188. }
  189. source += "}\n\n";
  190. source +=
  191. "uniform vec2 colorTextureDimensions; \n" +
  192. "uniform vec2 minMaxLuminance; \n" +
  193. "uniform sampler2D previousLuminance; \n" +
  194. "void main() { \n" +
  195. " float color = 0.0; \n" +
  196. " float xStep = 1.0 / colorTextureDimensions.x; \n" +
  197. " float yStep = 1.0 / colorTextureDimensions.y; \n" +
  198. " int count = 0; \n" +
  199. " for (int i = 0; i < 3; ++i) { \n" +
  200. " for (int j = 0; j < 3; ++j) { \n" +
  201. " vec2 offset; \n" +
  202. " offset.x = -xStep + float(i) * xStep; \n" +
  203. " offset.y = -yStep + float(j) * yStep; \n" +
  204. " if (offset.x < 0.0 || offset.x > 1.0 || offset.y < 0.0 || offset.y > 1.0) { \n" +
  205. " continue; \n" +
  206. " } \n" +
  207. " color += sampleTexture(offset); \n" +
  208. " ++count; \n" +
  209. " } \n" +
  210. " } \n" +
  211. " if (count > 0) { \n" +
  212. " color /= float(count); \n" +
  213. " } \n";
  214. if (index === length - 1) {
  215. source +=
  216. " float previous = texture(previousLuminance, vec2(0.5)).r; \n" +
  217. " color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n" +
  218. " color = previous + (color - previous) / (60.0 * 1.5); \n" +
  219. " color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n";
  220. }
  221. source += " out_FragColor = vec4(color); \n" + "} \n";
  222. return source;
  223. }
  224. function createCommands(autoexposure, context) {
  225. destroyCommands(autoexposure);
  226. const framebuffers = autoexposure._framebuffers;
  227. const length = framebuffers.length;
  228. const commands = new Array(length);
  229. for (let i = 0; i < length; ++i) {
  230. commands[i] = context.createViewportQuadCommand(
  231. getShaderSource(i, length),
  232. {
  233. framebuffer: framebuffers[i].framebuffer,
  234. uniformMap: createUniformMap(autoexposure, i),
  235. }
  236. );
  237. }
  238. autoexposure._commands = commands;
  239. }
  240. /**
  241. * A function that will be called before execute. Used to clear any textures attached to framebuffers.
  242. * @param {Context} context The context.
  243. * @private
  244. */
  245. AutoExposure.prototype.clear = function (context) {
  246. const framebuffers = this._framebuffers;
  247. if (!defined(framebuffers)) {
  248. return;
  249. }
  250. let clearCommand = this._clearCommand;
  251. if (!defined(clearCommand)) {
  252. clearCommand = this._clearCommand = new ClearCommand({
  253. color: new Color(0.0, 0.0, 0.0, 0.0),
  254. framebuffer: undefined,
  255. });
  256. }
  257. const length = framebuffers.length;
  258. for (let i = 0; i < length; ++i) {
  259. framebuffers[i].clear(context, clearCommand);
  260. }
  261. };
  262. /**
  263. * A function that will be called before execute. Used to create WebGL resources and load any textures.
  264. * @param {Context} context The context.
  265. * @private
  266. */
  267. AutoExposure.prototype.update = function (context) {
  268. const width = context.drawingBufferWidth;
  269. const height = context.drawingBufferHeight;
  270. if (width !== this._width || height !== this._height) {
  271. this._width = width;
  272. this._height = height;
  273. createFramebuffers(this, context);
  274. createCommands(this, context);
  275. if (!this._ready) {
  276. this._ready = true;
  277. }
  278. }
  279. this._minMaxLuminance.x = this.minimumLuminance;
  280. this._minMaxLuminance.y = this.maximumLuminance;
  281. const framebuffers = this._framebuffers;
  282. const temp = framebuffers[framebuffers.length - 1];
  283. framebuffers[framebuffers.length - 1] = this._previousLuminance;
  284. this._commands[
  285. this._commands.length - 1
  286. ].framebuffer = this._previousLuminance.framebuffer;
  287. this._previousLuminance = temp;
  288. };
  289. /**
  290. * Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage.
  291. * @param {Context} context The context.
  292. * @param {Texture} colorTexture The input color texture.
  293. * @private
  294. */
  295. AutoExposure.prototype.execute = function (context, colorTexture) {
  296. this._colorTexture = colorTexture;
  297. const commands = this._commands;
  298. if (!defined(commands)) {
  299. return;
  300. }
  301. const length = commands.length;
  302. for (let i = 0; i < length; ++i) {
  303. commands[i].execute(context);
  304. }
  305. };
  306. /**
  307. * Returns true if this object was destroyed; otherwise, false.
  308. * <p>
  309. * If this object was destroyed, it should not be used; calling any function other than
  310. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  311. * </p>
  312. *
  313. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  314. *
  315. * @see AutoExposure#destroy
  316. */
  317. AutoExposure.prototype.isDestroyed = function () {
  318. return false;
  319. };
  320. /**
  321. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  322. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  323. * <p>
  324. * Once an object is destroyed, it should not be used; calling any function other than
  325. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  326. * assign the return value (<code>undefined</code>) to the object as done in the example.
  327. * </p>
  328. *
  329. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  330. *
  331. * @see AutoExposure#isDestroyed
  332. */
  333. AutoExposure.prototype.destroy = function () {
  334. destroyFramebuffers(this);
  335. destroyCommands(this);
  336. return destroyObject(this);
  337. };
  338. export default AutoExposure;