| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 | import Cartesian3 from "../Core/Cartesian3.js";import ComponentDatatype from "../Core/ComponentDatatype.js";import defer from "../Core/defer.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import IndexDatatype from "../Core/IndexDatatype.js";import loadKTX2 from "../Core/loadKTX2.js";import PixelFormat from "../Core/PixelFormat.js";import Buffer from "../Renderer/Buffer.js";import BufferUsage from "../Renderer/BufferUsage.js";import ComputeCommand from "../Renderer/ComputeCommand.js";import CubeMap from "../Renderer/CubeMap.js";import PixelDatatype from "../Renderer/PixelDatatype.js";import ShaderProgram from "../Renderer/ShaderProgram.js";import ShaderSource from "../Renderer/ShaderSource.js";import Texture from "../Renderer/Texture.js";import VertexArray from "../Renderer/VertexArray.js";import OctahedralProjectionAtlasFS from "../Shaders/OctahedralProjectionAtlasFS.js";import OctahedralProjectionFS from "../Shaders/OctahedralProjectionFS.js";import OctahedralProjectionVS from "../Shaders/OctahedralProjectionVS.js";/** * Packs all mip levels of a cube map into a 2D texture atlas. * * Octahedral projection is a way of putting the cube maps onto a 2D texture * with minimal distortion and easy look up. * See Chapter 16 of WebGL Insights "HDR Image-Based Lighting on the Web" by Jeff Russell * and "Octahedron Environment Maps" for reference. * * @private */function OctahedralProjectedCubeMap(url) {  this._url = url;  this._cubeMapBuffers = undefined;  this._cubeMaps = undefined;  this._texture = undefined;  this._mipTextures = undefined;  this._va = undefined;  this._sp = undefined;  this._maximumMipmapLevel = undefined;  this._loading = false;  this._ready = false;  this._readyPromise = defer();}Object.defineProperties(OctahedralProjectedCubeMap.prototype, {  /**   * The url to the KTX2 file containing the specular environment map and convoluted mipmaps.   * @memberof OctahedralProjectedCubeMap.prototype   * @type {String}   * @readonly   */  url: {    get: function () {      return this._url;    },  },  /**   * A texture containing all the packed convolutions.   * @memberof OctahedralProjectedCubeMap.prototype   * @type {Texture}   * @readonly   */  texture: {    get: function () {      return this._texture;    },  },  /**   * The maximum number of mip levels.   * @memberOf OctahedralProjectedCubeMap.prototype   * @type {Number}   * @readonly   */  maximumMipmapLevel: {    get: function () {      return this._maximumMipmapLevel;    },  },  /**   * Determines if the texture atlas is complete and ready to use.   * @memberof OctahedralProjectedCubeMap.prototype   * @type {Boolean}   * @readonly   */  ready: {    get: function () {      return this._ready;    },  },  /**   * Gets a promise that resolves when the texture atlas is ready to use.   * @memberof OctahedralProjectedCubeMap.prototype   * @type {Promise<void>}   * @readonly   */  readyPromise: {    get: function () {      return this._readyPromise.promise;    },  },});OctahedralProjectedCubeMap.isSupported = function (context) {  return (    (context.colorBufferHalfFloat && context.halfFloatingPointTexture) ||    (context.floatingPointTexture && context.colorBufferFloat)  );};// These vertices are based on figure 1 from "Octahedron Environment Maps".const v1 = new Cartesian3(1.0, 0.0, 0.0);const v2 = new Cartesian3(0.0, 0.0, 1.0);const v3 = new Cartesian3(-1.0, 0.0, 0.0);const v4 = new Cartesian3(0.0, 0.0, -1.0);const v5 = new Cartesian3(0.0, 1.0, 0.0);const v6 = new Cartesian3(0.0, -1.0, 0.0);// top left, left, top, center, right, top right, bottom, bottom left, bottom rightconst cubeMapCoordinates = [v5, v3, v2, v6, v1, v5, v4, v5, v5];const length = cubeMapCoordinates.length;const flatCubeMapCoordinates = new Float32Array(length * 3);let offset = 0;for (let i = 0; i < length; ++i, offset += 3) {  Cartesian3.pack(cubeMapCoordinates[i], flatCubeMapCoordinates, offset);}const flatPositions = new Float32Array([  -1.0,  1.0, // top left  -1.0,  0.0, // left  0.0,  1.0, // top  0.0,  0.0, // center  1.0,  0.0, // right  1.0,  1.0, // top right  0.0,  -1.0, // bottom  -1.0,  -1.0, // bottom left  1.0,  -1.0, // bottom right]);const indices = new Uint16Array([  0,  1,  2, // top left, left, top,  2,  3,  1, // top, center, left,  7,  6,  1, // bottom left, bottom, left,  3,  6,  1, // center, bottom, left,  2,  5,  4, // top, top right, right,  3,  4,  2, // center, right, top,  4,  8,  6, // right, bottom right, bottom,  3,  4,  6, //center, right, bottom]);function createVertexArray(context) {  const positionBuffer = Buffer.createVertexBuffer({    context: context,    typedArray: flatPositions,    usage: BufferUsage.STATIC_DRAW,  });  const cubeMapCoordinatesBuffer = Buffer.createVertexBuffer({    context: context,    typedArray: flatCubeMapCoordinates,    usage: BufferUsage.STATIC_DRAW,  });  const indexBuffer = Buffer.createIndexBuffer({    context: context,    typedArray: indices,    usage: BufferUsage.STATIC_DRAW,    indexDatatype: IndexDatatype.UNSIGNED_SHORT,  });  const attributes = [    {      index: 0,      vertexBuffer: positionBuffer,      componentsPerAttribute: 2,      componentDatatype: ComponentDatatype.FLOAT,    },    {      index: 1,      vertexBuffer: cubeMapCoordinatesBuffer,      componentsPerAttribute: 3,      componentDatatype: ComponentDatatype.FLOAT,    },  ];  return new VertexArray({    context: context,    attributes: attributes,    indexBuffer: indexBuffer,  });}function createUniformTexture(texture) {  return function () {    return texture;  };}function cleanupResources(map) {  map._va = map._va && map._va.destroy();  map._sp = map._sp && map._sp.destroy();  let i;  let length;  const cubeMaps = map._cubeMaps;  if (defined(cubeMaps)) {    length = cubeMaps.length;    for (i = 0; i < length; ++i) {      cubeMaps[i].destroy();    }  }  const mipTextures = map._mipTextures;  if (defined(mipTextures)) {    length = mipTextures.length;    for (i = 0; i < length; ++i) {      mipTextures[i].destroy();    }  }  map._va = undefined;  map._sp = undefined;  map._cubeMaps = undefined;  map._cubeMapBuffers = undefined;  map._mipTextures = undefined;}/** * Creates compute commands to generate octahedral projections of each cube map * and then renders them to an atlas. * <p> * Only needs to be called twice. The first call queues the compute commands to generate the atlas. * The second call cleans up unused resources. Every call afterwards is a no-op. * </p> * * @param {FrameState} frameState The frame state. * * @private */OctahedralProjectedCubeMap.prototype.update = function (frameState) {  const context = frameState.context;  if (!OctahedralProjectedCubeMap.isSupported(context)) {    return;  }  if (defined(this._texture) && defined(this._va)) {    cleanupResources(this);  }  if (defined(this._texture)) {    return;  }  if (!defined(this._texture) && !this._loading) {    const cachedTexture = context.textureCache.getTexture(this._url);    if (defined(cachedTexture)) {      cleanupResources(this);      this._texture = cachedTexture;      this._maximumMipmapLevel = this._texture.maximumMipmapLevel;      this._ready = true;      this._readyPromise.resolve();      return;    }  }  const cubeMapBuffers = this._cubeMapBuffers;  if (!defined(cubeMapBuffers) && !this._loading) {    const that = this;    loadKTX2(this._url)      .then(function (buffers) {        that._cubeMapBuffers = buffers;        that._loading = false;      })      .catch(function (e) {        that._readyPromise.reject(e);      });    this._loading = true;  }  if (!defined(this._cubeMapBuffers)) {    return;  }  const defines = [];  // Datatype is defined if it is a normalized type (i.e. ..._UNORM, ..._SFLOAT)  let pixelDatatype = cubeMapBuffers[0].positiveX.pixelDatatype;  if (!defined(pixelDatatype)) {    pixelDatatype = context.halfFloatingPointTexture      ? PixelDatatype.HALF_FLOAT      : PixelDatatype.FLOAT;  } else {    defines.push("RGBA_NORMALIZED");  }  const pixelFormat = PixelFormat.RGBA;  const fs = new ShaderSource({    defines: defines,    sources: [OctahedralProjectionFS],  });  this._va = createVertexArray(context);  this._sp = ShaderProgram.fromCache({    context: context,    vertexShaderSource: OctahedralProjectionVS,    fragmentShaderSource: fs,    attributeLocations: {      position: 0,      cubeMapCoordinates: 1,    },  });  // We only need up to 6 mip levels to avoid artifacts.  const length = Math.min(cubeMapBuffers.length, 6);  this._maximumMipmapLevel = length - 1;  const cubeMaps = (this._cubeMaps = new Array(length));  const mipTextures = (this._mipTextures = new Array(length));  const originalSize = cubeMapBuffers[0].positiveX.width * 2.0;  const uniformMap = {    originalSize: function () {      return originalSize;    },  };  // First we project each cubemap onto a flat octahedron, and write that to a texture.  for (let i = 0; i < length; ++i) {    // Swap +Y/-Y faces since the octahedral projection expects this order.    const positiveY = cubeMapBuffers[i].positiveY;    cubeMapBuffers[i].positiveY = cubeMapBuffers[i].negativeY;    cubeMapBuffers[i].negativeY = positiveY;    const cubeMap = (cubeMaps[i] = new CubeMap({      context: context,      source: cubeMapBuffers[i],      pixelDatatype: pixelDatatype,    }));    const size = cubeMaps[i].width * 2;    const mipTexture = (mipTextures[i] = new Texture({      context: context,      width: size,      height: size,      pixelDatatype: pixelDatatype,      pixelFormat: pixelFormat,    }));    const command = new ComputeCommand({      vertexArray: this._va,      shaderProgram: this._sp,      uniformMap: {        cubeMap: createUniformTexture(cubeMap),      },      outputTexture: mipTexture,      persists: true,      owner: this,    });    frameState.commandList.push(command);    uniformMap[`texture${i}`] = createUniformTexture(mipTexture);  }  this._texture = new Texture({    context: context,    width: originalSize * 1.5 + 2.0, // We add a 1 pixel border to avoid linear sampling artifacts.    height: originalSize,    pixelDatatype: pixelDatatype,    pixelFormat: pixelFormat,  });  this._texture.maximumMipmapLevel = this._maximumMipmapLevel;  context.textureCache.addTexture(this._url, this._texture);  const atlasCommand = new ComputeCommand({    fragmentShaderSource: OctahedralProjectionAtlasFS,    uniformMap: uniformMap,    outputTexture: this._texture,    persists: false,    owner: this,  });  frameState.commandList.push(atlasCommand);  this._ready = true;  this._readyPromise.resolve();};/** * 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 OctahedralProjectedCubeMap#destroy */OctahedralProjectedCubeMap.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 OctahedralProjectedCubeMap#isDestroyed */OctahedralProjectedCubeMap.prototype.destroy = function () {  cleanupResources(this);  this._texture = this._texture && this._texture.destroy();  return destroyObject(this);};export default OctahedralProjectedCubeMap;
 |