import Cartesian3 from '../Core/Cartesian3.js'; import ComponentDatatype from '../Core/ComponentDatatype.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.js'; import destroyObject from '../Core/destroyObject.js'; import IndexDatatype from '../Core/IndexDatatype.js'; import loadKTX from '../Core/loadKTX.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 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'; import when from '../ThirdParty/when.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 = when.defer(); } defineProperties(OctahedralProjectedCubeMap.prototype, { /** * The url to the KTX 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} * @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". var v1 = new Cartesian3(1.0, 0.0, 0.0); var v2 = new Cartesian3(0.0, 0.0, 1.0); var v3 = new Cartesian3(-1.0, 0.0, 0.0); var v4 = new Cartesian3(0.0, 0.0, -1.0); var v5 = new Cartesian3(0.0, 1.0, 0.0); var v6 = new Cartesian3(0.0, -1.0, 0.0); // top left, left, top, center, right, top right, bottom, bottom left, bottom right var cubeMapCoordinates = [v5, v3, v2, v6, v1, v5, v4, v5, v5]; var length = cubeMapCoordinates.length; var flatCubeMapCoordinates = new Float32Array(length * 3); var offset = 0; for (var i = 0; i < length; ++i, offset += 3) { Cartesian3.pack(cubeMapCoordinates[i], flatCubeMapCoordinates, offset); } var 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 ]); var 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) { var positionBuffer = Buffer.createVertexBuffer({ context : context, typedArray : flatPositions, usage : BufferUsage.STATIC_DRAW }); var cubeMapCoordinatesBuffer = Buffer.createVertexBuffer({ context : context, typedArray : flatCubeMapCoordinates, usage : BufferUsage.STATIC_DRAW }); var indexBuffer = Buffer.createIndexBuffer({ context : context, typedArray : indices, usage : BufferUsage.STATIC_DRAW, indexDatatype : IndexDatatype.UNSIGNED_SHORT }); var 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(); var i; var length; var cubeMaps = map._cubeMaps; if (defined(cubeMaps)) { length = cubeMaps.length; for (i = 0; i < length; ++i) { cubeMaps[i].destroy(); } } var 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. *
* 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. *
* * @param {FrameState} frameState The frame state. * * @private */ OctahedralProjectedCubeMap.prototype.update = function(frameState) { var 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) { var 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; } } var cubeMapBuffers = this._cubeMapBuffers; if (!defined(cubeMapBuffers) && !this._loading) { var that = this; loadKTX(this._url).then(function(buffers) { that._cubeMapBuffers = buffers; that._loading = false; }); this._loading = true; } if (!defined(this._cubeMapBuffers)) { return; } this._va = createVertexArray(context); this._sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : OctahedralProjectionVS, fragmentShaderSource : OctahedralProjectionFS, attributeLocations : { position : 0, cubeMapCoordinates : 1 } }); // We only need up to 6 mip levels to avoid artifacts. var length = Math.min(cubeMapBuffers.length, 6); this._maximumMipmapLevel = length - 1; var cubeMaps = this._cubeMaps = new Array(length); var mipTextures = this._mipTextures = new Array(length); var originalSize = cubeMapBuffers[0].positiveX.width * 2.0; var uniformMap = { originalSize : function() { return originalSize; } }; var pixelDatatype = context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT; var pixelFormat = PixelFormat.RGBA; // First we project each cubemap onto a flat octahedron, and write that to a texture. for (var i = 0; i < length; ++i) { // Swap +Y/-Y faces since the octahedral projection expects this order. var positiveY = cubeMapBuffers[i].positiveY; cubeMapBuffers[i].positiveY = cubeMapBuffers[i].negativeY; cubeMapBuffers[i].negativeY = positiveY; var cubeMap = cubeMaps[i] = new CubeMap({ context : context, source : cubeMapBuffers[i] }); var size = cubeMaps[i].width * 2; var mipTexture = mipTextures[i] = new Texture({ context : context, width : size, height : size, pixelDatatype : pixelDatatype, pixelFormat : pixelFormat }); var 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); var 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. *
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
true
if this object was destroyed; otherwise, false
.
*
* @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.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*