AutoExposure.js 11 KB

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