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 right
- const 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;
|