AutoExposure.js 14 KB

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