@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
241 lines (217 loc) • 7.25 kB
JavaScript
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import Event from "../Core/Event.js";
import loadKTX2 from "../Core/loadKTX2.js";
import PixelFormat from "../Core/PixelFormat.js";
import CubeMap from "../Renderer/CubeMap.js";
import PixelDatatype from "../Renderer/PixelDatatype.js";
import Sampler from "../Renderer/Sampler.js";
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
/**
* Manages a cube map for use as a specular environment map.
*
* @alias SpecularEnvironmentCubeMap
* @constructor
*
* @param {string} url The url to the KTX2 file containing the specular environment map and convoluted mipmaps.
* @private
*/
function SpecularEnvironmentCubeMap(url) {
this._url = url;
this._cubeMapBuffers = undefined;
this._texture = undefined;
this._maximumMipmapLevel = undefined;
this._loading = false;
this._ready = false;
this._errorEvent = new Event();
}
Object.defineProperties(SpecularEnvironmentCubeMap.prototype, {
/**
* The url to the KTX2 file containing the specular environment map and convoluted mipmaps.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {string}
* @readonly
*/
url: {
get: function () {
return this._url;
},
},
/**
* Gets an event that is raised when encountering an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {Event}
* @readonly
*/
errorEvent: {
get: function () {
return this._errorEvent;
},
},
/**
* A texture containing all the packed convolutions.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {Texture}
* @readonly
*/
texture: {
get: function () {
return this._texture;
},
},
/**
* The maximum number of mip levels with valid environment map data.
* This may differ from the number of mips in the WebGL cubemap.
* The data loaded at <code>maximumMipmapLevel</code> is suitable for
* PBR rendering of a material with maximum roughness (1.0).
* @memberOf SpecularEnvironmentCubeMap.prototype
* @type {number}
* @readonly
*/
maximumMipmapLevel: {
get: function () {
return this._maximumMipmapLevel;
},
},
/**
* Determines if the cube map is complete and ready to use.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {boolean}
* @readonly
*/
ready: {
get: function () {
return this._ready;
},
},
});
SpecularEnvironmentCubeMap.isSupported = function (context) {
const supportsFloatBuffersAndTextures =
(context.colorBufferHalfFloat && context.halfFloatingPointTexture) ||
(context.floatingPointTexture && context.colorBufferFloat);
return supportsFloatBuffersAndTextures && context.supportsTextureLod;
};
function cleanupResources(map) {
map._cubeMapBuffers = undefined;
}
/**
* Loads the environment map image and constructs the cube map for specular radiance calculations.
* <p>
* Once the image is loaded, the next call cleans up unused resources. Every call after that is a no-op.
* </p>
* @param {FrameState} frameState The frame state.
*
* @private
*/
SpecularEnvironmentCubeMap.prototype.update = function (frameState) {
const { context } = frameState;
if (!SpecularEnvironmentCubeMap.isSupported(context)) {
return;
}
if (defined(this._texture)) {
cleanupResources(this);
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;
}
}
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 (error) {
if (that.isDestroyed()) {
return;
}
that._errorEvent.raiseEvent(error);
});
this._loading = true;
}
if (!defined(this._cubeMapBuffers)) {
return;
}
// Datatype is defined if it is a normalized type (i.e. ..._UNORM, ..._SFLOAT)
let { pixelDatatype } = cubeMapBuffers[0].positiveX;
if (!defined(pixelDatatype)) {
pixelDatatype = context.halfFloatingPointTexture
? PixelDatatype.HALF_FLOAT
: PixelDatatype.FLOAT;
}
const pixelFormat = PixelFormat.RGBA;
const mipLevels = cubeMapBuffers.length;
this._maximumMipmapLevel = mipLevels - 1;
const faceSize = cubeMapBuffers[0].positiveX.width;
const expectedMipLevels = Math.log2(faceSize) + 1;
if (mipLevels !== expectedMipLevels) {
// We assume the last supplied mip level was computed as the specular radiance
// for roughness 1.0.
// Fill the remaining levels with null values, to avoid WebGL errors.
const dummyMipLevel = {};
Object.values(CubeMap.FaceName).forEach((faceName) => {
dummyMipLevel[faceName] = undefined;
});
for (let mipLevel = mipLevels; mipLevel < expectedMipLevels; mipLevel++) {
cubeMapBuffers.push(dummyMipLevel);
}
}
const sampler = new Sampler({
minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
});
const cubeMap = new CubeMap({
context: context,
source: cubeMapBuffers[0],
flipY: false,
pixelDatatype: pixelDatatype,
pixelFormat: pixelFormat,
sampler: sampler,
});
cubeMap.loadMipmaps(cubeMapBuffers.slice(1));
this._texture = cubeMap;
this._texture.maximumMipmapLevel = this._maximumMipmapLevel;
context.textureCache.addTexture(this._url, this._texture);
this._ready = true;
};
/**
* 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 SpecularEnvironmentCubeMap#destroy
*/
SpecularEnvironmentCubeMap.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 SpecularEnvironmentCubeMap#isDestroyed
*/
SpecularEnvironmentCubeMap.prototype.destroy = function () {
cleanupResources(this);
this._texture = this._texture && this._texture.destroy();
return destroyObject(this);
};
export default SpecularEnvironmentCubeMap;