diff --git a/CHANGES.md b/CHANGES.md
index 0cdd0ec2e29c..e3107c706259 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,14 @@
# Change Log
+### 1.123.1 - 2024-11-07
+
+#### @cesium/engine
+
+##### Additions :tada:
+
+- Added fallback diffuse lighting, `DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS`, that is used when `DynamicEnvironmentMapManager` is disabled or unsupported. [#12292](https://github.com/CesiumGS/cesium/pull/12292)
+- Added `DynamicEnvironmentMapManager.isDynamicUpdateSupported` to check if dynamic environment map updates are supported. [#12292](https://github.com/CesiumGS/cesium/pull/12292)
+
### 1.123 - 2024-11-01
#### @cesium/engine
diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js
index d8d140ce29f9..40fd2560f1be 100644
--- a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js
+++ b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js
@@ -95,7 +95,8 @@ function DynamicEnvironmentMapManager(options) {
this._radianceCubeMap = undefined;
this._irradianceMapTexture = undefined;
- this._sphericalHarmonicCoefficients = new Array(9);
+ this._sphericalHarmonicCoefficients =
+ DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS.slice();
this._lastTime = new JulianDate();
const width = Math.pow(2, mipmapLevels - 1);
@@ -727,8 +728,13 @@ function updateSphericalHarmonicCoefficients(manager, frameState) {
*/
DynamicEnvironmentMapManager.prototype.update = function (frameState) {
const mode = frameState.mode;
+ const isSupported =
+ // A FrameState type works here because the function only references the context parameter.
+ // @ts-ignore
+ DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState);
if (
+ !isSupported ||
!this.enabled ||
!this.shouldUpdate ||
!defined(this._position) ||
@@ -841,12 +847,48 @@ DynamicEnvironmentMapManager.prototype.destroy = function () {
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
+ *
+ * @readonly
+ * @type {Cartesian3[]}
+ * @see {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf|An Efficient Representation for Irradiance Environment Maps}
+ */
+DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS =
+ Object.freeze([
+ Object.freeze(new Cartesian3(0.35449, 0.35449, 0.35449)),
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ Cartesian3.ZERO,
+ ]);
+
export default DynamicEnvironmentMapManager;
diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
index 1f70a75175c3..320870826e3e 100644
--- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js
@@ -211,9 +211,6 @@ describe(
options = {
cullRequestsWhileMoving: false,
- environmentMapOptions: {
- enabled: scene.highDynamicRangeSupported,
- },
};
});
diff --git a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js
index 57ea8ee5354f..de82af7a9478 100644
--- a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js
+++ b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js
@@ -31,6 +31,40 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
expect(manager.groundAlbedo).toBe(0.31);
});
+ it("constructs with options", function () {
+ const manager = new DynamicEnvironmentMapManager({
+ enabled: false,
+ maximumSecondsDifference: 0,
+ maximumPositionEpsilon: 0,
+ atmosphereScatteringIntensity: 0,
+ gamma: 0,
+ brightness: 0,
+ saturation: 0,
+ groundColor: Color.BLUE,
+ groundAlbedo: 0,
+ });
+
+ expect(manager.enabled).toBeFalse();
+ expect(manager.shouldUpdate).toBeTrue();
+ expect(manager.maximumSecondsDifference).toBe(0);
+ expect(manager.maximumPositionEpsilon).toBe(0);
+ expect(manager.atmosphereScatteringIntensity).toBe(0);
+ expect(manager.gamma).toBe(0);
+ expect(manager.brightness).toBe(0);
+ expect(manager.saturation).toBe(0);
+ expect(manager.groundColor).toEqual(Color.BLUE);
+ expect(manager.groundAlbedo).toBe(0);
+ });
+
+ it("uses default spherical harmonic coefficients", () => {
+ const manager = new DynamicEnvironmentMapManager();
+
+ expect(manager.sphericalHarmonicCoefficients.length).toBe(9);
+ expect(manager.sphericalHarmonicCoefficients).toEqual(
+ DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS,
+ );
+ });
+
describe(
"render tests",
() => {
@@ -53,7 +87,7 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
scene.atmosphere = new Atmosphere();
});
- // Allows the compute commands to be added to the command list at the right point in the pipeline
+ // A pared-down Primitive. Allows the compute commands to be added to the command list at the right point in the pipeline.
function EnvironmentMockPrimitive(manager) {
this.update = function (frameState) {
manager.update(frameState);
@@ -64,8 +98,74 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
};
}
+ it("does not update if position is undefined", async function () {
+ const manager = new DynamicEnvironmentMapManager();
+
+ const primitive = new EnvironmentMockPrimitive(manager);
+ scene.primitives.add(primitive);
+
+ scene.renderForSpecs();
+
+ expect(manager.radianceCubeMap).toBeUndefined();
+
+ scene.renderForSpecs();
+
+ expect(manager.sphericalHarmonicCoefficients).toEqual(
+ DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS,
+ );
+ });
+
+ it("does not update if enabled is false", async function () {
+ const manager = new DynamicEnvironmentMapManager();
+
+ const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583);
+ manager.position =
+ Ellipsoid.WGS84.cartographicToCartesian(cartographic);
+ manager.enabled = false;
+
+ const primitive = new EnvironmentMockPrimitive(manager);
+ scene.primitives.add(primitive);
+
+ scene.renderForSpecs();
+
+ expect(manager.radianceCubeMap).toBeUndefined();
+
+ scene.renderForSpecs();
+
+ expect(manager.sphericalHarmonicCoefficients).toEqual(
+ DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS,
+ );
+ });
+
+ it("does not update if requires extensions are not available", async function () {
+ spyOn(
+ DynamicEnvironmentMapManager,
+ "isDynamicUpdateSupported",
+ ).and.returnValue(false);
+
+ const manager = new DynamicEnvironmentMapManager();
+
+ const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583);
+ manager.position =
+ Ellipsoid.WGS84.cartographicToCartesian(cartographic);
+
+ const primitive = new EnvironmentMockPrimitive(manager);
+ scene.primitives.add(primitive);
+
+ scene.renderForSpecs();
+
+ expect(manager.radianceCubeMap).toBeUndefined();
+
+ scene.renderForSpecs();
+
+ expect(manager.sphericalHarmonicCoefficients).toEqual(
+ DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS,
+ );
+ });
+
it("creates environment map and spherical harmonics at surface in Philadelphia with static lighting", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -146,7 +246,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("creates environment map and spherical harmonics at altitude in Philadelphia with static lighting", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -231,7 +332,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("creates environment map and spherical harmonics above Earth's atmosphere with static lighting", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -317,7 +419,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("creates environment map and spherical harmonics at surface in Philadelphia with dynamic lighting", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -401,7 +504,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("creates environment map and spherical harmonics at surface in Sydney with dynamic lighting", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -485,7 +589,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses atmosphere properties", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -572,10 +677,10 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses atmosphereScatteringIntensity value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
-
const manager = new DynamicEnvironmentMapManager();
manager.atmosphereScatteringIntensity = 1.0;
@@ -654,7 +759,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses gamma value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -736,7 +842,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses brightness value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -818,7 +925,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses saturation value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -900,7 +1008,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses ground color value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -982,7 +1091,8 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("lighting uses ground albedo value", async function () {
- if (!scene.highDynamicRangeSupported) {
+ // Skip if required WebGL extensions are not supported
+ if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) {
return;
}
@@ -1064,18 +1174,6 @@ describe("Scene/DynamicEnvironmentMapManager", function () {
});
it("destroys", function () {
- if (!scene.highDynamicRangeSupported) {
- const manager = new DynamicEnvironmentMapManager();
- const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583);
- manager.position =
- Ellipsoid.WGS84.cartographicToCartesian(cartographic);
-
- manager.destroy();
-
- expect(manager.isDestroyed()).toBe(true);
- return;
- }
-
const manager = new DynamicEnvironmentMapManager();
const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583);
manager.position =
diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js
index 2f31aa01b5fa..7af9758fdad9 100644
--- a/packages/engine/Specs/Scene/Model/ModelSpec.js
+++ b/packages/engine/Specs/Scene/Model/ModelSpec.js
@@ -3113,10 +3113,13 @@ describe(
{
gltf: boxTexturedGltfUrl,
lightColor: Cartesian3.ZERO,
- imageBasedLighting: undefined,
},
scene,
);
+
+ // ignore any image-based lighting– Test directional light only
+ model.imageBasedLighting.imageBasedLightingFactor = Cartesian2.ZERO;
+
verifyRender(model, false);
});
@@ -3125,6 +3128,10 @@ describe(
{ gltf: boxTexturedGltfUrl, imageBasedLighting: undefined },
scene,
);
+
+ // ignore any image-based lighting– Test directional light only
+ model.imageBasedLighting.imageBasedLightingFactor = Cartesian2.ZERO;
+
model.lightColor = Cartesian3.ZERO;
verifyRender(model, false);
@@ -3193,7 +3200,7 @@ describe(
it("changing imageBasedLighting works", async function () {
const imageBasedLighting = new ImageBasedLighting({
sphericalHarmonicCoefficients: [
- new Cartesian3(0.35449, 0.35449, 0.35449),
+ new Cartesian3(1.0, 0.0, 0.0),
Cartesian3.ZERO,
Cartesian3.ZERO,
Cartesian3.ZERO,
diff --git a/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js
index e50ab6a8cf5c..514f53df64f5 100644
--- a/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js
+++ b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js
@@ -1,28 +1,12 @@
-import { Model, ImageBasedLighting, Cartesian3 } from "../../../index.js";
+import { Model } from "../../../index.js";
import pollToPromise from "../../../../../Specs/pollToPromise.js";
-// A white ambient light with low intensity
-const defaultIbl = new ImageBasedLighting({
- sphericalHarmonicCoefficients: [
- new Cartesian3(0.35449, 0.35449, 0.35449),
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- ],
-});
-
async function loadAndZoomToModelAsync(options, scene) {
options = {
environmentMapOptions: {
enabled: false, // disable other diffuse lighting by default
...options.environmentMapOptions,
},
- imageBasedLighting: defaultIbl,
...options,
};
diff --git a/packages/engine/Specs/Scene/ShadowMapSpec.js b/packages/engine/Specs/Scene/ShadowMapSpec.js
index 2a66f528b10d..1b142d13152b 100644
--- a/packages/engine/Specs/Scene/ShadowMapSpec.js
+++ b/packages/engine/Specs/Scene/ShadowMapSpec.js
@@ -11,7 +11,6 @@ import {
HeadingPitchRange,
HeadingPitchRoll,
HeightmapTerrainData,
- ImageBasedLighting,
JulianDate,
Math as CesiumMath,
Matrix4,
@@ -282,26 +281,11 @@ describe(
}
async function loadModel(options) {
- // A white ambient light with low intensity
- const defaultIbl = new ImageBasedLighting({
- sphericalHarmonicCoefficients: [
- new Cartesian3(0.35449, 0.35449, 0.35449),
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- Cartesian3.ZERO,
- ],
- });
const model = scene.primitives.add(
await Model.fromGltfAsync({
environmentMapOptions: {
enabled: false,
},
- imageBasedLighting: defaultIbl,
...options,
}),
);