| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 | import Cartesian2 from "../Core/Cartesian2.js";import Color from "../Core/Color.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import ClearCommand from "../Renderer/ClearCommand.js";import FramebufferManager from "../Renderer/FramebufferManager.js";import PixelDatatype from "../Renderer/PixelDatatype.js";/** * A post process stage that will get the luminance value at each pixel and * uses parallel reduction to compute the average luminance in a 1x1 texture. * This texture can be used as input for tone mapping. * * @constructor * @private */function AutoExposure() {  this._uniformMap = undefined;  this._command = undefined;  this._colorTexture = undefined;  this._depthTexture = undefined;  this._ready = false;  this._name = "czm_autoexposure";  this._logDepthChanged = undefined;  this._useLogDepth = undefined;  this._framebuffers = undefined;  this._previousLuminance = new FramebufferManager();  this._commands = undefined;  this._clearCommand = undefined;  this._minMaxLuminance = new Cartesian2();  /**   * Whether or not to execute this post-process stage when ready.   *   * @type {Boolean}   */  this.enabled = true;  this._enabled = true;  /**   * The minimum value used to clamp the luminance.   *   * @type {Number}   * @default 0.1   */  this.minimumLuminance = 0.1;  /**   * The maximum value used to clamp the luminance.   *   * @type {Number}   * @default 10.0   */  this.maximumLuminance = 10.0;}Object.defineProperties(AutoExposure.prototype, {  /**   * Determines if this post-process stage is ready to be executed. A stage is only executed when both <code>ready</code>   * and {@link AutoExposure#enabled} are <code>true</code>. A stage will not be ready while it is waiting on textures   * to load.   *   * @memberof AutoExposure.prototype   * @type {Boolean}   * @readonly   */  ready: {    get: function () {      return this._ready;    },  },  /**   * The unique name of this post-process stage for reference by other stages.   *   * @memberof AutoExposure.prototype   * @type {String}   * @readonly   */  name: {    get: function () {      return this._name;    },  },  /**   * A reference to the texture written to when executing this post process stage.   *   * @memberof AutoExposure.prototype   * @type {Texture}   * @readonly   * @private   */  outputTexture: {    get: function () {      const framebuffers = this._framebuffers;      if (!defined(framebuffers)) {        return undefined;      }      return framebuffers[framebuffers.length - 1].getColorTexture(0);    },  },});function destroyFramebuffers(autoexposure) {  const framebuffers = autoexposure._framebuffers;  if (!defined(framebuffers)) {    return;  }  const length = framebuffers.length;  for (let i = 0; i < length; ++i) {    framebuffers[i].destroy();  }  autoexposure._framebuffers = undefined;  autoexposure._previousLuminance.destroy();  autoexposure._previousLuminance = undefined;}function createFramebuffers(autoexposure, context) {  destroyFramebuffers(autoexposure);  let width = autoexposure._width;  let height = autoexposure._height;  const pixelDatatype = context.halfFloatingPointTexture    ? PixelDatatype.HALF_FLOAT    : PixelDatatype.FLOAT;  const length = Math.ceil(Math.log(Math.max(width, height)) / Math.log(3.0));  const framebuffers = new Array(length);  for (let i = 0; i < length; ++i) {    width = Math.max(Math.ceil(width / 3.0), 1.0);    height = Math.max(Math.ceil(height / 3.0), 1.0);    framebuffers[i] = new FramebufferManager();    framebuffers[i].update(context, width, height, 1, pixelDatatype);  }  const lastTexture = framebuffers[length - 1].getColorTexture(0);  autoexposure._previousLuminance.update(    context,    lastTexture.width,    lastTexture.height,    1,    pixelDatatype  );  autoexposure._framebuffers = framebuffers;}function destroyCommands(autoexposure) {  const commands = autoexposure._commands;  if (!defined(commands)) {    return;  }  const length = commands.length;  for (let i = 0; i < length; ++i) {    commands[i].shaderProgram.destroy();  }  autoexposure._commands = undefined;}function createUniformMap(autoexposure, index) {  let uniforms;  if (index === 0) {    uniforms = {      colorTexture: function () {        return autoexposure._colorTexture;      },      colorTextureDimensions: function () {        return autoexposure._colorTexture.dimensions;      },    };  } else {    const texture = autoexposure._framebuffers[index - 1].getColorTexture(0);    uniforms = {      colorTexture: function () {        return texture;      },      colorTextureDimensions: function () {        return texture.dimensions;      },    };  }  uniforms.minMaxLuminance = function () {    return autoexposure._minMaxLuminance;  };  uniforms.previousLuminance = function () {    return autoexposure._previousLuminance.getColorTexture(0);  };  return uniforms;}function getShaderSource(index, length) {  let source =    "uniform sampler2D colorTexture; \n" +    "varying vec2 v_textureCoordinates; \n" +    "float sampleTexture(vec2 offset) { \n";  if (index === 0) {    source +=      "    vec4 color = texture2D(colorTexture, v_textureCoordinates + offset); \n" +      "    return czm_luminance(color.rgb); \n";  } else {    source +=      "    return texture2D(colorTexture, v_textureCoordinates + offset).r; \n";  }  source += "}\n\n";  source +=    "uniform vec2 colorTextureDimensions; \n" +    "uniform vec2 minMaxLuminance; \n" +    "uniform sampler2D previousLuminance; \n" +    "void main() { \n" +    "    float color = 0.0; \n" +    "    float xStep = 1.0 / colorTextureDimensions.x; \n" +    "    float yStep = 1.0 / colorTextureDimensions.y; \n" +    "    int count = 0; \n" +    "    for (int i = 0; i < 3; ++i) { \n" +    "        for (int j = 0; j < 3; ++j) { \n" +    "            vec2 offset; \n" +    "            offset.x = -xStep + float(i) * xStep; \n" +    "            offset.y = -yStep + float(j) * yStep; \n" +    "            if (offset.x < 0.0 || offset.x > 1.0 || offset.y < 0.0 || offset.y > 1.0) { \n" +    "                continue; \n" +    "            } \n" +    "            color += sampleTexture(offset); \n" +    "            ++count; \n" +    "        } \n" +    "    } \n" +    "    if (count > 0) { \n" +    "        color /= float(count); \n" +    "    } \n";  if (index === length - 1) {    source +=      "    float previous = texture2D(previousLuminance, vec2(0.5)).r; \n" +      "    color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n" +      "    color = previous + (color - previous) / (60.0 * 1.5); \n" +      "    color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n";  }  source += "    gl_FragColor = vec4(color); \n" + "} \n";  return source;}function createCommands(autoexposure, context) {  destroyCommands(autoexposure);  const framebuffers = autoexposure._framebuffers;  const length = framebuffers.length;  const commands = new Array(length);  for (let i = 0; i < length; ++i) {    commands[i] = context.createViewportQuadCommand(      getShaderSource(i, length),      {        framebuffer: framebuffers[i].framebuffer,        uniformMap: createUniformMap(autoexposure, i),      }    );  }  autoexposure._commands = commands;}/** * A function that will be called before execute. Used to clear any textures attached to framebuffers. * @param {Context} context The context. * @private */AutoExposure.prototype.clear = function (context) {  const framebuffers = this._framebuffers;  if (!defined(framebuffers)) {    return;  }  let clearCommand = this._clearCommand;  if (!defined(clearCommand)) {    clearCommand = this._clearCommand = new ClearCommand({      color: new Color(0.0, 0.0, 0.0, 0.0),      framebuffer: undefined,    });  }  const length = framebuffers.length;  for (let i = 0; i < length; ++i) {    framebuffers[i].clear(context, clearCommand);  }};/** * A function that will be called before execute. Used to create WebGL resources and load any textures. * @param {Context} context The context. * @private */AutoExposure.prototype.update = function (context) {  const width = context.drawingBufferWidth;  const height = context.drawingBufferHeight;  if (width !== this._width || height !== this._height) {    this._width = width;    this._height = height;    createFramebuffers(this, context);    createCommands(this, context);    if (!this._ready) {      this._ready = true;    }  }  this._minMaxLuminance.x = this.minimumLuminance;  this._minMaxLuminance.y = this.maximumLuminance;  const framebuffers = this._framebuffers;  const temp = framebuffers[framebuffers.length - 1];  framebuffers[framebuffers.length - 1] = this._previousLuminance;  this._commands[    this._commands.length - 1  ].framebuffer = this._previousLuminance.framebuffer;  this._previousLuminance = temp;};/** * Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage. * @param {Context} context The context. * @param {Texture} colorTexture The input color texture. * @private */AutoExposure.prototype.execute = function (context, colorTexture) {  this._colorTexture = colorTexture;  const commands = this._commands;  if (!defined(commands)) {    return;  }  const length = commands.length;  for (let i = 0; i < length; ++i) {    commands[i].execute(context);  }};/** * Returns true if this object was destroyed; otherwise, false. * <p> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * </p> * * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>. * * @see AutoExposure#destroy */AutoExposure.prototype.isDestroyed = function () {  return false;};/** * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. * <p> * Once an object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * </p> * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @see AutoExposure#isDestroyed */AutoExposure.prototype.destroy = function () {  destroyFramebuffers(this);  destroyCommands(this);  return destroyObject(this);};export default AutoExposure;
 |