From 730c4195089418800cc7b4954f39e759df8bd416 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 7 Nov 2024 10:18:26 -0500 Subject: [PATCH] Add fallback spherical harmonics when dynamic environment lighting is not supported --- .../Scene/DynamicEnvironmentMapManager.js | 44 ++++- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 3 - .../Scene/DynamicEnvironmentMapManagerSpec.js | 150 +++++++++++++++--- .../Scene/Model/loadAndZoomToModelAsync.js | 18 +-- packages/engine/Specs/Scene/ShadowMapSpec.js | 16 -- 5 files changed, 168 insertions(+), 63 deletions(-) diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js index d8d140ce29f..2b6abef67c5 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.DEAFULT_SPHERICAL_HARMONIC_COEFFICIENTS.slice(); this._lastTime = new JulianDate(); const width = Math.pow(2, mipmapLevels - 1); @@ -727,8 +728,11 @@ function updateSphericalHarmonicCoefficients(manager, frameState) { */ DynamicEnvironmentMapManager.prototype.update = function (frameState) { const mode = frameState.mode; + const isSupported = + DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState); if ( + !isSupported || !this.enabled || !this.shouldUpdate || !defined(this._position) || @@ -841,12 +845,50 @@ 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|FrameState} contextOption The object containing the rendering context + * @returns {boolean} true if supported + */ +DynamicEnvironmentMapManager.isDynamicUpdateSupported = function ( + contextOption, +) { + const context = contextOption.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.DEAFULT_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 1f70a75175c..320870826e3 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 57ea8ee5354..7647e76fbe1 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.DEAFULT_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.DEAFULT_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.DEAFULT_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.DEAFULT_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/loadAndZoomToModelAsync.js b/packages/engine/Specs/Scene/Model/loadAndZoomToModelAsync.js index e50ab6a8cf5..514f53df64f 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 2a66f528b10..1b142d13152 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, }), );