import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import Color from "../Core/Color.js";
import Frozen from "../Core/Frozen.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import JulianDate from "../Core/JulianDate.js";
import Matrix4 from "../Core/Matrix4.js";
import PixelFormat from "../Core/PixelFormat.js";
import SceneMode from "./SceneMode.js";
import Transforms from "../Core/Transforms.js";
import ComputeCommand from "../Renderer/ComputeCommand.js";
import ContextLimits from "../Renderer/ContextLimits.js";
import CubeMap from "../Renderer/CubeMap.js";
import Framebuffer from "../Renderer/Framebuffer.js";
import Texture from "../Renderer/Texture.js";
import PixelDatatype from "../Renderer/PixelDatatype.js";
import Sampler from "../Renderer/Sampler.js";
import ShaderProgram from "../Renderer/ShaderProgram.js";
import ShaderSource from "../Renderer/ShaderSource.js";
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
import Atmosphere from "./Atmosphere.js";
import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js";
import AtmosphereCommon from "../Shaders/AtmosphereCommon.js";
import ComputeIrradianceFS from "../Shaders/ComputeIrradianceFS.js";
import ComputeRadianceMapFS from "../Shaders/ComputeRadianceMapFS.js";
import ConvolveSpecularMapFS from "../Shaders/ConvolveSpecularMapFS.js";
import ConvolveSpecularMapVS from "../Shaders/ConvolveSpecularMapVS.js";
/**
* @typedef {object} DynamicEnvironmentMapManager.ConstructorOptions
* Options for the DynamicEnvironmentMapManager constructor
* @property {boolean} [enabled=true] If true, the environment map and related properties will continue to update.
* @property {number} [mipmapLevels=7] The maximum desired number of mipmap levels to generate for specular maps. More mipmap levels will produce a higher resolution specular reflection. The actual number of mipmaps used will be bounded by the cubemap texture size supported on the client machine. The number of mipmaps must be at least one for the environment map to be generated.
* @property {number} [maximumSecondsDifference=3600] The maximum amount of elapsed seconds before a new environment map is created.
* @property {number} [maximumPositionEpsilon=1000] The maximum difference in position before a new environment map is created, in meters. Small differences in position will not visibly affect results.
* @property {number} [atmosphereScatteringIntensity=2.0] The intensity of the scattered light emitted from the atmosphere. This should be adjusted relative to the value of {@link Scene.light} intensity.
* @property {number} [gamma=1.0] The gamma correction to apply to the range of light emitted from the environment. 1.0 uses the unmodified emitted light color.
* @property {number} [brightness=1.0] The brightness of light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 makes the light darker while greater than 1.0 makes it brighter.
* @property {number} [saturation=1.0] The saturation of the light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 reduces the saturation while greater than 1.0 increases it.
* @property {Color} [groundColor=DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR] Solid color used to represent the ground.
* @property {number} [groundAlbedo=0.31] The percentage of light reflected from the ground. The average earth albedo is 0.31.
*/
/**
* Generates an environment map at the given position based on scene's current lighting conditions. From this, it produces multiple levels of specular maps and spherical harmonic coefficients than can be used with {@link ImageBasedLighting} for models or tilesets.
* @alias DynamicEnvironmentMapManager
* @constructor
* @param {DynamicEnvironmentMapManager.ConstructorOptions} [options] An object describing initialization options.
*
* @example
* // Enable time-of-day environment mapping in a scene
* scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SUNLIGHT;
*
* // Decrease the directional lighting contribution
* scene.light.intensity = 0.5
*
* // Increase the intensity of of the environment map lighting contribution
* const environmentMapManager = tileset.environmentMapManager;
* environmentMapManager.atmosphereScatteringIntensity = 3.0;
*
* @example
* // Change the ground color used for a model's environment map to a forest green
* const environmentMapManager = model.environmentMapManager;
* environmentMapManager.groundColor = Cesium.Color.fromCssColorString("#203b34");
*/
function DynamicEnvironmentMapManager(options) {
this._position = undefined;
this._radianceMapDirty = false;
this._radianceCommandsDirty = false;
this._convolutionsCommandsDirty = false;
this._irradianceCommandDirty = false;
this._irradianceTextureDirty = false;
this._sphericalHarmonicCoefficientsDirty = false;
this._shouldRegenerateShaders = false;
this._shouldReset = false;
options = options ?? Frozen.EMPTY_OBJECT;
const mipmapLevels = Math.max(
Math.floor(
Math.min(
options.mipmapLevels ?? 7,
Math.log2(ContextLimits.maximumCubeMapSize),
),
),
0,
);
this._mipmapLevels = mipmapLevels;
const arrayLength = Math.max(mipmapLevels - 1, 0) * 6;
this._radianceMapComputeCommands = new Array(6);
this._convolutionComputeCommands = new Array(arrayLength);
this._irradianceComputeCommand = undefined;
this._radianceMapFS = undefined;
this._irradianceMapFS = undefined;
this._convolveSP = undefined;
this._va = undefined;
this._radianceMapTextures = new Array(6);
this._specularMapTextures = new Array(arrayLength);
this._radianceCubeMap = undefined;
this._irradianceMapTexture = undefined;
this._sphericalHarmonicCoefficients =
DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS.slice();
this._lastTime = new JulianDate();
const width = Math.max(Math.pow(2, mipmapLevels - 1), 1);
this._textureDimensions = new Cartesian2(width, width);
this._radiiAndDynamicAtmosphereColor = new Cartesian3();
this._sceneEnvironmentMap = undefined;
this._backgroundColor = undefined;
// If this DynamicEnvironmentMapManager has an owner, only its owner should update or destroy it.
// This is because in a Cesium3DTileset multiple models may reference one tileset's DynamicEnvironmentMapManager.
this._owner = undefined;
/**
* If true, the environment map and related properties will continue to update.
* @type {boolean}
* @default true
*/
this.enabled = options.enabled ?? true;
/**
* Disables updates. For internal use.
* @private
* @default true
*/
this.shouldUpdate = true;
/**
* The maximum amount of elapsed seconds before a new environment map is created.
* @type {number}
* @default 3600
*/
this.maximumSecondsDifference = options.maximumSecondsDifference ?? 60 * 60;
/**
* The maximum difference in position before a new environment map is created, in meters. Small differences in position will not visibly affect results.
* @type {number}
* @default 1000
*/
this.maximumPositionEpsilon = options.maximumPositionEpsilon ?? 1000.0;
/**
* The intensity of the scattered light emitted from the atmosphere. This should be adjusted relative to the value of {@link Scene.light} intensity.
* @type {number}
* @default 2.0
* @see DirectionalLight.intensity
* @see SunLight.intensity
*/
this.atmosphereScatteringIntensity =
options.atmosphereScatteringIntensity ?? 2.0;
/**
* The gamma correction to apply to the range of light emitted from the environment. 1.0 uses the unmodified incoming light color.
* @type {number}
* @default 1.0
*/
this.gamma = options.gamma ?? 1.0;
/**
* The brightness of light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0
* makes the light darker while greater than 1.0 makes it brighter.
* @type {number}
* @default 1.0
*/
this.brightness = options.brightness ?? 1.0;
/**
* The saturation of the light emitted from the environment. 1.0 uses the unmodified emitted environment color. Less than 1.0 reduces the
* saturation while greater than 1.0 increases it.
* @type {number}
* @default 1.0
*/
this.saturation = options.saturation ?? 1.0;
/**
* Solid color used to represent the ground.
* @type {Color}
* @default DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR
*/
this.groundColor =
options.groundColor ??
DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR;
/**
* The percentage of light reflected from the ground. The average earth albedo is 0.31.
* @type {number}
* @default 0.31
*/
this.groundAlbedo = options.groundAlbedo ?? 0.31;
}
Object.defineProperties(DynamicEnvironmentMapManager.prototype, {
/**
* A reference to the DynamicEnvironmentMapManager's owner, if any.
* @memberof DynamicEnvironmentMapManager.prototype
* @type {object|undefined}
* @readonly
* @private
*/
owner: {
get: function () {
return this._owner;
},
},
/**
* True if model shaders need to be regenerated to account for updates.
* @memberof DynamicEnvironmentMapManager.prototype
* @type {boolean}
* @readonly
* @private
*/
shouldRegenerateShaders: {
get: function () {
return this._shouldRegenerateShaders;
},
},
/**
* The position around which the environment map is generated.
* @memberof DynamicEnvironmentMapManager.prototype
* @type {Cartesian3|undefined}
*/
position: {
get: function () {
return this._position;
},
set: function (value) {
if (
Cartesian3.equalsEpsilon(
value,
this._position,
0.0,
this.maximumPositionEpsilon,
)
) {
return;
}
this._position = Cartesian3.clone(value, this._position);
this._shouldReset = true;
},
},
/**
* The computed radiance map, or undefined if it has not yet been created.
* @memberof DynamicEnvironmentMapManager.prototype
* @type {CubeMap|undefined}
* @readonly
* @private
*/
radianceCubeMap: {
get: function () {
return this._radianceCubeMap;
},
},
/**
* The maximum number of mip levels available in the radiance cubemap.
* @memberof DynamicEnvironmentMapManager.prototype
* @type {number}
* @readonly
* @private
*/
maximumMipmapLevel: {
get: function () {
return this._mipmapLevels;
},
},
/**
* The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
*
* There are nine Cartesian3 coefficients.
* The order of the coefficients is: L0,0, L1,-1, L1,0, L1,1, L2,-2, L2,-1, L2,0, L2,1, L2,2
*
* Do not call this function directly. *
* @private */ DynamicEnvironmentMapManager.prototype.update = function (frameState) { const mode = frameState.mode; const isSupported = // @ts-expect-error A FrameState type works here because the function only references the context parameter. DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState) && this._mipmapLevels >= 1; if ( !isSupported || !this.enabled || !this.shouldUpdate || !defined(this._position) || mode === SceneMode.MORPHING ) { this._shouldRegenerateShaders = false; return; } DynamicEnvironmentMapManager._updateCommandQueue(frameState); const dynamicLighting = frameState.atmosphere.dynamicLighting; const regenerateEnvironmentMap = atmosphereNeedsUpdate(this, frameState) || (dynamicLighting === DynamicAtmosphereLightingType.SUNLIGHT && !JulianDate.equalsEpsilon( frameState.time, this._lastTime, this.maximumSecondsDifference, )); if (this._shouldReset || regenerateEnvironmentMap) { this.reset(); this._shouldReset = false; this._lastTime = JulianDate.clone(frameState.time, this._lastTime); return; } if (this._radianceMapDirty) { updateRadianceMap(this, frameState); this._radianceMapDirty = false; } if (this._convolutionsCommandsDirty) { updateSpecularMaps(this, frameState); this._convolutionsCommandsDirty = false; } if (this._irradianceCommandDirty) { updateIrradianceResources(this, frameState); this._irradianceCommandDirty = false; } if (this._irradianceTextureDirty) { this._shouldRegenerateShaders = false; return; } if (this._sphericalHarmonicCoefficientsDirty) { updateSphericalHarmonicCoefficients(this, frameState); this._sphericalHarmonicCoefficientsDirty = false; return; } this._shouldRegenerateShaders = false; }; /** * Returns true if this object was destroyed; otherwise, false. *isDestroyed will result in a {@link DeveloperError} exception.
* @returns {boolean} true if this object was destroyed; otherwise, false.
* @see DynamicEnvironmentMapManager#destroy
*/
DynamicEnvironmentMapManager.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.
* isDestroyed will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined) to the object as done in the example.
* @throws {DeveloperError} This object was destroyed, i.e., destroy() was called.
* @example
* mapManager = mapManager && mapManager.destroy();
* @see DynamicEnvironmentMapManager#isDestroyed
*/
DynamicEnvironmentMapManager.prototype.destroy = function () {
// Cancel in-progress commands
let length = this._radianceMapComputeCommands.length;
for (let i = 0; i < length; ++i) {
this._radianceMapComputeCommands[i] = undefined;
}
length = this._convolutionComputeCommands.length;
for (let i = 0; i < length; ++i) {
this._convolutionComputeCommands[i] = undefined;
}
this._irradianceMapComputeCommand = undefined;
// Destroy all textures
length = this._radianceMapTextures.length;
for (let i = 0; i < length; ++i) {
this._radianceMapTextures[i] =
this._radianceMapTextures[i] &&
!this._radianceMapTextures[i].isDestroyed() &&
this._radianceMapTextures[i].destroy();
}
length = this._specularMapTextures.length;
for (let i = 0; i < length; ++i) {
this._specularMapTextures[i] =
this._specularMapTextures[i] &&
!this._specularMapTextures[i].isDestroyed() &&
this._specularMapTextures[i].destroy();
}
this._radianceCubeMap =
this._radianceCubeMap && this._radianceCubeMap.destroy();
this._irradianceMapTexture =
this._irradianceMapTexture &&
!this._irradianceMapTexture.isDestroyed() &&
this._irradianceMapTexture.destroy();
if (defined(this._va)) {
this._va.destroy();
}
if (defined(this._convolveSP)) {
this._convolveSP.destroy();
}
return destroyObject(this);
};
/**
* Returns true if dynamic updates are supported in the current WebGL rendering context.
* Dynamic updates requires the EXT_color_buffer_float or EXT_color_buffer_half_float extension.
*
* @param {Scene} scene The object containing the rendering context
* @returns {boolean} true if supported
*/
DynamicEnvironmentMapManager.isDynamicUpdateSupported = function (scene) {
const context = scene.context;
return context.halfFloatingPointTexture || context.colorBufferFloat;
};
/**
* Average hue of ground color on earth, a warm green-gray.
* @type {Color}
* @readonly
*/
DynamicEnvironmentMapManager.AVERAGE_EARTH_GROUND_COLOR = Object.freeze(
Color.fromCssColorString("#717145"),
);
/**
* The default third order spherical harmonic coefficients used for the diffuse color of image-based lighting, a white ambient light with low intensity.
*
* There are nine Cartesian3 coefficients.
* The order of the coefficients is: L0,0, L1,-1, L1,0, L1,1, L2,-2, L2,-1, L2,0, L2,1, L2,2
*