From 73146e09e7512dc279a7dcb2c65a03c9af313329 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 7 Dec 2023 11:19:38 -0500 Subject: [PATCH 01/55] Set up scaffolding for FogPipelineStage --- .../Source/Scene/Model/FogPipelineStage.js | 18 ++++++++++++++++++ .../Scene/Model/ModelRuntimePrimitive.js | 2 ++ .../Source/Shaders/Model/FogStageFS.glsl | 17 +++++++++++++++++ .../Shaders/Model/ModelColorStageFS.glsl | 2 +- .../engine/Source/Shaders/Model/ModelFS.glsl | 2 ++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/engine/Source/Scene/Model/FogPipelineStage.js create mode 100644 packages/engine/Source/Shaders/Model/FogStageFS.glsl diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js new file mode 100644 index 000000000000..5f32ea71fe51 --- /dev/null +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -0,0 +1,18 @@ +import FogStageFS from "../../Shaders/Model/FogStageFS.js"; + +/** + * The fog color pipeline stage is responsible for applying fog to tiles in the distance in horizon views. + * + * @namespace FogColorPipelineStage + * + * @private + */ +const FogColorPipelineStage = { + name: "FogColorPipelineStage", // Helps with debugging +}; + +FogColorPipelineStage.process = function (renderResources, model, frameState) { + renderResources.shaderBuilder.addFragmentLines(FogStageFS); +}; + +export default FogColorPipelineStage; diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index 5593643409ea..824c22153be0 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -11,6 +11,7 @@ import CustomShaderMode from "./CustomShaderMode.js"; import CustomShaderPipelineStage from "./CustomShaderPipelineStage.js"; import DequantizationPipelineStage from "./DequantizationPipelineStage.js"; import FeatureIdPipelineStage from "./FeatureIdPipelineStage.js"; +import FogPipelineStage from "./FogPipelineStage.js"; import GeometryPipelineStage from "./GeometryPipelineStage.js"; import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; @@ -296,6 +297,7 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { } pipelineStages.push(AlphaPipelineStage); + pipelineStages.push(FogPipelineStage); pipelineStages.push(PrimitiveStatisticsPipelineStage); diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl new file mode 100644 index 000000000000..188d979c5544 --- /dev/null +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -0,0 +1,17 @@ +void fogStage(inout vec4 color, in ProcessedAttributes attributes) { + const vec4 FOG_COLOR = vec4(0.5, 0.0, 1.0, 1.0); + + // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. + // otherwise in the vertex shader. but for prototyping, I'll do everything in the FS for simplicity + + + + // Matches the constant in GlobeFS.glsl. This makes the fog falloff + // more gradual. + const float fogModifier = 0.15; + float distanceToCamera = attributes.positionEC.z; + // where to get distance? + vec3 withFog = czm_fog(distanceToCamera, color.rgb, FOG_COLOR.rgb, fogModifier); + + color = vec4(withFog, color.a); +} diff --git a/packages/engine/Source/Shaders/Model/ModelColorStageFS.glsl b/packages/engine/Source/Shaders/Model/ModelColorStageFS.glsl index ad03772237e2..099fc0f6fcb6 100644 --- a/packages/engine/Source/Shaders/Model/ModelColorStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelColorStageFS.glsl @@ -4,4 +4,4 @@ void modelColorStage(inout czm_modelMaterial material) float highlight = ceil(model_colorBlend); material.diffuse *= mix(model_color.rgb, vec3(1.0), highlight); material.alpha *= model_color.a; -} \ No newline at end of file +} diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index f56da251646a..565013f1bc63 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -79,5 +79,7 @@ void main() silhouetteStage(color); #endif + fogStage(color, attributes); + out_FragColor = color; } From 35c7d9d797e0c70962f5748d91216a025d5d1dca Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 7 Dec 2023 16:56:41 -0500 Subject: [PATCH 02/55] Add atmosphere settings to FrameState and UniformState --- .../engine/Source/Renderer/UniformState.js | 97 +++++++++++++++++++ packages/engine/Source/Scene/FrameState.js | 26 +++++ .../Source/Scene/Model/FogPipelineStage.js | 9 +- .../Source/Shaders/Model/FogStageFS.glsl | 10 +- 4 files changed, 139 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index ffe2b972ecaa..dd9d3241624a 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -158,6 +158,14 @@ function UniformState() { this._fogDensity = undefined; + this._atmosphereHsbShift = undefined; + this._atmosphereLightIntensity = undefined; + this._atmosphereRayleighCoefficient = new Cartesian3(); + this._atmosphereRayleighScaleHeight = new Cartesian3(); + this._atmosphereMieCoefficient = new Cartesian3(); + this._atmosphereMieScaleHeight = undefined; + this._atmosphereMieAnisotropy = undefined; + this._invertClassificationColor = undefined; this._splitPosition = 0.0; @@ -865,6 +873,77 @@ Object.defineProperties(UniformState.prototype, { }, }, + /** + * A color shift to apply to the atmosphere color in HSB. + * @memberof UniformState.prototype + * @type {Cartesian3} + */ + atmosphereHsbShift: { + get: function () { + return this._atmosphereHsbShift; + }, + }, + /** + * The intensity of the light that is used for computing the atmosphere color + * @memberof UniformState.prototype + * @type {number} + */ + atmosphereLightIntensity: { + get: function () { + return this._atmosphereLightIntensity; + }, + }, + /** + * The Rayleigh scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. + * @memberof UniformState.prototype + * @type {Cartesian3} + */ + atmosphereRayleighCoefficient: { + get: function () { + return this._atmosphereRayleighCoefficient; + }, + }, + /** + * The Rayleigh scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. + * @memberof UniformState.prototype + * @type {number} + */ + atmosphereRayleighScaleHeight: { + get: function () { + return this._atmosphereRayleighScaleHeight; + }, + }, + /** + * The Mie scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. + * @memberof UniformState.prototype + * @type {Cartesian3} + */ + atmosphereMieCoefficient: { + get: function () { + return this._atmosphereMieCoefficient; + }, + }, + /** + * The Mie scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. + * @memberof UniformState.prototype + * @type {number} + */ + atmosphereMieScaleHeight: { + get: function () { + return this._atmosphereMieScaleHeight; + }, + }, + /** + * The anisotropy of the medium to consider for Mie scattering. + * @memberof UniformState.prototype + * @type {number} + */ + atmosphereAnisotropy: { + get: function () { + return this._atmosphereAnisotropy; + }, + }, + /** * A scalar that represents the geometric tolerance per meter * @memberof UniformState.prototype @@ -1294,6 +1373,24 @@ UniformState.prototype.update = function (frameState) { this._fogDensity = frameState.fog.density; + this._atmosphereHsbShift = Cartesian3.clone( + frameState.atmosphere.hsbShift, + this._atmosphereHsbShift + ); + this._atmosphereRayleighCoefficient = Cartesian3.clone( + frameState.atmosphere.rayleighCoefficient, + this._atmosphereRayleighCoefficient + ); + this._atmosphereMieCoefficient = Cartesian3.clone( + frameState.atmosphere.mieCoefficient, + this._atmosphereMieCoefficient + ); + this._atmospherelightIntensity = frameState.atmosphere.lightIntensity; + this._atmosphereRayleighScaleHeight = + frameState.atmosphere.rayleighScaleHeight; + this._atmosphereMieScaleHeight = frameState.atmosphere.mieScaleHeight; + this._atmosphereMieAnisotropy = frameState.atmosphere.mieAnisotropy; + this._invertClassificationColor = frameState.invertClassificationColor; this._frameState = frameState; diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 14b50cefe1d7..31172ab4b696 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -1,4 +1,5 @@ import SceneMode from "./SceneMode.js"; +import Cartesian3 from "../Core/Cartesian3.js"; /** * State information about the current frame. An instance of this class @@ -274,6 +275,31 @@ function FrameState(context, creditDisplay, jobScheduler) { minimumBrightness: undefined, }; + /** + * @typedef FrameState.Atmosphere + * @type {object} + * @property {Cartesian3} hsbShift A color shift to apply to the atmosphere color in HSB. + * @property {number} lightIntensity The intensity of the light that is used for computing the atmosphere color + * @property {Cartesian3} rayleighCoefficient The Rayleigh scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. + * @property {number} rayleighScaleHeight The Rayleigh scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. + * @property {Cartesian3} mieCoefficient The Mie scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. + * @property {number} mieScaleHeight The Mie scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. + * @property {number} mieAnisotropy The anisotropy of the medium to consider for Mie scattering. + */ + + /** + * @type {FrameState.Atmosphere} + */ + this.atmosphere = { + hsbShift: new Cartesian3(), + lightIntensity: undefined, + rayleighCoefficient: new Cartesian3(), + rayleighScaleHeight: undefined, + mieCoefficient: new Cartesian3(), + mieScaleHeight: undefined, + mieAnisotropy: undefined, + }; + /** * A scalar used to exaggerate the terrain. * @type {number} diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index 5f32ea71fe51..5471c143e76e 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -1,3 +1,4 @@ +import AtmosphereCommon from "../../Shaders/AtmosphereCommon.js"; import FogStageFS from "../../Shaders/Model/FogStageFS.js"; /** @@ -12,7 +13,13 @@ const FogColorPipelineStage = { }; FogColorPipelineStage.process = function (renderResources, model, frameState) { - renderResources.shaderBuilder.addFragmentLines(FogStageFS); + // TODO: AtmosphereCommon.glsl includes uniforms that really should be + // added separately to match the Model pipeline paradigm... Maybe that file could + // be split into multiple files. + renderResources.shaderBuilder.addFragmentLines([ + AtmosphereCommon, + FogStageFS, + ]); }; export default FogColorPipelineStage; diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 188d979c5544..10e72cbee969 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,11 +1,17 @@ +vec3 computeFogColor() { + //vec4 groundAtmosphereColor = computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); + //vec3 fogColor = groundAtmosphereColor.rgb; + return vec3(1.0); +} + void fogStage(inout vec4 color, in ProcessedAttributes attributes) { const vec4 FOG_COLOR = vec4(0.5, 0.0, 1.0, 1.0); + //vec3 fogColor = computeFogColor; + // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. // otherwise in the vertex shader. but for prototyping, I'll do everything in the FS for simplicity - - // Matches the constant in GlobeFS.glsl. This makes the fog falloff // more gradual. const float fogModifier = 0.15; From 8723127f45107de814b1bf2df314d1873a31cfe3 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 18 Dec 2023 11:40:36 -0500 Subject: [PATCH 03/55] Make builtins for common atmosphere functions --- .../Source/Shaders/AtmosphereCommon.glsl | 2 +- .../Builtin/Functions/approximateTanh.glsl | 10 ++ .../Functions/computeAtmosphereColor.glsl | 44 ++++++ .../Functions/computeEllipsoidPosition.glsl | 22 +++ .../computeGroundAtmosphereScattering.glsl | 30 ++++ .../Builtin/Functions/computeScattering.glsl | 149 ++++++++++++++++++ packages/engine/Source/Shaders/GlobeFS.glsl | 18 +-- .../Source/Shaders/Model/FogStageFS.glsl | 44 +++++- 8 files changed, 306 insertions(+), 13 deletions(-) create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/computeAtmosphereColor.glsl create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/computeGroundAtmosphereScattering.glsl create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl diff --git a/packages/engine/Source/Shaders/AtmosphereCommon.glsl b/packages/engine/Source/Shaders/AtmosphereCommon.glsl index 84acc5255659..e0e23eab78cb 100644 --- a/packages/engine/Source/Shaders/AtmosphereCommon.glsl +++ b/packages/engine/Source/Shaders/AtmosphereCommon.glsl @@ -66,7 +66,7 @@ void computeScattering( // Value close to 0.0: close to the horizon // Value close to 1.0: above in the sky float w_stop_gt_lprl = 0.5 * (1.0 + approximateTanh(x)); - + // The ray should start from the first intersection with the outer atmopshere, or from the camera position, if it is inside the atmosphere. float start_0 = primaryRayAtmosphereIntersect.start; primaryRayAtmosphereIntersect.start = max(primaryRayAtmosphereIntersect.start, 0.0); diff --git a/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl b/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl new file mode 100644 index 000000000000..f956b14274c3 --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl @@ -0,0 +1,10 @@ +/** + * Compute a rational approximation to tanh(x) + * + * @param {float} x A real number input + * @returns {float} An approximation for tanh(x) +*/ +float czm_approximateTanh(float x) { + float x2 = x * x; + return max(-1.0, min(+1.0, x * (27.0 + x2) / (27.0 + 9.0 * x2))); +} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeAtmosphereColor.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeAtmosphereColor.glsl new file mode 100644 index 000000000000..d4cc21971e7a --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeAtmosphereColor.glsl @@ -0,0 +1,44 @@ +/** + * Compute the atmosphere color, applying Rayleigh and Mie scattering. This + * builtin uses automatic uniforms so the atmophere settings are synced with the + * state of the Scene, even in other contexts like Model. + * + * @name czm_computeAtmosphereColor + * @glslFunction + * + * @param {vec3} positionWC Position of the fragment in world coords (low precision) + * @param {vec3} lightDirection Light direction from the sun or other light source. + * @param {vec3} rayleighColor The Rayleigh scattering color computed by a scattering function + * @param {vec3} mieColor The Mie scattering color computed by a scattering function + * @param {float} opacity The opacity computed by a scattering function. + */ +vec4 czm_computeAtmosphereColor( + vec3 positionWC, + vec3 lightDirection, + vec3 rayleighColor, + vec3 mieColor, + float opacity +) { + // Setup the primary ray: from the camera position to the vertex position. + vec3 cameraToPositionWC = positionWC - czm_viewerPositionWC; + vec3 cameraToPositionWCDirection = normalize(cameraToPositionWC); + + float cosAngle = dot(cameraToPositionWCDirection, lightDirection); + float cosAngleSq = cosAngle * cosAngle; + + float G = czm_atmosphereMieAnisotropy; + float GSq = G * G; + + // The Rayleigh phase function. + float rayleighPhase = 3.0 / (50.2654824574) * (1.0 + cosAngleSq); + // The Mie phase function. + float miePhase = 3.0 / (25.1327412287) * ((1.0 - GSq) * (cosAngleSq + 1.0)) / (pow(1.0 + GSq - 2.0 * cosAngle * G, 1.5) * (2.0 + GSq)); + + // The final color is generated by combining the effects of the Rayleigh and Mie scattering. + vec3 rayleigh = rayleighPhase * rayleighColor; + vec3 mie = miePhase * mieColor; + + vec3 color = (rayleigh + mie) * czm_atmosphereLightIntensity; + + return vec4(color, opacity); +} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl new file mode 100644 index 000000000000..f11eea6caa73 --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl @@ -0,0 +1,22 @@ +/** + * Compute the WC position on the elipsoid of the current fragment. The result + * is low-precision due to use of 32-bit floats. + * + * @return {vec3} The position in world coordinates. + */ +vec3 czm_computeEllipsoidPosition() +{ + float mpp = czm_metersPerPixel(vec4(0.0, 0.0, -czm_currentFrustum.x, 1.0), 1.0); + vec2 xy = gl_FragCoord.xy / czm_viewport.zw * 2.0 - vec2(1.0); + xy *= czm_viewport.zw * mpp * 0.5; + + vec3 direction = normalize(vec3(xy, -czm_currentFrustum.x)); + czm_ray ray = czm_ray(vec3(0.0), direction); + + vec3 ellipsoid_center = czm_view[3].xyz; + + czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid_center, czm_ellipsoidInverseRadii); + + vec3 ellipsoidPosition = czm_pointAlongRay(ray, intersection.start); + return (czm_inverseView * vec4(ellipsoidPosition, 1.0)).xyz; +} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeGroundAtmosphereScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeGroundAtmosphereScattering.glsl new file mode 100644 index 000000000000..d8b172e3b259 --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeGroundAtmosphereScattering.glsl @@ -0,0 +1,30 @@ +/** + * Compute atmosphere scattering for the ground atmosphere and fog. This method + * uses automatic uniforms so it is always synced with the scene settings. + * + * @name czm_computeGroundAtmosphereScattering + * @glslfunction + * + * @param {vec3} positionWC The position of the fragment in world coordinates. + * @param {vec3} lightDirection The direction of the light to calculate the scattering from. + * @param {vec3} rayleighColor The variable the Rayleigh scattering will be written to. + * @param {vec3} mieColor The variable the Mie scattering will be written to. + * @param {float} opacity The variable the transmittance will be written to. + */ +void czm_computeGroundAtmosphereScattering(vec3 positionWC, vec3 lightDirection, out vec3 rayleighColor, out vec3 mieColor, out float opacity) { + vec3 cameraToPositionWC = positionWC - czm_viewerPositionWC; + vec3 cameraToPositionWCDirection = normalize(cameraToPositionWC); + czm_ray primaryRay = czm_ray(czm_viewerPositionWC, cameraToPositionWCDirection); + + float atmosphereInnerRadius = length(positionWC); + + czm_computeScattering( + primaryRay, + length(cameraToPositionWC), + lightDirection, + atmosphereInnerRadius, + rayleighColor, + mieColor, + opacity + ); +} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl new file mode 100644 index 000000000000..46dee64c07cc --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl @@ -0,0 +1,149 @@ +/** + * This function computes the colors contributed by Rayliegh and Mie scattering on a given ray, as well as + * the transmittance value for the ray. This function uses automatic uniforms + * so the atmosphere settings are always synced with the current scene. + * + * @name czm_computeScattering + * @glslfunction + * + * @param {czm_ray} primaryRay The ray from the camera to the position. + * @param {float} primaryRayLength The length of the primary ray. + * @param {vec3} lightDirection The direction of the light to calculate the scattering from. + * @param {vec3} rayleighColor The variable the Rayleigh scattering will be written to. + * @param {vec3} mieColor The variable the Mie scattering will be written to. + * @param {float} opacity The variable the transmittance will be written to. + */ +void czm_computeScattering( + czm_ray primaryRay, + float primaryRayLength, + vec3 lightDirection, + float atmosphereInnerRadius, + out vec3 rayleighColor, + out vec3 mieColor, + out float opacity +) { + const float ATMOSPHERE_THICKNESS = 111e3; // The thickness of the atmosphere in meters. + const int PRIMARY_STEPS_MAX = 16; // Maximum number of times the ray from the camera to the world position (primary ray) is sampled. + const int LIGHT_STEPS_MAX = 4; // Maximum number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray. + + // Initialize the default scattering amounts to 0. + rayleighColor = vec3(0.0); + mieColor = vec3(0.0); + opacity = 0.0; + + float atmosphereOuterRadius = atmosphereInnerRadius + ATMOSPHERE_THICKNESS; + + vec3 origin = vec3(0.0); + + // Calculate intersection from the camera to the outer ring of the atmosphere. + czm_raySegment primaryRayAtmosphereIntersect = czm_raySphereIntersectionInterval(primaryRay, origin, atmosphereOuterRadius); + + // Return empty colors if no intersection with the atmosphere geometry. + if (primaryRayAtmosphereIntersect == czm_emptyRaySegment) { + return; + } + + // To deal with smaller values of PRIMARY_STEPS (e.g. 4) + // we implement a split strategy: sky or horizon. + // For performance reasons, instead of a if/else branch + // a soft choice is implemented through a weight 0.0 <= w_stop_gt_lprl <= 1.0 + float x = 1e-7 * primaryRayAtmosphereIntersect.stop / length(primaryRayLength); + // Value close to 0.0: close to the horizon + // Value close to 1.0: above in the sky + float w_stop_gt_lprl = 0.5 * (1.0 + czm_approximateTanh(x)); + + // The ray should start from the first intersection with the outer atmopshere, or from the camera position, if it is inside the atmosphere. + float start_0 = primaryRayAtmosphereIntersect.start; + primaryRayAtmosphereIntersect.start = max(primaryRayAtmosphereIntersect.start, 0.0); + // The ray should end at the exit from the atmosphere or at the distance to the vertex, whichever is smaller. + primaryRayAtmosphereIntersect.stop = min(primaryRayAtmosphereIntersect.stop, length(primaryRayLength)); + + // For the number of ray steps, distinguish inside or outside atmosphere (outer space) + // (1) from outer space we have to use more ray steps to get a realistic rendering + // (2) within atmosphere we need fewer steps for faster rendering + float x_o_a = start_0 - ATMOSPHERE_THICKNESS; // ATMOSPHERE_THICKNESS used as an ad-hoc constant, no precise meaning here, only the order of magnitude matters + float w_inside_atmosphere = 1.0 - 0.5 * (1.0 + czm_approximateTanh(x_o_a)); + int PRIMARY_STEPS = PRIMARY_STEPS_MAX - int(w_inside_atmosphere * 12.0); // Number of times the ray from the camera to the world position (primary ray) is sampled. + int LIGHT_STEPS = LIGHT_STEPS_MAX - int(w_inside_atmosphere * 2.0); // Number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray. + + // Setup for sampling positions along the ray - starting from the intersection with the outer ring of the atmosphere. + float rayPositionLength = primaryRayAtmosphereIntersect.start; + // (1) Outside the atmosphere: constant rayStepLength + // (2) Inside atmosphere: variable rayStepLength to compensate the rough rendering of the smaller number of ray steps + float totalRayLength = primaryRayAtmosphereIntersect.stop - rayPositionLength; + float rayStepLengthIncrease = w_inside_atmosphere * ((1.0 - w_stop_gt_lprl) * totalRayLength / (float(PRIMARY_STEPS * (PRIMARY_STEPS + 1)) / 2.0)); + float rayStepLength = max(1.0 - w_inside_atmosphere, w_stop_gt_lprl) * totalRayLength / max(7.0 * w_inside_atmosphere, float(PRIMARY_STEPS)); + + vec3 rayleighAccumulation = vec3(0.0); + vec3 mieAccumulation = vec3(0.0); + vec2 opticalDepth = vec2(0.0); + vec2 heightScale = vec2(czm_atmosphereRayleighScaleHeight, czm_atmosphereMieScaleHeight); + + // Sample positions on the primary ray. + for (int i = 0; i < PRIMARY_STEPS_MAX; ++i) { + + // The loop should be: for (int i = 0; i < PRIMARY_STEPS; ++i) {...} but WebGL1 cannot + // loop with non-constant condition, so it has to break early instead + if (i >= PRIMARY_STEPS) { + break; + } + + // Calculate sample position along viewpoint ray. + vec3 samplePosition = primaryRay.origin + primaryRay.direction * (rayPositionLength + rayStepLength); + + // Calculate height of sample position above ellipsoid. + float sampleHeight = length(samplePosition) - atmosphereInnerRadius; + + // Calculate and accumulate density of particles at the sample position. + vec2 sampleDensity = exp(-sampleHeight / heightScale) * rayStepLength; + opticalDepth += sampleDensity; + + // Generate ray from the sample position segment to the light source, up to the outer ring of the atmosphere. + czm_ray lightRay = czm_ray(samplePosition, lightDirection); + czm_raySegment lightRayAtmosphereIntersect = czm_raySphereIntersectionInterval(lightRay, origin, atmosphereOuterRadius); + + float lightStepLength = lightRayAtmosphereIntersect.stop / float(LIGHT_STEPS); + float lightPositionLength = 0.0; + + vec2 lightOpticalDepth = vec2(0.0); + + // Sample positions along the light ray, to accumulate incidence of light on the latest sample segment. + for (int j = 0; j < LIGHT_STEPS_MAX; ++j) { + + // The loop should be: for (int j = 0; i < LIGHT_STEPS; ++j) {...} but WebGL1 cannot + // loop with non-constant condition, so it has to break early instead + if (j >= LIGHT_STEPS) { + break; + } + + // Calculate sample position along light ray. + vec3 lightPosition = samplePosition + lightDirection * (lightPositionLength + lightStepLength * 0.5); + + // Calculate height of the light sample position above ellipsoid. + float lightHeight = length(lightPosition) - atmosphereInnerRadius; + + // Calculate density of photons at the light sample position. + lightOpticalDepth += exp(-lightHeight / heightScale) * lightStepLength; + + // Increment distance on light ray. + lightPositionLength += lightStepLength; + } + + // Compute attenuation via the primary ray and the light ray. + vec3 attenuation = exp(-((czm_atmosphereMieCoefficient * (opticalDepth.y + lightOpticalDepth.y)) + (czm_atmosphereRayleighCoefficient * (opticalDepth.x + lightOpticalDepth.x)))); + + // Accumulate the scattering. + rayleighAccumulation += sampleDensity.x * attenuation; + mieAccumulation += sampleDensity.y * attenuation; + + // Increment distance on primary ray. + rayPositionLength += (rayStepLength += rayStepLengthIncrease); + } + + // Compute the scattering amount. + rayleighColor = czm_atmosphereRayleighCoefficient * rayleighAccumulation; + mieColor = czm_atmosphereMieCoefficient * mieAccumulation; + + // Compute the transmittance i.e. how much light is passing through the atmosphere. + opacity = length(exp(-((czm_atmosphereMieCoefficient * opticalDepth.y) + (czm_atmosphereRayleighCoefficient * opticalDepth.x)))); +} diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index bde6ce15c82c..3d181e60ee0c 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -396,7 +396,7 @@ void main() materialInput.st = v_textureCoordinates.st; materialInput.normalEC = normalize(v_normalEC); materialInput.positionToEyeEC = -v_positionEC; - materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalize(v_normalEC)); + materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalize(v_normalEC)); materialInput.slope = v_slope; materialInput.height = v_height; materialInput.aspect = v_aspect; @@ -442,7 +442,7 @@ void main() { bool dynamicLighting = false; #if defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_DAYNIGHT_SHADING) || defined(ENABLE_VERTEX_LIGHTING)) - dynamicLighting = true; + dynamicLighting = true; #endif vec3 rayleighColor; @@ -480,18 +480,18 @@ void main() // Fog is applied to tiles selected for fog, close to the Earth. #ifdef FOG vec3 fogColor = groundAtmosphereColor.rgb; - + // If there is lighting, apply that to the fog. #if defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING)) float darken = clamp(dot(normalize(czm_viewerPositionWC), atmosphereLightDirection), u_minimumBrightness, 1.0); - fogColor *= darken; + fogColor *= darken; #endif #ifndef HDR fogColor.rgb = czm_acesTonemapping(fogColor.rgb); fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif - + const float modifier = 0.15; finalColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor.rgb, modifier), finalColor.a); @@ -507,20 +507,20 @@ void main() #if defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING)) float fadeInDist = u_nightFadeDistance.x; float fadeOutDist = u_nightFadeDistance.y; - + float sunlitAtmosphereIntensity = clamp((cameraDist - fadeOutDist) / (fadeInDist - fadeOutDist), 0.05, 1.0); float darken = clamp(dot(normalize(positionWC), atmosphereLightDirection), 0.0, 1.0); vec3 darkenendGroundAtmosphereColor = mix(groundAtmosphereColor.rgb, finalAtmosphereColor.rgb, darken); finalAtmosphereColor = mix(darkenendGroundAtmosphereColor, finalAtmosphereColor, sunlitAtmosphereIntensity); #endif - + #ifndef HDR finalAtmosphereColor.rgb = vec3(1.0) - exp(-fExposure * finalAtmosphereColor.rgb); #else finalAtmosphereColor.rgb = czm_saturation(finalAtmosphereColor.rgb, 1.6); #endif - + finalColor.rgb = mix(finalColor.rgb, finalAtmosphereColor.rgb, fade); #endif } @@ -544,7 +544,7 @@ void main() finalColor.a *= interpolateByDistance(alphaByDistance, v_distance); } #endif - + out_FragColor = finalColor; } diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 10e72cbee969..b8d037ab61b1 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,13 +1,51 @@ vec3 computeFogColor() { - //vec4 groundAtmosphereColor = computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); - //vec3 fogColor = groundAtmosphereColor.rgb; + +//#if defined(DYNAMIC_ATMOSPHERE_LIGHTING_FROM_SUN) + vec3 atmosphereLightDirection = czm_sunDirectionWC; +//#else +// vec3 atmosphereLightDirection = czm_lightDirectionWC; +//#endif + + vec3 rayleighColor; + vec3 mieColor; + float opacity; + + vec3 positionWC; + vec3 lightDirection; + + positionWC = computeEllipsoidPosition(); + lightDirection = czm_branchFreeTernary(dynamicLighting, atmosphereLightDirection, normalize(positionWC)); + + // This is true when dynamic lighting is enabled in the scene. + bool dynamicLighting = false; + + // The fog color is derived from the ground atmosphere color + czm_computeGroundAtmosphereScattering( + positionWC, + lightDirection, + rayleighColor, + mieColor, + opacity + ); + + vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); + vec3 fogColor = groundAtmosphereColor.rgb; + + // Darken the fog + + // Tonemap if HDR rendering is disabled +#ifndef HDR + fogColor.rgb = czm_acesTonemapping(fogColor.rgb); + fogColor.rgb = czm_inverseGamma(fogColor.rgb); +#endif + return vec3(1.0); } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { const vec4 FOG_COLOR = vec4(0.5, 0.0, 1.0, 1.0); - //vec3 fogColor = computeFogColor; + vec3 fogColor = computeFogColor(); // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. // otherwise in the vertex shader. but for prototyping, I'll do everything in the FS for simplicity From bf1890cecd41bc18bb8213fb600c1848215be2ff Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 18 Dec 2023 12:40:41 -0500 Subject: [PATCH 04/55] Get a barebones atmosphere shader compiling --- .../Source/Renderer/AutomaticUniforms.js | 92 +++++++++++++++++++ .../engine/Source/Renderer/UniformState.js | 4 +- .../Source/Shaders/Model/FogStageFS.glsl | 10 +- 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index a3304ee874b4..f9dabf17763b 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1538,6 +1538,98 @@ const AutomaticUniforms = { }, }), + /** + * An automatic uniform representing the color shift for the atmosphere in HSB color space + * + * @example + * uniform vec3 czm_atmosphereHsbShift; + */ + czm_atmosphereHsbShift: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT_VEC3, + getValue: function (uniformState) { + return uniformState.atmosphereHsbShift; + }, + }), + /** + * An automatic uniform representing the intensity of the light that is used for computing the atmosphere color + * + * @example + * uniform float czm_atmosphereLightIntensity; + */ + czm_atmosphereLightIntensity: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereLightIntensity; + }, + }), + /** + * An automatic uniform representing the Rayleigh scattering coefficient used when computing the atmosphere scattering + * + * @example + * uniform vec3 czm_atmosphereRayleighCoefficient; + */ + czm_atmosphereRayleighCoefficient: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT_VEC3, + getValue: function (uniformState) { + return uniformState.atmosphereRayleighCoefficient; + }, + }), + /** + * An automatic uniform representing the Rayleigh scale height in meters used for computing atmosphere scattering. + * + * @example + * uniform vec3 czm_atmosphereRayleighScaleHeight; + */ + czm_atmosphereRayleighScaleHeight: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereRayleighScaleHeight; + }, + }), + /** + * An automatic uniform representing the Mie scattering coefficient used when computing atmosphere scattering. + * + * @example + * uniform vec3 czm_atmosphereMieCoefficient; + */ + czm_atmosphereMieCoefficient: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT_VEC3, + getValue: function (uniformState) { + return uniformState.atmosphereMieCoefficient; + }, + }), + /** + * An automatic uniform storign the Mie scale height used when computing atmosphere scattering. + * + * @example + * uniform float czm_atmosphereMieScaleHeight; + */ + czm_atmosphereMieScaleHeight: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereMieScaleHeight; + }, + }), + /** + * An automatic uniform representing the anisotropy of the medium to consider for Mie scattering. + * + * @example + * uniform float czm_atmosphereAnisotropy; + */ + czm_atmosphereMieAnisotropy: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereAnisotropy; + }, + }), + /** * An automatic GLSL uniform representing the splitter position to use when rendering with a splitter. * This will be in pixel coordinates relative to the canvas. diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index dd9d3241624a..d30837b3a89a 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -938,9 +938,9 @@ Object.defineProperties(UniformState.prototype, { * @memberof UniformState.prototype * @type {number} */ - atmosphereAnisotropy: { + atmosphereMieAnisotropy: { get: function () { - return this._atmosphereAnisotropy; + return this._atmosphereMieAnisotropy; }, }, diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index b8d037ab61b1..77160803c26e 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -13,12 +13,12 @@ vec3 computeFogColor() { vec3 positionWC; vec3 lightDirection; - positionWC = computeEllipsoidPosition(); - lightDirection = czm_branchFreeTernary(dynamicLighting, atmosphereLightDirection, normalize(positionWC)); - // This is true when dynamic lighting is enabled in the scene. bool dynamicLighting = false; + positionWC = czm_computeEllipsoidPosition(); + lightDirection = czm_branchFreeTernary(dynamicLighting, atmosphereLightDirection, normalize(positionWC)); + // The fog color is derived from the ground atmosphere color czm_computeGroundAtmosphereScattering( positionWC, @@ -43,8 +43,6 @@ vec3 computeFogColor() { } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { - const vec4 FOG_COLOR = vec4(0.5, 0.0, 1.0, 1.0); - vec3 fogColor = computeFogColor(); // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. @@ -55,7 +53,7 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { const float fogModifier = 0.15; float distanceToCamera = attributes.positionEC.z; // where to get distance? - vec3 withFog = czm_fog(distanceToCamera, color.rgb, FOG_COLOR.rgb, fogModifier); + vec3 withFog = czm_fog(distanceToCamera, color.rgb, fogColor, fogModifier); color = vec4(withFog, color.a); } From 0634b5309f019b39eafc6786a33cfc376575156a Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 18 Dec 2023 14:22:55 -0500 Subject: [PATCH 05/55] Create Atmosphere object to propagate uniforms --- packages/engine/Source/Scene/Atmosphere.js | 98 +++++++++++++++++++ packages/engine/Source/Scene/Globe.js | 1 + .../Source/Scene/Model/FogPipelineStage.js | 6 +- packages/engine/Source/Scene/Scene.js | 4 + .../Source/Shaders/Model/FogStageFS.glsl | 2 +- 5 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 packages/engine/Source/Scene/Atmosphere.js diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js new file mode 100644 index 000000000000..9d9c52c7b174 --- /dev/null +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -0,0 +1,98 @@ +import Cartesian3 from "../Core/Cartesian3"; + +function Atmosphere() { + /** + * The intensity of the light that is used for computing the ground atmosphere color. + * + * @type {number} + * @default 10.0 + */ + this.lightIntensity = 10.0; + + /** + * The Rayleigh scattering coefficient used in the atmospheric scattering equations for the ground atmosphere. + * + * @type {Cartesian3} + * @default Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) + */ + this.rayleighCoefficient = new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6); + + /** + * The Mie scattering coefficient used in the atmospheric scattering equations for the ground atmosphere. + * + * @type {Cartesian3} + * @default Cartesian3(21e-6, 21e-6, 21e-6) + */ + this.mieCoefficient = new Cartesian3(21e-6, 21e-6, 21e-6); + + /** + * The Rayleigh scale height used in the atmospheric scattering equations for the ground atmosphere, in meters. + * + * @type {number} + * @default 10000.0 + */ + this.rayleighScaleHeight = 10000.0; + + /** + * The Mie scale height used in the atmospheric scattering equations for the ground atmosphere, in meters. + * + * @type {number} + * @default 3200.0 + */ + this.mieScaleHeight = 3200.0; + + /** + * The anisotropy of the medium to consider for Mie scattering. + *

+ * Valid values are between -1.0 and 1.0. + *

+ * @type {number} + * @default 0.9 + */ + this.mieAnisotropy = 0.9; + + /** + * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A hue shift of 1.0 indicates a complete rotation of the hues available. + * @type {number} + * @default 0.0 + */ + this.hueShift = 0.0; + + /** + * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A saturation shift of -1.0 is monochrome. + * @type {number} + * @default 0.0 + */ + this.saturationShift = 0.0; + + /** + * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift). + * A brightness shift of -1.0 is complete darkness, which will let space show through. + * @type {number} + * @default 0.0 + */ + this.brightnessShift = 0.0; +} + +Atmosphere.prototype.update = function (frameState) { + const atmosphere = frameState.atmosphere; + atmosphere.hsbShift.x = this.hueShift; + atmosphere.hsbShift.y = this.saturationShift; + atmosphere.hsbShift.z = this.brightnessShift; + atmosphere.lightIntensity = this.lightIntensity; + atmosphere.rayleighCoefficient = Cartesian3.clone( + this.rayleighCoefficient, + atmosphere.rayleighCoefficient + ); + atmosphere.rayleightScaleHeight = this.rayleighScaleHeight; + atmosphere.mieCoefficient = Cartesian3.clone( + this.mieCoefficient, + atmosphere.mieCoefficient + ); + atmosphere.mieScaleHeight = this.mieScaleHeight; + atmosphere.mieAnisotropy = this.mieAnisotropy; +}; + +export default Atmosphere; diff --git a/packages/engine/Source/Scene/Globe.js b/packages/engine/Source/Scene/Globe.js index 35dd3ea498cf..898457eb1922 100644 --- a/packages/engine/Source/Scene/Globe.js +++ b/packages/engine/Source/Scene/Globe.js @@ -1059,6 +1059,7 @@ Globe.prototype.beginFrame = function (frameState) { tileProvider.undergroundColor = this._undergroundColor; tileProvider.undergroundColorAlphaByDistance = this._undergroundColorAlphaByDistance; tileProvider.lambertDiffuseMultiplier = this.lambertDiffuseMultiplier; + surface.beginFrame(frameState); } }; diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index 5471c143e76e..c9d902e74b76 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -1,4 +1,3 @@ -import AtmosphereCommon from "../../Shaders/AtmosphereCommon.js"; import FogStageFS from "../../Shaders/Model/FogStageFS.js"; /** @@ -16,10 +15,7 @@ FogColorPipelineStage.process = function (renderResources, model, frameState) { // TODO: AtmosphereCommon.glsl includes uniforms that really should be // added separately to match the Model pipeline paradigm... Maybe that file could // be split into multiple files. - renderResources.shaderBuilder.addFragmentLines([ - AtmosphereCommon, - FogStageFS, - ]); + renderResources.shaderBuilder.addFragmentLines([FogStageFS]); }; export default FogColorPipelineStage; diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 127c6aacfaad..70a98676b358 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -37,6 +37,7 @@ import Context from "../Renderer/Context.js"; import ContextLimits from "../Renderer/ContextLimits.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; +import Atmosphere from "./Atmosphere.js"; import BrdfLutGenerator from "./BrdfLutGenerator.js"; import Camera from "./Camera.js"; import Cesium3DTilePass from "./Cesium3DTilePass.js"; @@ -494,6 +495,8 @@ function Scene(options) { */ this.cameraEventWaitTime = 500.0; + this.atmosphere = new Atmosphere(); + /** * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional * performance improvements by rendering less geometry and dispatching less terrain requests. @@ -3715,6 +3718,7 @@ function render(scene) { } frameState.backgroundColor = backgroundColor; + scene.atmosphere.update(frameState); scene.fog.update(frameState); us.update(frameState); diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 77160803c26e..c32c27f4ecd4 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -39,7 +39,7 @@ vec3 computeFogColor() { fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif - return vec3(1.0); + return fogColor.rgb; } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { From d21e7bd1ee6555a40b21855eca818637cf1df119 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 18 Dec 2023 15:52:55 -0500 Subject: [PATCH 06/55] Fix typos in uniform names --- packages/engine/Source/Renderer/AutomaticUniforms.js | 2 +- packages/engine/Source/Renderer/UniformState.js | 6 +++--- packages/engine/Source/Scene/Atmosphere.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index f9dabf17763b..1662bf472ee3 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1626,7 +1626,7 @@ const AutomaticUniforms = { size: 1, datatype: WebGLConstants.FLOAT, getValue: function (uniformState) { - return uniformState.atmosphereAnisotropy; + return uniformState.atmosphereMieAnisotropy; }, }), diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index d30837b3a89a..b2f5ad412c9e 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1377,17 +1377,17 @@ UniformState.prototype.update = function (frameState) { frameState.atmosphere.hsbShift, this._atmosphereHsbShift ); + this._atmosphereLightIntensity = frameState.atmosphere.lightIntensity; this._atmosphereRayleighCoefficient = Cartesian3.clone( frameState.atmosphere.rayleighCoefficient, this._atmosphereRayleighCoefficient ); + this._atmosphereRayleighScaleHeight = + frameState.atmosphere.rayleighScaleHeight; this._atmosphereMieCoefficient = Cartesian3.clone( frameState.atmosphere.mieCoefficient, this._atmosphereMieCoefficient ); - this._atmospherelightIntensity = frameState.atmosphere.lightIntensity; - this._atmosphereRayleighScaleHeight = - frameState.atmosphere.rayleighScaleHeight; this._atmosphereMieScaleHeight = frameState.atmosphere.mieScaleHeight; this._atmosphereMieAnisotropy = frameState.atmosphere.mieAnisotropy; diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index 9d9c52c7b174..010904e97961 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -86,7 +86,7 @@ Atmosphere.prototype.update = function (frameState) { this.rayleighCoefficient, atmosphere.rayleighCoefficient ); - atmosphere.rayleightScaleHeight = this.rayleighScaleHeight; + atmosphere.rayleighScaleHeight = this.rayleighScaleHeight; atmosphere.mieCoefficient = Cartesian3.clone( this.mieCoefficient, atmosphere.mieCoefficient From 092f91e5e09f88df321f5a1fefaf97bdb2ccf79d Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 20 Dec 2023 09:46:17 -0500 Subject: [PATCH 07/55] Temporary debugging code --- .../Builtin/Functions/computeScattering.glsl | 12 +++++++++++ packages/engine/Source/Shaders/GlobeFS.glsl | 2 ++ .../Source/Shaders/Model/FogStageFS.glsl | 20 +++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl index 46dee64c07cc..63cef0c248eb 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl @@ -40,6 +40,7 @@ void czm_computeScattering( // Return empty colors if no intersection with the atmosphere geometry. if (primaryRayAtmosphereIntersect == czm_emptyRaySegment) { + rayleighColor = vec3(1.0, 0.0, 1.0); return; } @@ -79,6 +80,8 @@ void czm_computeScattering( vec2 opticalDepth = vec2(0.0); vec2 heightScale = vec2(czm_atmosphereRayleighScaleHeight, czm_atmosphereMieScaleHeight); + //vec3 lastVals = vec3(0.0); + // Sample positions on the primary ray. for (int i = 0; i < PRIMARY_STEPS_MAX; ++i) { @@ -127,11 +130,16 @@ void czm_computeScattering( // Increment distance on light ray. lightPositionLength += lightStepLength; + + //lastVals = vec3(length(lightPosition)); + //lastVals = vec3(float(lightHeight < 0.0), lightHeight / 1000.0, lightOpticalDepth); } // Compute attenuation via the primary ray and the light ray. vec3 attenuation = exp(-((czm_atmosphereMieCoefficient * (opticalDepth.y + lightOpticalDepth.y)) + (czm_atmosphereRayleighCoefficient * (opticalDepth.x + lightOpticalDepth.x)))); + //lastAttenuation = vec3(rayStepLength, lightStepLength); + // Accumulate the scattering. rayleighAccumulation += sampleDensity.x * attenuation; mieAccumulation += sampleDensity.y * attenuation; @@ -146,4 +154,8 @@ void czm_computeScattering( // Compute the transmittance i.e. how much light is passing through the atmosphere. opacity = length(exp(-((czm_atmosphereMieCoefficient * opticalDepth.y) + (czm_atmosphereRayleighCoefficient * opticalDepth.x)))); + + //rayleighColor = vec3(atmosphereInnerRadius / 1.0e7, lastVals.x / 1.0e7, 0.0); //lastVals; + //vec3(float(PRIMARY_STEPS) / 16.0, float(LIGHT_STEPS) / 4.0, 0.0);//mieAccumulation; //rayleighAccumulation; + //rayleighColor = w_stop_gt_lprl /*w_inside_atmosphere*/ * vec3(1.0, 1.0, 0.0); //w_inside_atmosphere, 0.0); } diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 3d181e60ee0c..79d5960159ee 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -523,6 +523,8 @@ void main() finalColor.rgb = mix(finalColor.rgb, finalAtmosphereColor.rgb, fade); #endif + + //finalColor.rgb = computeEllipsoidPosition() / 1e7; } #endif diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index c32c27f4ecd4..dd2da6c7d767 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -6,7 +6,7 @@ vec3 computeFogColor() { // vec3 atmosphereLightDirection = czm_lightDirectionWC; //#endif - vec3 rayleighColor; + vec3 rayleighColor = vec3(0.0, 0.0, 1.0); vec3 mieColor; float opacity; @@ -14,7 +14,7 @@ vec3 computeFogColor() { vec3 lightDirection; // This is true when dynamic lighting is enabled in the scene. - bool dynamicLighting = false; + bool dynamicLighting = true; positionWC = czm_computeEllipsoidPosition(); lightDirection = czm_branchFreeTernary(dynamicLighting, atmosphereLightDirection, normalize(positionWC)); @@ -28,18 +28,33 @@ vec3 computeFogColor() { opacity ); + //rayleighColor = vec3(1.0, 0.0, 0.0); + //mieColor = vec3(0.0, 1.0, 0.0); + vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); vec3 fogColor = groundAtmosphereColor.rgb; // Darken the fog + // If there is lighting, apply that to the fog. +//#if defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING)) + //const float u_minimumBrightness = 0.03; // TODO: pull this from the light shader + //float darken = clamp(dot(normalize(czm_viewerPositionWC), atmosphereLightDirection), u_minimumBrightness, 1.0); + //fogColor *= darken; +//#endif + // Tonemap if HDR rendering is disabled #ifndef HDR fogColor.rgb = czm_acesTonemapping(fogColor.rgb); fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif + // TODO: fogColor.a is only used for ground atmosphere... is that needed? + + //return positionWC / 1e7; + //return rayleighColor; return fogColor.rgb; + //return mieColor; } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { @@ -56,4 +71,5 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { vec3 withFog = czm_fog(distanceToCamera, color.rgb, fogColor, fogModifier); color = vec4(withFog, color.a); + //color = mix(color, vec4(fogColor, 1.0), 0.5); } From 6b7be7f9c51cf078c1f1970bc9247b95198b8068 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 20 Dec 2023 15:54:06 -0500 Subject: [PATCH 08/55] Use scene.fog to choose when fog stage is enabled --- packages/engine/Source/Scene/FrameState.js | 4 +++- .../Source/Scene/Model/FogPipelineStage.js | 15 ++++++++------- packages/engine/Source/Scene/Model/Model.js | 17 ++++++++++++++--- .../Source/Scene/Model/ModelRuntimePrimitive.js | 6 +++++- .../engine/Source/Shaders/Model/ModelFS.glsl | 2 ++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 31172ab4b696..98e4121f20a0 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -255,7 +255,8 @@ function FrameState(context, creditDisplay, jobScheduler) { /** * @typedef FrameState.Fog * @type {object} - * @property {boolean} enabled true if fog is enabled, false otherwise. + * @property {boolean} enabled true if fog is enabled, false otherwise. This affects both fog culling and rendering. + * @property {boolean} renderable true if fog should be rendered, false if not. This flag should be checked in combination with fog.enabled. * @property {number} density A positive number used to mix the color and fog color based on camera distance. * @property {number} sse A scalar used to modify the screen space error of geometry partially in fog. * @property {number} minimumBrightness The minimum brightness of terrain with fog applied. @@ -270,6 +271,7 @@ function FrameState(context, creditDisplay, jobScheduler) { * @default false */ enabled: false, + renderable: false, density: undefined, sse: undefined, minimumBrightness: undefined, diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index c9d902e74b76..b23cb4654ae9 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -1,4 +1,5 @@ import FogStageFS from "../../Shaders/Model/FogStageFS.js"; +import ShaderDestination from "../../Renderer/ShaderDestination.js"; /** * The fog color pipeline stage is responsible for applying fog to tiles in the distance in horizon views. @@ -7,15 +8,15 @@ import FogStageFS from "../../Shaders/Model/FogStageFS.js"; * * @private */ -const FogColorPipelineStage = { +const FogPipelineStage = { name: "FogColorPipelineStage", // Helps with debugging }; -FogColorPipelineStage.process = function (renderResources, model, frameState) { - // TODO: AtmosphereCommon.glsl includes uniforms that really should be - // added separately to match the Model pipeline paradigm... Maybe that file could - // be split into multiple files. - renderResources.shaderBuilder.addFragmentLines([FogStageFS]); +FogPipelineStage.process = function (renderResources, model, frameState) { + const shaderBuilder = renderResources.shaderBuilder; + + shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.FRAGMENT); + shaderBuilder.addFragmentLines([FogStageFS]); }; -export default FogColorPipelineStage; +export default FogPipelineStage; diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 57c174ce725e..9659e97b45ef 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -112,7 +112,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; * @internalConstructor * * @privateParam {ResourceLoader} options.loader The loader used to load resources for this model. - * @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally. + * @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally. * @privateParam {object} options Object with the following properties: * @privateParam {Resource} options.resource The Resource to the 3D model. * @privateParam {boolean} [options.show=true] Whether or not to render the model. @@ -154,7 +154,7 @@ import StyleCommandsNeeded from "./StyleCommandsNeeded.js"; * @privateParam {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @privateParam {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. * @privateParam {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this model. This cannot be set after the model has loaded. - + * * @see Model.fromGltfAsync * @@ -192,7 +192,7 @@ function Model(options) { * When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's Cartesian WGS84 coordinates. * Local reference frames can be used by providing a different transformation matrix, like that returned * by {@link Transforms.eastNorthUpToFixedFrame}. - * + * * @type {Matrix4} * @default {@link Matrix4.IDENTITY} @@ -454,6 +454,8 @@ function Model(options) { this._sceneMode = undefined; this._projectTo2D = defaultValue(options.projectTo2D, false); + this._fogRenderable = undefined; + this._skipLevelOfDetail = false; this._ignoreCommands = defaultValue(options.ignoreCommands, false); @@ -1789,6 +1791,7 @@ Model.prototype.update = function (frameState) { updateSkipLevelOfDetail(this, frameState); updateClippingPlanes(this, frameState); updateSceneMode(this, frameState); + updateFog(this, frameState); this._defaultTexture = frameState.context.defaultTexture; @@ -1983,6 +1986,14 @@ function updateSceneMode(model, frameState) { } } +function updateFog(model, frameState) { + const fogRenderable = frameState.fog.enabled && frameState.fog.renderable; + if (fogRenderable !== model._fogRenderable) { + model.resetDrawCommands(); + model._fogRenderable = fogRenderable; + } +} + function buildDrawCommands(model, frameState) { if (!model._drawCommandsBuilt) { model.destroyPipelineResources(); diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index 824c22153be0..ac9529427fae 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -199,6 +199,7 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { const mode = frameState.mode; const use2D = mode !== SceneMode.SCENE3D && !frameState.scene3DOnly && model._projectTo2D; + const fogRenderable = frameState.fog.enabled && frameState.fog.renderable; const hasMorphTargets = defined(primitive.morphTargets) && primitive.morphTargets.length > 0; @@ -297,7 +298,10 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { } pipelineStages.push(AlphaPipelineStage); - pipelineStages.push(FogPipelineStage); + + if (fogRenderable) { + pipelineStages.push(FogPipelineStage); + } pipelineStages.push(PrimitiveStatisticsPipelineStage); diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 565013f1bc63..534c7f2e78f2 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -79,7 +79,9 @@ void main() silhouetteStage(color); #endif + #ifdef HAS_FOG fogStage(color, attributes); + #endif out_FragColor = color; } From 3027987ab0e1738d8e173a48819147dffa8aafd5 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 20 Dec 2023 16:53:35 -0500 Subject: [PATCH 09/55] Add a DynamicAtmosphereLightingType enum --- .../Source/Renderer/AutomaticUniforms.js | 13 +++++++ .../engine/Source/Renderer/UniformState.js | 30 +++++++++++----- packages/engine/Source/Scene/Atmosphere.js | 16 +++++++++ .../Scene/DynamicAtmosphereLightingType.js | 34 +++++++++++++++++++ packages/engine/Source/Scene/FrameState.js | 2 ++ .../getDynamicAtmosphereLightDirection.glsl | 23 +++++++++++++ .../Source/Shaders/Model/FogStageFS.glsl | 17 ++-------- 7 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 packages/engine/Source/Scene/DynamicAtmosphereLightingType.js create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 1662bf472ee3..9158ca90963f 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1629,6 +1629,19 @@ const AutomaticUniforms = { return uniformState.atmosphereMieAnisotropy; }, }), + /** + * An automatic uniform representing which light source to use for dynamic lighting + * + * @example + * uniform float czm_atmosphereDynamicLighting + */ + czm_atmosphereDynamicLighting: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereDynamicLighting; + }, + }), /** * An automatic GLSL uniform representing the splitter position to use when rendering with a splitter. diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index b2f5ad412c9e..aa50ac141a78 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -165,6 +165,7 @@ function UniformState() { this._atmosphereMieCoefficient = new Cartesian3(); this._atmosphereMieScaleHeight = undefined; this._atmosphereMieAnisotropy = undefined; + this._atmosphereDynamicLighting = undefined; this._invertClassificationColor = undefined; @@ -943,6 +944,17 @@ Object.defineProperties(UniformState.prototype, { return this._atmosphereMieAnisotropy; }, }, + /** + * Which light source to use for dynamically lighting the atmosphere + * + * @memberof UniformState.prototype + * @type {DynamicAtmosphereLightingType} + */ + atmosphereDynamicLighting: { + get: function () { + return this._atmosphereDynamicLighting; + }, + }, /** * A scalar that represents the geometric tolerance per meter @@ -1373,23 +1385,25 @@ UniformState.prototype.update = function (frameState) { this._fogDensity = frameState.fog.density; + const atmosphere = frameState.atmosphere; + this._atmosphereHsbShift = Cartesian3.clone( - frameState.atmosphere.hsbShift, + atmosphere.hsbShift, this._atmosphereHsbShift ); - this._atmosphereLightIntensity = frameState.atmosphere.lightIntensity; + this._atmosphereLightIntensity = atmosphere.lightIntensity; this._atmosphereRayleighCoefficient = Cartesian3.clone( - frameState.atmosphere.rayleighCoefficient, + atmosphere.rayleighCoefficient, this._atmosphereRayleighCoefficient ); - this._atmosphereRayleighScaleHeight = - frameState.atmosphere.rayleighScaleHeight; + this._atmosphereRayleighScaleHeight = atmosphere.rayleighScaleHeight; this._atmosphereMieCoefficient = Cartesian3.clone( - frameState.atmosphere.mieCoefficient, + atmosphere.mieCoefficient, this._atmosphereMieCoefficient ); - this._atmosphereMieScaleHeight = frameState.atmosphere.mieScaleHeight; - this._atmosphereMieAnisotropy = frameState.atmosphere.mieAnisotropy; + this._atmosphereMieScaleHeight = atmosphere.mieScaleHeight; + this._atmosphereMieAnisotropy = atmosphere.mieAnisotropy; + this._atmosphereDynamicLighting = atmosphere.dynamicLighting; this._invertClassificationColor = frameState.invertClassificationColor; diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index 010904e97961..d5b2c4321547 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -1,4 +1,5 @@ import Cartesian3 from "../Core/Cartesian3"; +import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType"; function Atmosphere() { /** @@ -46,6 +47,7 @@ function Atmosphere() { *

* Valid values are between -1.0 and 1.0. *

+ * * @type {number} * @default 0.9 */ @@ -54,6 +56,7 @@ function Atmosphere() { /** * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A hue shift of 1.0 indicates a complete rotation of the hues available. + * * @type {number} * @default 0.0 */ @@ -62,6 +65,7 @@ function Atmosphere() { /** * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A saturation shift of -1.0 is monochrome. + * * @type {number} * @default 0.0 */ @@ -70,10 +74,20 @@ function Atmosphere() { /** * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift). * A brightness shift of -1.0 is complete darkness, which will let space show through. + * * @type {number} * @default 0.0 */ this.brightnessShift = 0.0; + + /** + * When not DynamicAtmosphereLightingType.OFF, the selected light source will + * be used for dynamically lighting all atmosphere-related rendering effects. + * + * @type {DynamicAtmosphereLightingType} + * @default DynamicAtmosphereLightingType.OFF + */ + this.dynamicLighting = DynamicAtmosphereLightingType.OFF; } Atmosphere.prototype.update = function (frameState) { @@ -93,6 +107,8 @@ Atmosphere.prototype.update = function (frameState) { ); atmosphere.mieScaleHeight = this.mieScaleHeight; atmosphere.mieAnisotropy = this.mieAnisotropy; + + atmosphere.dynamicLighting = this.dynamicLighting; }; export default Atmosphere; diff --git a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js new file mode 100644 index 000000000000..5078ac2b861d --- /dev/null +++ b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js @@ -0,0 +1,34 @@ +/** + * Atmosphere lighting effects (sky atmosphere, ground atmosphere, fog) can be + * further modified with dynamic lighting from the sun or other light source + * that changes over time. This enum determines which light source to use. + * + * @enum {number} + */ +const DynamicAtmosphereLightingType = { + /** + * Do not use dynamic atmosphere lighting. Anything that uses atmosphere + * lighting will be lit from directly above the vertex/fragment + * + * @type {number} + * @constant + */ + OFF: 0, + /** + * Use the scene's current light source for dynamic atmosphere lighting. + * + * @type {number} + * @constant + */ + SCENE_LIGHT: 1, + /** + * Force the dynamic atmosphere lighting to always use the sunlight direction, + * even if the scene uses a different light source. + * + * @type {number} + * @constant + */ + SUNLIGHT: 2, +}; + +export default Object.freeze(DynamicAtmosphereLightingType); diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 98e4121f20a0..566935667132 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -287,6 +287,7 @@ function FrameState(context, creditDisplay, jobScheduler) { * @property {Cartesian3} mieCoefficient The Mie scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. * @property {number} mieScaleHeight The Mie scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. * @property {number} mieAnisotropy The anisotropy of the medium to consider for Mie scattering. + * @property {DynamicAtmosphereLightingType} dynamicLighting An enum value determining what light source to use for dynamic lighting the atmosphere (if enabled) */ /** @@ -300,6 +301,7 @@ function FrameState(context, creditDisplay, jobScheduler) { mieCoefficient: new Cartesian3(), mieScaleHeight: undefined, mieAnisotropy: undefined, + dynamicLighting: undefined, }; /** diff --git a/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl new file mode 100644 index 000000000000..2dd991d28026 --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl @@ -0,0 +1,23 @@ +/** + * Select which direction vector to use for dynamic atmosphere lighting based on the czm_atmosphereDynamicLighting enum. + * + * @name czm_getDynamicAtmosphereLightDirection + * @glslfunction + * @see DynamicAtmosphereLightingType.js + * + * @param {vec3} positionWC the position of the vertex/fragment in world coordinates. This is normalized and returned when dynamic lighting is turned off. + * @return {vec3} The normalized light direction vector. Depending on the enum value, it is either positionWC, czm_lightDirectionWC or czm_sunDirectionWC + */ +vec3 czm_getDynamicAtmosphereLightDirection(vec3 positionWC) { + float lightEnum = czm_atmosphereDynamicLighting; + + const float OFF = 0.0; + const float SCENE_LIGHT = 1.0; + const float SUNLIGHT = 2.0; + + vec3 lightDirection = + positionWC * float(lightEnum == OFF) + + czm_lightDirectionWC * float(lightEnum == SCENE_LIGHT) + + czm_sunDirectionWC * float(lightEnum == SUNLIGHT); + return normalize(lightDirection); +} diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index dd2da6c7d767..4fd92a6e673c 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,23 +1,10 @@ vec3 computeFogColor() { - -//#if defined(DYNAMIC_ATMOSPHERE_LIGHTING_FROM_SUN) - vec3 atmosphereLightDirection = czm_sunDirectionWC; -//#else -// vec3 atmosphereLightDirection = czm_lightDirectionWC; -//#endif - vec3 rayleighColor = vec3(0.0, 0.0, 1.0); vec3 mieColor; float opacity; - vec3 positionWC; - vec3 lightDirection; - - // This is true when dynamic lighting is enabled in the scene. - bool dynamicLighting = true; - - positionWC = czm_computeEllipsoidPosition(); - lightDirection = czm_branchFreeTernary(dynamicLighting, atmosphereLightDirection, normalize(positionWC)); + vec3 positionWC = czm_computeEllipsoidPosition(); + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC); // The fog color is derived from the ground atmosphere color czm_computeGroundAtmosphereScattering( From a1cb8e13f229654bdf10e7d8f89241f0de87f1c8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 15:40:35 -0500 Subject: [PATCH 10/55] Document curvature directions in more detail --- packages/engine/Source/Renderer/AutomaticUniforms.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 46879d0d7581..4d6beab2cf9b 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -954,7 +954,8 @@ const AutomaticUniforms = { /** * An automatic GLSL uniform containing the ellipsoid radii of curvature at the camera position. - * The .x component is the prime vertical radius, .y is the meridional. + * The .x component is the prime vertical radius of curvature (east-west direction) + * .y is the meridional radius of curvature (north-south direction) * This uniform is only valid when the {@link SceneMode} is SCENE3D. */ czm_eyeEllipsoidCurvature: new AutomaticUniform({ From 9fb41108d6f19146eca38379ac2c21c95ac62ff8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 4 Jan 2024 16:58:58 -0500 Subject: [PATCH 11/55] Use a different method to compute the world position --- .../Functions/computeEllipsoidPosition.glsl | 8 ++++ .../Source/Shaders/Model/FogStageFS.glsl | 38 +++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl index f11eea6caa73..ddeeee0df3a8 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl @@ -4,6 +4,8 @@ * * @return {vec3} The position in world coordinates. */ + + /* vec3 czm_computeEllipsoidPosition() { float mpp = czm_metersPerPixel(vec4(0.0, 0.0, -czm_currentFrustum.x, 1.0), 1.0); @@ -20,3 +22,9 @@ vec3 czm_computeEllipsoidPosition() vec3 ellipsoidPosition = czm_pointAlongRay(ray, intersection.start); return (czm_inverseView * vec4(ellipsoidPosition, 1.0)).xyz; } +*/ + +vec3 czm_computeEllipsoidPosition() +{ + return vec3(0.0); +} diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 4fd92a6e673c..439551746d72 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,9 +1,41 @@ -vec3 computeFogColor() { +vec3 computeEllipsoidPosition(vec3 positionMC) { + // Compute the distance from the camera to the local center of curvature. + vec4 vertexPositionENU = czm_modelToEnu * vec4(positionMC, 1.0); + vec2 vertexAzimuth = normalize(vertexPositionENU.xy); + // Curvature = 1 / radius of curvature. + float azimuthalCurvature = dot(vertexAzimuth * vertexAzimuth, czm_eyeEllipsoidCurvature); + float eyeToCenter = 1.0 / azimuthalCurvature + czm_eyeHeight; + + // Compute the approximate ellipsoid normal at the vertex position. + // Uses a circular approximation for the Earth curvature along the geodesic. + vec3 vertexPositionEC = (czm_modelView * vec4(positionMC, 1.0)).xyz; + vec3 centerToVertex = eyeToCenter * czm_eyeEllipsoidNormalEC + vertexPositionEC; + vec3 vertexNormal = normalize(centerToVertex); + + // Estimate the (sine of the) angle between the camera direction and the vertex normal + float verticalDistance = dot(vertexPositionEC, czm_eyeEllipsoidNormalEC); + float horizontalDistance = length(vertexPositionEC - verticalDistance * czm_eyeEllipsoidNormalEC); + float sinTheta = horizontalDistance / (eyeToCenter + verticalDistance); + bool isSmallAngle = clamp(sinTheta, 0.0, 0.05) == sinTheta; + + // Approximate the change in height above the ellipsoid, from camera to vertex position. + float exactVersine = 1.0 - dot(czm_eyeEllipsoidNormalEC, vertexNormal); + float smallAngleVersine = 0.5 * sinTheta * sinTheta; + float versine = isSmallAngle ? smallAngleVersine : exactVersine; + float dHeight = dot(vertexPositionEC, vertexNormal) - eyeToCenter * versine; + float vertexHeight = czm_eyeHeight + dHeight; + + vec3 ellipsoidPositionEC = vertexPositionEC - vertexHeight * vertexNormal; + return (czm_inverseView * vec4(ellipsoidPositionEC, 1.0)).xyz; + +} + +vec3 computeFogColor(vec3 positionMC) { vec3 rayleighColor = vec3(0.0, 0.0, 1.0); vec3 mieColor; float opacity; - vec3 positionWC = czm_computeEllipsoidPosition(); + vec3 positionWC = computeEllipsoidPosition(positionMC); vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC); // The fog color is derived from the ground atmosphere color @@ -45,7 +77,7 @@ vec3 computeFogColor() { } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { - vec3 fogColor = computeFogColor(); + vec3 fogColor = computeFogColor(attributes.positionMC); // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. // otherwise in the vertex shader. but for prototyping, I'll do everything in the FS for simplicity From bc781be41745bc1742b66304bd5241483f89ece2 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Fri, 5 Jan 2024 13:23:15 -0500 Subject: [PATCH 12/55] Add unit test for Atmosphere --- packages/engine/Specs/Scene/AtmosphereSpec.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/engine/Specs/Scene/AtmosphereSpec.js diff --git a/packages/engine/Specs/Scene/AtmosphereSpec.js b/packages/engine/Specs/Scene/AtmosphereSpec.js new file mode 100644 index 000000000000..9758be52837c --- /dev/null +++ b/packages/engine/Specs/Scene/AtmosphereSpec.js @@ -0,0 +1,69 @@ +import { Cartesian3, DynamicAtmosphereLightingType } from "../../index.js"; + +import createScene from "../../../../Specs/createScene"; + +describe("scene/Atmosphere", function () { + let scene; + beforeEach(function () { + scene = createScene(); + }); + + afterEach(function () { + scene.destroyForSpecs(); + }); + + it("updates frameState each frame", function () { + const atmosphere = scene.atmosphere; + const frameStateAtmosphere = scene.frameState.atmosphere; + + // Render and check that scene.atmosphere updated + // frameState.atmosphere. For the first frame this should + // be the default settings. + scene.renderForSpecs(); + expect(frameStateAtmosphere.hsbShift).toEqual(new Cartesian3()); + expect(frameStateAtmosphere.lightIntensity).toEqual(10.0); + expect(frameStateAtmosphere.rayleighCoefficient).toEqual( + new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) + ); + expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(10000.0); + expect(frameStateAtmosphere.mieCoefficient).toEqual( + new Cartesian3(21e-6, 21e-6, 21e-6) + ); + expect(frameStateAtmosphere.mieScaleHeight).toEqual(3200.0); + expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.9); + expect(frameStateAtmosphere.dynamicLighting).toEqual( + DynamicAtmosphereLightingType.OFF + ); + + // Now change the settings, render again and check that + // the frame state was updated. + atmosphere.hueShift = 0.5; + atmosphere.saturationShift = -0.5; + atmosphere.brightnessShift = 0.25; + atmosphere.lightIntensity = 5.0; + atmosphere.rayleighCoefficient = new Cartesian3(1.0, 1.0, 1.0); + atmosphere.rayleighScaleHeight = 1000; + atmosphere.mieCoefficient = new Cartesian3(2.0, 2.0, 2.0); + atmosphere.mieScaleHeight = 100; + atmosphere.mieAnisotropy = 0.5; + atmosphere.dynamicLighting = DynamicAtmosphereLightingType.SUNLIGHT; + + scene.renderForSpecs(); + expect(frameStateAtmosphere.hsbShift).toEqual( + new Cartesian3(0.5, -0.5, 0.25) + ); + expect(frameStateAtmosphere.lightIntensity).toEqual(5.0); + expect(frameStateAtmosphere.rayleighCoefficient).toEqual( + new Cartesian3(1.0, 1.0, 1.0) + ); + expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(1000); + expect(frameStateAtmosphere.mieCoefficient).toEqual( + new Cartesian3(2.0, 2.0, 2.0) + ); + expect(frameStateAtmosphere.mieScaleHeight).toEqual(100.0); + expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.5); + expect(frameStateAtmosphere.dynamicLighting).toEqual( + DynamicAtmosphereLightingType.SUNLIGHT + ); + }); +}); From 990b81560024d4f3ce6b51c74cf1722224a0a1f5 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Fri, 5 Jan 2024 14:15:22 -0500 Subject: [PATCH 13/55] Add unit tests for automatic uniforms --- .../Specs/Renderer/AutomaticUniformSpec.js | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js index 55319d78300b..349687d5a1ba 100644 --- a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js +++ b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js @@ -1,11 +1,14 @@ import { + Atmosphere, Cartesian2, Cartesian3, Cartographic, Color, defaultValue, DirectionalLight, + DynamicAtmosphereLightingType, Ellipsoid, + Fog, GeographicProjection, Matrix4, OrthographicFrustum, @@ -1783,6 +1786,191 @@ describe( }).contextToRender(); }); + it("has czm_fogDensity", function () { + const frameState = createFrameState( + context, + createMockCamera( + undefined, + undefined, + undefined, + // Explicit position and direction as the default position of (0, 0, 0) + // will lead to a divide by zero when updating fog below. + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0) + ) + ); + const fog = new Fog(); + fog.density = 0.1; + fog.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_fogDensity != 0.0);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereHsbShift", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.hueShift = 1.0; + atmosphere.saturationShift = 2.0; + atmosphere.brightnessShift = 3.0; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereHsbShift == vec3(1.0, 2.0, 3.0));" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereLightIntensity", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.lightIntensity = 2.0; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereLightIntensity == 2.0);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereRayleighCoefficient", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.rayleighCoefficient = new Cartesian3(1.0, 2.0, 3.0); + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereRayleighCoefficient == vec3(1.0, 2.0, 3.0));" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereRayleighScaleHeight", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.rayleighScaleHeight = 100.0; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereRayleighScaleHeight == 100.0);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereMieCoefficient", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.mieCoefficient = new Cartesian3(1.0, 2.0, 3.0); + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereMieCoefficient == vec3(1.0, 2.0, 3.0));" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereMieScaleHeight", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.mieScaleHeight = 100.0; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereMieScaleHeight == 100.0);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereMieAnisotropy", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + atmosphere.mieAnisotropy = 100.0; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_atmosphereMieAnisotropy == 100.0);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + + it("has czm_atmosphereDynamicLighting", function () { + const frameState = createFrameState(context, createMockCamera()); + const atmosphere = new Atmosphere(); + const enumValue = DynamicAtmosphereLightingType.SCENE_LIGHT; + atmosphere.dynamicLighting = enumValue; + atmosphere.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + ` out_FragColor = vec4(czm_atmosphereDynamicLighting == float(${enumValue}));` + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + it("has czm_pass and czm_passEnvironment", function () { const us = context.uniformState; us.updatePass(Pass.ENVIRONMENT); From c4e1b373f57d9863880fb29cc490962cd075d34c Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Fri, 5 Jan 2024 16:43:14 -0500 Subject: [PATCH 14/55] Fix and update unit tests for ModelRuntimePrimitive --- .../Scene/Model/ModelRuntimePrimitiveSpec.js | 165 ++++++++++++------ 1 file changed, 116 insertions(+), 49 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js index dff8c900829f..8dd9ddb669b3 100644 --- a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js @@ -2,10 +2,12 @@ import { AlphaPipelineStage, BatchTexturePipelineStage, Cesium3DTileStyle, + ClassificationPipelineStage, CustomShader, CustomShaderMode, CustomShaderPipelineStage, FeatureIdPipelineStage, + FogPipelineStage, CPUStylingPipelineStage, DequantizationPipelineStage, GeometryPipelineStage, @@ -30,7 +32,8 @@ import { WireframePipelineStage, ClassificationType, } from "../../../index.js"; -import ClassificationPipelineStage from "../../../Source/Scene/Model/ClassificationPipelineStage.js"; + +import createFrameState from "../../../../../Specs/createFrameState.js"; describe("Scene/Model/ModelRuntimePrimitive", function () { const mockPrimitive = { @@ -43,33 +46,21 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { allowPicking: true, featureIdLabel: "featureId_0", }; - const mockFrameState = { - context: { - webgl2: false, - }, - mode: SceneMode.SCENE3D, + const mockWebgl1Context = { + webgl2: false, }; - - const mockFrameStateWebgl2 = { - context: { - webgl2: true, - }, + const mockWebgl2Context = { + webgl2: true, }; - const mockFrameState2D = { - context: { - webgl2: false, - }, - mode: SceneMode.SCENE2D, - }; + const mockFrameState = createFrameState(mockWebgl1Context); + const mockFrameStateWebgl2 = createFrameState(mockWebgl2Context); - const mockFrameState3DOnly = { - context: { - webgl2: false, - }, - mode: SceneMode.SCENE3D, - scene3DOnly: true, - }; + const mockFrameState2D = createFrameState(mockWebgl1Context); + mockFrameState2D.mode = SceneMode.SCENE2D; + + const mockFrameState3DOnly = createFrameState(mockWebgl1Context); + mockFrameState3DOnly.scene3DOnly = true; const emptyVertexShader = "void vertexMain(VertexInput vsInput, inout vec3 position) {}"; @@ -160,7 +151,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -203,7 +193,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -250,7 +239,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { SelectedFeatureIdPipelineStage, BatchTexturePipelineStage, CPUStylingPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -307,7 +295,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, PickingPipelineStage, AlphaPipelineStage, @@ -335,7 +322,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -366,7 +352,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { GeometryPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -397,7 +382,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, CustomShaderPipelineStage, LightingPipelineStage, AlphaPipelineStage, @@ -438,7 +422,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -473,7 +456,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -507,7 +489,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -538,7 +519,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -570,7 +550,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -600,7 +579,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -633,7 +611,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -675,7 +652,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -708,7 +684,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -740,7 +715,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -772,7 +746,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -803,7 +776,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -835,7 +807,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -866,7 +837,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -897,7 +867,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -929,7 +898,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, PrimitiveOutlinePipelineStage, AlphaPipelineStage, @@ -962,7 +930,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -993,7 +960,6 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { MaterialPipelineStage, FeatureIdPipelineStage, MetadataPipelineStage, - VerticalExaggerationPipelineStage, LightingPipelineStage, AlphaPipelineStage, PrimitiveStatisticsPipelineStage, @@ -1002,4 +968,105 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { primitive.configurePipeline(mockFrameState); verifyExpectedStages(primitive.pipelineStages, expectedStages); }); + + it("configures pipeline stages for vertical exaggeration", function () { + const primitive = new ModelRuntimePrimitive({ + primitive: mockPrimitive, + node: mockNode, + model: mockModel, + }); + const frameState = createFrameState(mockWebgl2Context); + frameState.verticalExaggeration = 2.0; + + const expectedStages = [ + GeometryPipelineStage, + MaterialPipelineStage, + FeatureIdPipelineStage, + MetadataPipelineStage, + VerticalExaggerationPipelineStage, + LightingPipelineStage, + PickingPipelineStage, + AlphaPipelineStage, + PrimitiveStatisticsPipelineStage, + ]; + + primitive.configurePipeline(frameState); + verifyExpectedStages(primitive.pipelineStages, expectedStages); + }); + + it("does not add fog stage when fog is not enabled", function () { + const primitive = new ModelRuntimePrimitive({ + primitive: mockPrimitive, + node: mockNode, + model: mockModel, + }); + const frameState = createFrameState(mockWebgl2Context); + frameState.fog.enabled = false; + frameState.fog.renderable = false; + + const expectedStages = [ + GeometryPipelineStage, + MaterialPipelineStage, + FeatureIdPipelineStage, + MetadataPipelineStage, + LightingPipelineStage, + PickingPipelineStage, + AlphaPipelineStage, + PrimitiveStatisticsPipelineStage, + ]; + + primitive.configurePipeline(frameState); + verifyExpectedStages(primitive.pipelineStages, expectedStages); + }); + + it("does not add fog stage when fog is not renderable", function () { + const primitive = new ModelRuntimePrimitive({ + primitive: mockPrimitive, + node: mockNode, + model: mockModel, + }); + const frameState = createFrameState(mockWebgl2Context); + frameState.fog.enabled = true; + frameState.fog.renderable = false; + + const expectedStages = [ + GeometryPipelineStage, + MaterialPipelineStage, + FeatureIdPipelineStage, + MetadataPipelineStage, + LightingPipelineStage, + PickingPipelineStage, + AlphaPipelineStage, + PrimitiveStatisticsPipelineStage, + ]; + + primitive.configurePipeline(frameState); + verifyExpectedStages(primitive.pipelineStages, expectedStages); + }); + + it("configures pipeline stages when fog is enabled and renderable", function () { + const primitive = new ModelRuntimePrimitive({ + primitive: mockPrimitive, + node: mockNode, + model: mockModel, + }); + const frameState = createFrameState(mockWebgl2Context); + frameState.fog.enabled = true; + frameState.fog.renderable = true; + + const expectedStages = [ + GeometryPipelineStage, + MaterialPipelineStage, + FeatureIdPipelineStage, + MetadataPipelineStage, + LightingPipelineStage, + PickingPipelineStage, + AlphaPipelineStage, + FogPipelineStage, + PrimitiveStatisticsPipelineStage, + ]; + + primitive.configurePipeline(frameState); + verifyExpectedStages(primitive.pipelineStages, expectedStages); + }); }); From 046160c12ba33abbdd13ae5e9544d575d63ba6e8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 8 Jan 2024 11:29:51 -0500 Subject: [PATCH 15/55] Implement the other method for computing the ellipsoid position --- .../Source/Shaders/Model/FogStageFS.glsl | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 439551746d72..41c30da5a1a7 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,4 +1,4 @@ -vec3 computeEllipsoidPosition(vec3 positionMC) { +vec3 computeEllipsoidPositionCurvature(vec3 positionMC) { // Compute the distance from the camera to the local center of curvature. vec4 vertexPositionENU = czm_modelToEnu * vec4(positionMC, 1.0); vec2 vertexAzimuth = normalize(vertexPositionENU.xy); @@ -30,17 +30,64 @@ vec3 computeEllipsoidPosition(vec3 positionMC) { } +// robust iterative solution without trig functions +// https://github.com/0xfaded/ellipse_demo/issues/1 +// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse +vec2 nearestPointOnEllipse(vec2 pos, vec2 radii) { + vec2 p = abs(pos); + vec2 inverseRadii = 1.0 / radii; + vec2 evoluteScale = (radii.x * radii.x - radii.y * radii.y) * vec2(1.0, -1.0) * inverseRadii; + + // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t)) + // but store the cos and sin of t in a vec2 for efficiency. + // Initial guess: t = cos(pi/4) + vec2 tTrigs = vec2(0.70710678118); + vec2 v = radii * tTrigs; + + const int iterations = 3; + for (int i = 0; i < iterations; ++i) { + // Find the evolute of the ellipse (center of curvature) at v. + vec2 evolute = evoluteScale * tTrigs * tTrigs * tTrigs; + // Find the (approximate) intersection of p - evolute with the ellipsoid. + vec2 q = normalize(p - evolute) * length(v - evolute); + // Update the estimate of t. + tTrigs = (q + evolute) * inverseRadii; + tTrigs = normalize(clamp(tTrigs, 0.0, 1.0)); + v = radii * tTrigs; + } + + return v * sign(pos); +} + +vec3 computeEllipsoidPositionIterative(vec3 positionMC) { + // Get the world-space position and project onto a meridian plane of + // the ellipsoid + vec3 positionWC = (czm_model * vec4(positionMC, 1.0)).xyz; + + vec2 positionEllipse = vec2(length(positionWC.xy), positionWC.z); + vec2 nearestPoint = nearestPointOnEllipse(positionEllipse, czm_ellipsoidRadii.xz); + + // Reconstruct a 3D point in world space + return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); +} + +#define USE_ITERATIVE_METHOD + vec3 computeFogColor(vec3 positionMC) { vec3 rayleighColor = vec3(0.0, 0.0, 1.0); vec3 mieColor; float opacity; - vec3 positionWC = computeEllipsoidPosition(positionMC); - vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC); +#ifdef USE_ITERATIVE_METHOD + vec3 ellipsoidPositionWC = computeEllipsoidPositionIterative(positionMC); +#else + vec3 ellipsoidPositionWC = computeEllipsoidPositionCurvature(positionMC); +#endif + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC); // The fog color is derived from the ground atmosphere color czm_computeGroundAtmosphereScattering( - positionWC, + ellipsoidPositionWC, lightDirection, rayleighColor, mieColor, @@ -50,7 +97,7 @@ vec3 computeFogColor(vec3 positionMC) { //rayleighColor = vec3(1.0, 0.0, 0.0); //mieColor = vec3(0.0, 1.0, 0.0); - vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); + vec4 groundAtmosphereColor = czm_computeAtmosphereColor(ellipsoidPositionWC, lightDirection, rayleighColor, mieColor, opacity); vec3 fogColor = groundAtmosphereColor.rgb; // Darken the fog From 4255132a95901783fa0b27db189b0aa2bb126131 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 8 Jan 2024 16:13:26 -0500 Subject: [PATCH 16/55] Experiment with different ellipsoid calculations --- .../Source/Renderer/AutomaticUniforms.js | 8 +++ .../engine/Source/Renderer/UniformState.js | 9 +++ packages/engine/Source/Scene/Atmosphere.js | 8 +++ packages/engine/Source/Scene/FrameState.js | 2 + .../Source/Shaders/Model/FogStageFS.glsl | 69 +++++++++++++++++-- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 4d6beab2cf9b..604f4c29915c 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1697,6 +1697,14 @@ const AutomaticUniforms = { }, }), + czm_atmosphereMethod: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.atmosphereMethod; + }, + }), + /** * An automatic GLSL uniform representing the splitter position to use when rendering with a splitter. * This will be in pixel coordinates relative to the canvas. diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 75bfbde2d316..5eb6cca9f2f9 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -171,6 +171,8 @@ function UniformState() { this._atmosphereMieAnisotropy = undefined; this._atmosphereDynamicLighting = undefined; + this._atmosphereMethod = undefined; + this._invertClassificationColor = undefined; this._splitPosition = 0.0; @@ -1006,6 +1008,12 @@ Object.defineProperties(UniformState.prototype, { }, }, + atmosphereMethod: { + get: function () { + return this._atmosphereMethod; + }, + }, + /** * A scalar that represents the geometric tolerance per meter * @memberof UniformState.prototype @@ -1522,6 +1530,7 @@ UniformState.prototype.update = function (frameState) { this._atmosphereMieScaleHeight = atmosphere.mieScaleHeight; this._atmosphereMieAnisotropy = atmosphere.mieAnisotropy; this._atmosphereDynamicLighting = atmosphere.dynamicLighting; + this._atmosphereMethod = atmosphere.method; this._invertClassificationColor = frameState.invertClassificationColor; diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index d5b2c4321547..1452494f3eda 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -88,6 +88,12 @@ function Atmosphere() { * @default DynamicAtmosphereLightingType.OFF */ this.dynamicLighting = DynamicAtmosphereLightingType.OFF; + + // TEMPORARY! Just for side-by-side-comparisons for the PR + // 0 = positionWC = czm_model * positionMC + // 1 = positionWC = computeEllipsoidPositionCurvature(positionMC) + // 2 = positionWC = computeEllipsoidPositionIterative(positionMC) + this.method = 1; } Atmosphere.prototype.update = function (frameState) { @@ -109,6 +115,8 @@ Atmosphere.prototype.update = function (frameState) { atmosphere.mieAnisotropy = this.mieAnisotropy; atmosphere.dynamicLighting = this.dynamicLighting; + + atmosphere.method = this.method; }; export default Atmosphere; diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 0ad600f948ba..81b10aa18e5d 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -302,6 +302,8 @@ function FrameState(context, creditDisplay, jobScheduler) { mieScaleHeight: undefined, mieAnisotropy: undefined, dynamicLighting: undefined, + + method: 1, }; /** diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 41c30da5a1a7..02fd31bffde4 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -59,6 +59,32 @@ vec2 nearestPointOnEllipse(vec2 pos, vec2 radii) { return v * sign(pos); } +vec2 nearestPointOnEllipse1Iter(vec2 pos, vec2 radii) { + vec2 p = abs(pos); + vec2 inverseRadii = 1.0 / radii; + vec2 evoluteScale = (radii.x * radii.x - radii.y * radii.y) * vec2(1.0, -1.0) * inverseRadii; + + // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t)) + // but store the cos and sin of t in a vec2 for efficiency. + // Initial guess: t = cos(pi/4) + vec2 tTrigs = vec2(0.70710678118); + vec2 v = radii * tTrigs; + + const int iterations = 1; + for (int i = 0; i < iterations; ++i) { + // Find the evolute of the ellipse (center of curvature) at v. + vec2 evolute = evoluteScale * tTrigs * tTrigs * tTrigs; + // Find the (approximate) intersection of p - evolute with the ellipsoid. + vec2 q = normalize(p - evolute) * length(v - evolute); + // Update the estimate of t. + tTrigs = (q + evolute) * inverseRadii; + tTrigs = normalize(clamp(tTrigs, 0.0, 1.0)); + v = radii * tTrigs; + } + + return v * sign(pos); +} + vec3 computeEllipsoidPositionIterative(vec3 positionMC) { // Get the world-space position and project onto a meridian plane of // the ellipsoid @@ -71,6 +97,18 @@ vec3 computeEllipsoidPositionIterative(vec3 positionMC) { return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); } +vec3 computeEllipsoidPositionIterative1Iter(vec3 positionMC) { + // Get the world-space position and project onto a meridian plane of + // the ellipsoid + vec3 positionWC = (czm_model * vec4(positionMC, 1.0)).xyz; + + vec2 positionEllipse = vec2(length(positionWC.xy), positionWC.z); + vec2 nearestPoint = nearestPointOnEllipse1Iter(positionEllipse, czm_ellipsoidRadii.xz); + + // Reconstruct a 3D point in world space + return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); +} + #define USE_ITERATIVE_METHOD vec3 computeFogColor(vec3 positionMC) { @@ -78,11 +116,24 @@ vec3 computeFogColor(vec3 positionMC) { vec3 mieColor; float opacity; -#ifdef USE_ITERATIVE_METHOD - vec3 ellipsoidPositionWC = computeEllipsoidPositionIterative(positionMC); -#else - vec3 ellipsoidPositionWC = computeEllipsoidPositionCurvature(positionMC); -#endif + // Measuring performance is difficult in the shader, so run the code many times + // so the difference is noticeable in the FPS counter + const float N = 200.0; + const float WEIGHT = 1.0 / N; + vec3 ellipsoidPositionWC = vec3(0.0); + for (float i = 0.0; i < N; i++) { + if (czm_atmosphereMethod == 0.0) { + // Baseline for comparison - just use the vertex position in WC + ellipsoidPositionWC += WEIGHT * (czm_model * vec4(positionMC, 1.0)).xyz; + } else if (czm_atmosphereMethod == 1.0) { + // Use the curvature method + ellipsoidPositionWC += WEIGHT * computeEllipsoidPositionCurvature(positionMC); + } else { + // use the 2D iterative method + ellipsoidPositionWC += WEIGHT * computeEllipsoidPositionIterative1Iter(positionMC); + } + } + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC); // The fog color is derived from the ground atmosphere color @@ -138,4 +189,12 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { color = vec4(withFog, color.a); //color = mix(color, vec4(fogColor, 1.0), 0.5); + + // Compare the two methods. + vec3 curvature = computeEllipsoidPositionCurvature(attributes.positionMC); + vec3 iterative = computeEllipsoidPositionIterative(attributes.positionMC); + vec3 iterative1 = computeEllipsoidPositionIterative1Iter(attributes.positionMC); + + //color = vec4(abs(curvature - iterative) / 1e3, 1.0); + //color = vec4(abs(iterative - iterative1) / 1e2, 1.0); } From e3266dff2bc61cd26a37b967ecac55ae25a27246 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 8 Jan 2024 16:19:35 -0500 Subject: [PATCH 17/55] Remove temporary debugging code --- .../Source/Renderer/AutomaticUniforms.js | 8 -- .../engine/Source/Renderer/UniformState.js | 9 -- packages/engine/Source/Scene/Atmosphere.js | 8 -- packages/engine/Source/Scene/FrameState.js | 2 - .../Source/Shaders/Model/FogStageFS.glsl | 127 +++--------------- 5 files changed, 15 insertions(+), 139 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 604f4c29915c..4d6beab2cf9b 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1697,14 +1697,6 @@ const AutomaticUniforms = { }, }), - czm_atmosphereMethod: new AutomaticUniform({ - size: 1, - datatype: WebGLConstants.FLOAT, - getValue: function (uniformState) { - return uniformState.atmosphereMethod; - }, - }), - /** * An automatic GLSL uniform representing the splitter position to use when rendering with a splitter. * This will be in pixel coordinates relative to the canvas. diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 5eb6cca9f2f9..75bfbde2d316 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -171,8 +171,6 @@ function UniformState() { this._atmosphereMieAnisotropy = undefined; this._atmosphereDynamicLighting = undefined; - this._atmosphereMethod = undefined; - this._invertClassificationColor = undefined; this._splitPosition = 0.0; @@ -1008,12 +1006,6 @@ Object.defineProperties(UniformState.prototype, { }, }, - atmosphereMethod: { - get: function () { - return this._atmosphereMethod; - }, - }, - /** * A scalar that represents the geometric tolerance per meter * @memberof UniformState.prototype @@ -1530,7 +1522,6 @@ UniformState.prototype.update = function (frameState) { this._atmosphereMieScaleHeight = atmosphere.mieScaleHeight; this._atmosphereMieAnisotropy = atmosphere.mieAnisotropy; this._atmosphereDynamicLighting = atmosphere.dynamicLighting; - this._atmosphereMethod = atmosphere.method; this._invertClassificationColor = frameState.invertClassificationColor; diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index 1452494f3eda..d5b2c4321547 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -88,12 +88,6 @@ function Atmosphere() { * @default DynamicAtmosphereLightingType.OFF */ this.dynamicLighting = DynamicAtmosphereLightingType.OFF; - - // TEMPORARY! Just for side-by-side-comparisons for the PR - // 0 = positionWC = czm_model * positionMC - // 1 = positionWC = computeEllipsoidPositionCurvature(positionMC) - // 2 = positionWC = computeEllipsoidPositionIterative(positionMC) - this.method = 1; } Atmosphere.prototype.update = function (frameState) { @@ -115,8 +109,6 @@ Atmosphere.prototype.update = function (frameState) { atmosphere.mieAnisotropy = this.mieAnisotropy; atmosphere.dynamicLighting = this.dynamicLighting; - - atmosphere.method = this.method; }; export default Atmosphere; diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 81b10aa18e5d..0ad600f948ba 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -302,8 +302,6 @@ function FrameState(context, creditDisplay, jobScheduler) { mieScaleHeight: undefined, mieAnisotropy: undefined, dynamicLighting: undefined, - - method: 1, }; /** diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 02fd31bffde4..6c6b2eb23337 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -1,39 +1,10 @@ -vec3 computeEllipsoidPositionCurvature(vec3 positionMC) { - // Compute the distance from the camera to the local center of curvature. - vec4 vertexPositionENU = czm_modelToEnu * vec4(positionMC, 1.0); - vec2 vertexAzimuth = normalize(vertexPositionENU.xy); - // Curvature = 1 / radius of curvature. - float azimuthalCurvature = dot(vertexAzimuth * vertexAzimuth, czm_eyeEllipsoidCurvature); - float eyeToCenter = 1.0 / azimuthalCurvature + czm_eyeHeight; - - // Compute the approximate ellipsoid normal at the vertex position. - // Uses a circular approximation for the Earth curvature along the geodesic. - vec3 vertexPositionEC = (czm_modelView * vec4(positionMC, 1.0)).xyz; - vec3 centerToVertex = eyeToCenter * czm_eyeEllipsoidNormalEC + vertexPositionEC; - vec3 vertexNormal = normalize(centerToVertex); - - // Estimate the (sine of the) angle between the camera direction and the vertex normal - float verticalDistance = dot(vertexPositionEC, czm_eyeEllipsoidNormalEC); - float horizontalDistance = length(vertexPositionEC - verticalDistance * czm_eyeEllipsoidNormalEC); - float sinTheta = horizontalDistance / (eyeToCenter + verticalDistance); - bool isSmallAngle = clamp(sinTheta, 0.0, 0.05) == sinTheta; - - // Approximate the change in height above the ellipsoid, from camera to vertex position. - float exactVersine = 1.0 - dot(czm_eyeEllipsoidNormalEC, vertexNormal); - float smallAngleVersine = 0.5 * sinTheta * sinTheta; - float versine = isSmallAngle ? smallAngleVersine : exactVersine; - float dHeight = dot(vertexPositionEC, vertexNormal) - eyeToCenter * versine; - float vertexHeight = czm_eyeHeight + dHeight; - - vec3 ellipsoidPositionEC = vertexPositionEC - vertexHeight * vertexNormal; - return (czm_inverseView * vec4(ellipsoidPositionEC, 1.0)).xyz; - -} - // robust iterative solution without trig functions // https://github.com/0xfaded/ellipse_demo/issues/1 // https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse -vec2 nearestPointOnEllipse(vec2 pos, vec2 radii) { +// +// This version uses only a single iteration for best performance. For fog +// rendering, the difference is negligible. +vec2 nearestPointOnEllipseFast(vec2 pos, vec2 radii) { vec2 p = abs(pos); vec2 inverseRadii = 1.0 / radii; vec2 evoluteScale = (radii.x * radii.x - radii.y * radii.y) * vec2(1.0, -1.0) * inverseRadii; @@ -44,96 +15,36 @@ vec2 nearestPointOnEllipse(vec2 pos, vec2 radii) { vec2 tTrigs = vec2(0.70710678118); vec2 v = radii * tTrigs; - const int iterations = 3; - for (int i = 0; i < iterations; ++i) { - // Find the evolute of the ellipse (center of curvature) at v. - vec2 evolute = evoluteScale * tTrigs * tTrigs * tTrigs; - // Find the (approximate) intersection of p - evolute with the ellipsoid. - vec2 q = normalize(p - evolute) * length(v - evolute); - // Update the estimate of t. - tTrigs = (q + evolute) * inverseRadii; - tTrigs = normalize(clamp(tTrigs, 0.0, 1.0)); - v = radii * tTrigs; - } + // Find the evolute of the ellipse (center of curvature) at v. + vec2 evolute = evoluteScale * tTrigs * tTrigs * tTrigs; + // Find the (approximate) intersection of p - evolute with the ellipsoid. + vec2 q = normalize(p - evolute) * length(v - evolute); + // Update the estimate of t. + tTrigs = (q + evolute) * inverseRadii; + tTrigs = normalize(clamp(tTrigs, 0.0, 1.0)); + v = radii * tTrigs; return v * sign(pos); } -vec2 nearestPointOnEllipse1Iter(vec2 pos, vec2 radii) { - vec2 p = abs(pos); - vec2 inverseRadii = 1.0 / radii; - vec2 evoluteScale = (radii.x * radii.x - radii.y * radii.y) * vec2(1.0, -1.0) * inverseRadii; - - // We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t)) - // but store the cos and sin of t in a vec2 for efficiency. - // Initial guess: t = cos(pi/4) - vec2 tTrigs = vec2(0.70710678118); - vec2 v = radii * tTrigs; - - const int iterations = 1; - for (int i = 0; i < iterations; ++i) { - // Find the evolute of the ellipse (center of curvature) at v. - vec2 evolute = evoluteScale * tTrigs * tTrigs * tTrigs; - // Find the (approximate) intersection of p - evolute with the ellipsoid. - vec2 q = normalize(p - evolute) * length(v - evolute); - // Update the estimate of t. - tTrigs = (q + evolute) * inverseRadii; - tTrigs = normalize(clamp(tTrigs, 0.0, 1.0)); - v = radii * tTrigs; - } - - return v * sign(pos); -} - -vec3 computeEllipsoidPositionIterative(vec3 positionMC) { +vec3 computeEllipsoidPositionWC(vec3 positionMC) { // Get the world-space position and project onto a meridian plane of // the ellipsoid vec3 positionWC = (czm_model * vec4(positionMC, 1.0)).xyz; vec2 positionEllipse = vec2(length(positionWC.xy), positionWC.z); - vec2 nearestPoint = nearestPointOnEllipse(positionEllipse, czm_ellipsoidRadii.xz); + vec2 nearestPoint = nearestPointOnEllipseFast(positionEllipse, czm_ellipsoidRadii.xz); // Reconstruct a 3D point in world space return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); } -vec3 computeEllipsoidPositionIterative1Iter(vec3 positionMC) { - // Get the world-space position and project onto a meridian plane of - // the ellipsoid - vec3 positionWC = (czm_model * vec4(positionMC, 1.0)).xyz; - - vec2 positionEllipse = vec2(length(positionWC.xy), positionWC.z); - vec2 nearestPoint = nearestPointOnEllipse1Iter(positionEllipse, czm_ellipsoidRadii.xz); - - // Reconstruct a 3D point in world space - return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); -} - -#define USE_ITERATIVE_METHOD - vec3 computeFogColor(vec3 positionMC) { vec3 rayleighColor = vec3(0.0, 0.0, 1.0); vec3 mieColor; float opacity; - // Measuring performance is difficult in the shader, so run the code many times - // so the difference is noticeable in the FPS counter - const float N = 200.0; - const float WEIGHT = 1.0 / N; - vec3 ellipsoidPositionWC = vec3(0.0); - for (float i = 0.0; i < N; i++) { - if (czm_atmosphereMethod == 0.0) { - // Baseline for comparison - just use the vertex position in WC - ellipsoidPositionWC += WEIGHT * (czm_model * vec4(positionMC, 1.0)).xyz; - } else if (czm_atmosphereMethod == 1.0) { - // Use the curvature method - ellipsoidPositionWC += WEIGHT * computeEllipsoidPositionCurvature(positionMC); - } else { - // use the 2D iterative method - ellipsoidPositionWC += WEIGHT * computeEllipsoidPositionIterative1Iter(positionMC); - } - } - + vec3 ellipsoidPositionWC = computeEllipsoidPositionWC(positionMC); vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC); // The fog color is derived from the ground atmosphere color @@ -189,12 +100,4 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { color = vec4(withFog, color.a); //color = mix(color, vec4(fogColor, 1.0), 0.5); - - // Compare the two methods. - vec3 curvature = computeEllipsoidPositionCurvature(attributes.positionMC); - vec3 iterative = computeEllipsoidPositionIterative(attributes.positionMC); - vec3 iterative1 = computeEllipsoidPositionIterative1Iter(attributes.positionMC); - - //color = vec4(abs(curvature - iterative) / 1e3, 1.0); - //color = vec4(abs(iterative - iterative1) / 1e2, 1.0); } From cc1f34634f76a53e9538008d01b5ff4dcf1c70f9 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Mon, 8 Jan 2024 16:41:05 -0500 Subject: [PATCH 18/55] Have scene.atmosphere work when globe is off --- .../Scene/DynamicAtmosphereLightingType.js | 22 +++++++++++++++++++ packages/engine/Source/Scene/Scene.js | 10 ++++++--- packages/engine/Source/Scene/SkyAtmosphere.js | 10 +++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js index 5078ac2b861d..892e072a5f08 100644 --- a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js +++ b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js @@ -31,4 +31,26 @@ const DynamicAtmosphereLightingType = { SUNLIGHT: 2, }; +/** + * Get the lighting enum from the older globe flags + * + * @param {Globe} globe The globe + * @return {DynamicAtmosphereLightingType} The corresponding enum value + * + * @private + */ +DynamicAtmosphereLightingType.fromGlobeFlags = function (globe) { + const lightingOn = globe.enableLighting && globe.dynamicAtmosphereLighting; + if (!lightingOn) { + return DynamicAtmosphereLightingType.OFF; + } + + // Force sunlight + if (globe.dynamicAtmosphereLightingFromSun) { + return DynamicAtmosphereLightingType.SUNLIGHT; + } + + return DynamicAtmosphereLightingType.SCENE_LIGHT; +}; + export default Object.freeze(DynamicAtmosphereLightingType); diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 2d254f3d8c8f..db269eadb67a 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -47,6 +47,7 @@ import DebugCameraPrimitive from "./DebugCameraPrimitive.js"; import DepthPlane from "./DepthPlane.js"; import DerivedCommand from "./DerivedCommand.js"; import DeviceOrientationCameraController from "./DeviceOrientationCameraController.js"; +import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; import Fog from "./Fog.js"; import FrameState from "./FrameState.js"; import GlobeTranslucencyState from "./GlobeTranslucencyState.js"; @@ -3170,6 +3171,7 @@ Scene.prototype.updateEnvironment = function () { const environmentState = this._environmentState; const renderPass = frameState.passes.render; const offscreenPass = frameState.passes.offscreen; + const atmosphere = this.atmosphere; const skyAtmosphere = this.skyAtmosphere; const globe = this.globe; const globeTranslucencyState = this._globeTranslucencyState; @@ -3188,17 +3190,19 @@ Scene.prototype.updateEnvironment = function () { } else { if (defined(skyAtmosphere)) { if (defined(globe)) { - skyAtmosphere.setDynamicAtmosphereColor( - globe.enableLighting && globe.dynamicAtmosphereLighting, - globe.dynamicAtmosphereLightingFromSun + skyAtmosphere.setDynamicLighting( + DynamicAtmosphereLightingType.fromGlobeFlags(globe) ); environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || !globe.show || globe._surface._tilesToRender.length > 0; } else { + const dynamicLighting = atmosphere.dynamicLighting; + skyAtmosphere.setDynamicAtmosphereColor(dynamicLighting); environmentState.isReadyForAtmosphere = true; } + environmentState.skyAtmosphereCommand = skyAtmosphere.update( frameState, globe diff --git a/packages/engine/Source/Scene/SkyAtmosphere.js b/packages/engine/Source/Scene/SkyAtmosphere.js index 11cccc5f23f5..9c4329052ae5 100644 --- a/packages/engine/Source/Scene/SkyAtmosphere.js +++ b/packages/engine/Source/Scene/SkyAtmosphere.js @@ -227,6 +227,16 @@ SkyAtmosphere.prototype.setDynamicAtmosphereColor = function ( this._radiiAndDynamicAtmosphereColor.z = lightEnum; }; +/** + * Set the dynamic lighting enum value for the shader + * @param {DynamicAtmosphereLightingType} lightingEnum + * + * @private + */ +SkyAtmosphere.prototype.setDynamicLighting = function (lightingEnum) { + this._radiiAndDynamicAtmosphereColor.z = lightingEnum; +}; + const scratchModelMatrix = new Matrix4(); /** From bce6afcc60065fd215ec36c5f8f5fe2f9fd55f72 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 10:39:21 -0500 Subject: [PATCH 19/55] darken dynamic lighting --- .../engine/Source/Renderer/AutomaticUniforms.js | 13 +++++++++++++ .../engine/Source/Shaders/Model/FogStageFS.glsl | 14 ++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 4d6beab2cf9b..8e34b2d75cf5 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1592,6 +1592,19 @@ const AutomaticUniforms = { }, }), + /** + * An automatic GLSL uniform scalar used to set a minimum brightness when dynamic lighting is applied to fog. + * + * @see czm_fog + */ + czm_fogMinimumBrightness: new AutomaticUniform({ + size: 1, + datatype: WebGLConstants.FLOAT, + getValue: function (uniformState) { + return uniformState.fogMinimumBrightness; + }, + }), + /** * An automatic uniform representing the color shift for the atmosphere in HSB color space * diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 6c6b2eb23337..6c74bf721a44 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -62,14 +62,12 @@ vec3 computeFogColor(vec3 positionMC) { vec4 groundAtmosphereColor = czm_computeAtmosphereColor(ellipsoidPositionWC, lightDirection, rayleighColor, mieColor, opacity); vec3 fogColor = groundAtmosphereColor.rgb; - // Darken the fog - - // If there is lighting, apply that to the fog. -//#if defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING)) - //const float u_minimumBrightness = 0.03; // TODO: pull this from the light shader - //float darken = clamp(dot(normalize(czm_viewerPositionWC), atmosphereLightDirection), u_minimumBrightness, 1.0); - //fogColor *= darken; -//#endif + // If there is dynamic lighting, apply that to the fog. + const float OFF = 0.0; + if (czm_atmosphereDynamicLighting != OFF) { + float darken = clamp(dot(normalize(czm_viewerPositionWC), lightDirection), czm_fogMinimumBrightness, 1.0); + fogColor *= darken; + } // Tonemap if HDR rendering is disabled #ifndef HDR From 3112490fdfadce67c8038b4e98573ec4882801b5 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:23:57 -0500 Subject: [PATCH 20/55] Remove dead function --- packages/engine/Source/Scene/Scene.js | 2 +- packages/engine/Source/Scene/SkyAtmosphere.js | 11 ----------- packages/engine/Specs/Scene/SkyAtmosphereSpec.js | 13 +++++++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index db269eadb67a..8e5a9be1f1a6 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -3199,7 +3199,7 @@ Scene.prototype.updateEnvironment = function () { globe._surface._tilesToRender.length > 0; } else { const dynamicLighting = atmosphere.dynamicLighting; - skyAtmosphere.setDynamicAtmosphereColor(dynamicLighting); + skyAtmosphere.setDynamicLighting(dynamicLighting); environmentState.isReadyForAtmosphere = true; } diff --git a/packages/engine/Source/Scene/SkyAtmosphere.js b/packages/engine/Source/Scene/SkyAtmosphere.js index 9c4329052ae5..c97a7a88e098 100644 --- a/packages/engine/Source/Scene/SkyAtmosphere.js +++ b/packages/engine/Source/Scene/SkyAtmosphere.js @@ -216,17 +216,6 @@ Object.defineProperties(SkyAtmosphere.prototype, { }, }); -/** - * @private - */ -SkyAtmosphere.prototype.setDynamicAtmosphereColor = function ( - enableLighting, - useSunDirection -) { - const lightEnum = enableLighting ? (useSunDirection ? 2.0 : 1.0) : 0.0; - this._radiiAndDynamicAtmosphereColor.z = lightEnum; -}; - /** * Set the dynamic lighting enum value for the shader * @param {DynamicAtmosphereLightingType} lightingEnum diff --git a/packages/engine/Specs/Scene/SkyAtmosphereSpec.js b/packages/engine/Specs/Scene/SkyAtmosphereSpec.js index edc027b84dea..f3d62d2f4546 100644 --- a/packages/engine/Specs/Scene/SkyAtmosphereSpec.js +++ b/packages/engine/Specs/Scene/SkyAtmosphereSpec.js @@ -1,5 +1,6 @@ import { Cartesian3, + DynamicAtmosphereLightingType, Ellipsoid, SceneMode, SkyAtmosphere, @@ -52,9 +53,9 @@ describe( s.destroy(); }); - it("draws sky with setDynamicAtmosphereColor set to true", function () { + it("draws sky with dynamic lighting (scene light source)", function () { const s = new SkyAtmosphere(); - s.setDynamicAtmosphereColor(true, false); + s.setDynamicLighting(DynamicAtmosphereLightingType.SCENE_LIGHT); expect(scene).toRender([0, 0, 0, 255]); scene.render(); @@ -67,9 +68,9 @@ describe( s.destroy(); }); - it("draws sky with setDynamicAtmosphereColor set to true using the sun direction", function () { + it("draws sky with dynamic lighting (sunlight)", function () { const s = new SkyAtmosphere(); - s.setDynamicAtmosphereColor(true, true); + s.setDynamicLighting(DynamicAtmosphereLightingType.SUNLIGHT); expect(scene).toRender([0, 0, 0, 255]); scene.render(); @@ -82,9 +83,9 @@ describe( s.destroy(); }); - it("draws sky with setDynamicAtmosphereColor set to false", function () { + it("draws sky with dynamic lighting off", function () { const s = new SkyAtmosphere(); - s.setDynamicAtmosphereColor(false, false); + s.setDynamicLighting(DynamicAtmosphereLightingType.OFF); expect(scene).toRender([0, 0, 0, 255]); scene.render(); From b57ea043df37c3f68684b23105f14ba184aa82c2 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:30:51 -0500 Subject: [PATCH 21/55] Remove debugging code --- .../Shaders/Builtin/Functions/computeScattering.glsl | 4 ---- packages/engine/Source/Shaders/Model/FogStageFS.glsl | 9 --------- 2 files changed, 13 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl index 63cef0c248eb..284ead0846bb 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl @@ -154,8 +154,4 @@ void czm_computeScattering( // Compute the transmittance i.e. how much light is passing through the atmosphere. opacity = length(exp(-((czm_atmosphereMieCoefficient * opticalDepth.y) + (czm_atmosphereRayleighCoefficient * opticalDepth.x)))); - - //rayleighColor = vec3(atmosphereInnerRadius / 1.0e7, lastVals.x / 1.0e7, 0.0); //lastVals; - //vec3(float(PRIMARY_STEPS) / 16.0, float(LIGHT_STEPS) / 4.0, 0.0);//mieAccumulation; //rayleighAccumulation; - //rayleighColor = w_stop_gt_lprl /*w_inside_atmosphere*/ * vec3(1.0, 1.0, 0.0); //w_inside_atmosphere, 0.0); } diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 6c74bf721a44..8350a0438e49 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -56,9 +56,6 @@ vec3 computeFogColor(vec3 positionMC) { opacity ); - //rayleighColor = vec3(1.0, 0.0, 0.0); - //mieColor = vec3(0.0, 1.0, 0.0); - vec4 groundAtmosphereColor = czm_computeAtmosphereColor(ellipsoidPositionWC, lightDirection, rayleighColor, mieColor, opacity); vec3 fogColor = groundAtmosphereColor.rgb; @@ -75,12 +72,7 @@ vec3 computeFogColor(vec3 positionMC) { fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif - // TODO: fogColor.a is only used for ground atmosphere... is that needed? - - //return positionWC / 1e7; - //return rayleighColor; return fogColor.rgb; - //return mieColor; } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { @@ -97,5 +89,4 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { vec3 withFog = czm_fog(distanceToCamera, color.rgb, fogColor, fogModifier); color = vec4(withFog, color.a); - //color = mix(color, vec4(fogColor, 1.0), 0.5); } From bd7f7b95595cdb4280c3384bb70f84ebb84120aa Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:35:11 -0500 Subject: [PATCH 22/55] Fix typo --- packages/engine/Source/Scene/Model/FogPipelineStage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index b23cb4654ae9..09b72fedf068 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -9,7 +9,7 @@ import ShaderDestination from "../../Renderer/ShaderDestination.js"; * @private */ const FogPipelineStage = { - name: "FogColorPipelineStage", // Helps with debugging + name: "FogPipelineStage", // Helps with debugging }; FogPipelineStage.process = function (renderResources, model, frameState) { From c68e971dbb90bd350662b5e0e162dead4604bf30 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:52:01 -0500 Subject: [PATCH 23/55] Use common function for SkyAtmosphere --- .../engine/Source/Renderer/AutomaticUniforms.js | 1 + .../getDynamicAtmosphereLightDirection.glsl | 7 +++---- .../engine/Source/Shaders/Model/FogStageFS.glsl | 2 +- .../Source/Shaders/SkyAtmosphereCommon.glsl | 16 +++------------- .../engine/Source/Shaders/SkyAtmosphereFS.glsl | 5 +++-- .../engine/Source/Shaders/SkyAtmosphereVS.glsl | 5 +++-- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 8e34b2d75cf5..0b16c23ae9a3 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1696,6 +1696,7 @@ const AutomaticUniforms = { return uniformState.atmosphereMieAnisotropy; }, }), + /** * An automatic uniform representing which light source to use for dynamic lighting * diff --git a/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl index 2dd991d28026..9b934362b090 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl @@ -1,16 +1,15 @@ /** - * Select which direction vector to use for dynamic atmosphere lighting based on the czm_atmosphereDynamicLighting enum. + * Select which direction vector to use for dynamic atmosphere lighting based on an enum value * * @name czm_getDynamicAtmosphereLightDirection * @glslfunction * @see DynamicAtmosphereLightingType.js * * @param {vec3} positionWC the position of the vertex/fragment in world coordinates. This is normalized and returned when dynamic lighting is turned off. + * @param {float} lightEnum The enum value for selecting between light sources. * @return {vec3} The normalized light direction vector. Depending on the enum value, it is either positionWC, czm_lightDirectionWC or czm_sunDirectionWC */ -vec3 czm_getDynamicAtmosphereLightDirection(vec3 positionWC) { - float lightEnum = czm_atmosphereDynamicLighting; - +vec3 czm_getDynamicAtmosphereLightDirection(vec3 positionWC, float lightEnum) { const float OFF = 0.0; const float SCENE_LIGHT = 1.0; const float SUNLIGHT = 2.0; diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 8350a0438e49..a6c6fa74d652 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -45,7 +45,7 @@ vec3 computeFogColor(vec3 positionMC) { float opacity; vec3 ellipsoidPositionWC = computeEllipsoidPositionWC(positionMC); - vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC); + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC, czm_atmosphereDynamicLighting); // The fog color is derived from the ground atmosphere color czm_computeGroundAtmosphereScattering( diff --git a/packages/engine/Source/Shaders/SkyAtmosphereCommon.glsl b/packages/engine/Source/Shaders/SkyAtmosphereCommon.glsl index 0c27d89f9311..9a958474b9d4 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereCommon.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereCommon.glsl @@ -8,16 +8,6 @@ float interpolateByDistance(vec4 nearFarScalar, float distance) return mix(startValue, endValue, t); } -vec3 getLightDirection(vec3 positionWC) -{ - float lightEnum = u_radiiAndDynamicAtmosphereColor.z; - vec3 lightDirection = - positionWC * float(lightEnum == 0.0) + - czm_lightDirectionWC * float(lightEnum == 1.0) + - czm_sunDirectionWC * float(lightEnum == 2.0); - return normalize(lightDirection); -} - void computeAtmosphereScattering(vec3 positionWC, vec3 lightDirection, out vec3 rayleighColor, out vec3 mieColor, out float opacity, out float underTranslucentGlobe) { float ellipsoidRadiiDifference = czm_ellipsoidRadii.x - czm_ellipsoidRadii.z; @@ -28,7 +18,7 @@ void computeAtmosphereScattering(vec3 positionWC, vec3 lightDirection, out vec3 float distanceAdjustModifier = ellipsoidRadiiDifference / 2.0; float distanceAdjust = distanceAdjustModifier * clamp((czm_eyeHeight - distanceAdjustMin) / (distanceAdjustMax - distanceAdjustMin), 0.0, 1.0); - // Since atmosphere scattering assumes the atmosphere is a spherical shell, we compute an inner radius of the atmosphere best fit + // Since atmosphere scattering assumes the atmosphere is a spherical shell, we compute an inner radius of the atmosphere best fit // for the position on the ellipsoid. float radiusAdjust = (ellipsoidRadiiDifference / 4.0) + distanceAdjust; float atmosphereInnerRadius = (length(czm_viewerPositionWC) - czm_eyeHeight) - radiusAdjust; @@ -46,7 +36,7 @@ void computeAtmosphereScattering(vec3 positionWC, vec3 lightDirection, out vec3 // Check for intersection with the inner radius of the atmopshere. czm_raySegment primaryRayEarthIntersect = czm_raySphereIntersectionInterval(primaryRay, vec3(0.0), atmosphereInnerRadius + radiusAdjust); if (primaryRayEarthIntersect.start > 0.0 && primaryRayEarthIntersect.stop > 0.0) { - + // Compute position on globe. vec3 direction = normalize(positionWC); czm_ray ellipsoidRay = czm_ray(positionWC, -direction); @@ -62,7 +52,7 @@ void computeAtmosphereScattering(vec3 positionWC, vec3 lightDirection, out vec3 vec3 nearColor = vec3(0.0); rayleighColor = mix(nearColor, horizonColor, exp(-angle) * opacity); - + // Set the traslucent flag to avoid alpha adjustment in computeFinalColor funciton. underTranslucentGlobe = 1.0; return; diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index d0c67c94d440..db07b7fbd1ec 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -11,8 +11,9 @@ in float v_translucent; void main (void) { - vec3 lightDirection = getLightDirection(v_outerPositionWC); - + float lightEnum = u_radiiAndDynamicAtmosphereColor.z; + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(v_outerPositionWC, lightEnum); + vec3 mieColor; vec3 rayleighColor; float opacity; diff --git a/packages/engine/Source/Shaders/SkyAtmosphereVS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereVS.glsl index 88d0bf59bcf8..8ed97f1a0979 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereVS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereVS.glsl @@ -12,7 +12,8 @@ out float v_translucent; void main(void) { vec4 positionWC = czm_model * position; - vec3 lightDirection = getLightDirection(positionWC.xyz); + float lightEnum = u_radiiAndDynamicAtmosphereColor.z; + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC.xyz, lightEnum); #ifndef PER_FRAGMENT_ATMOSPHERE computeAtmosphereScattering( @@ -24,7 +25,7 @@ void main(void) v_translucent ); #endif - + v_outerPositionWC = positionWC.xyz; gl_Position = czm_modelViewProjection * position; } From a458be139556441bf0a22b7290dd498981836941 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:52:43 -0500 Subject: [PATCH 24/55] Remove debugging code --- .../Source/Shaders/Builtin/Functions/computeScattering.glsl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl index 284ead0846bb..f3c5bb9c5d57 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl @@ -80,8 +80,6 @@ void czm_computeScattering( vec2 opticalDepth = vec2(0.0); vec2 heightScale = vec2(czm_atmosphereRayleighScaleHeight, czm_atmosphereMieScaleHeight); - //vec3 lastVals = vec3(0.0); - // Sample positions on the primary ray. for (int i = 0; i < PRIMARY_STEPS_MAX; ++i) { @@ -130,9 +128,6 @@ void czm_computeScattering( // Increment distance on light ray. lightPositionLength += lightStepLength; - - //lastVals = vec3(length(lightPosition)); - //lastVals = vec3(float(lightHeight < 0.0), lightHeight / 1000.0, lightOpticalDepth); } // Compute attenuation via the primary ray and the light ray. From 63efb02eb75bcf9fadc7b5ad57c4e033e3c8d974 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 11:54:52 -0500 Subject: [PATCH 25/55] Remove unneeded file --- .../Functions/computeEllipsoidPosition.glsl | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl deleted file mode 100644 index ddeeee0df3a8..000000000000 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeEllipsoidPosition.glsl +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Compute the WC position on the elipsoid of the current fragment. The result - * is low-precision due to use of 32-bit floats. - * - * @return {vec3} The position in world coordinates. - */ - - /* -vec3 czm_computeEllipsoidPosition() -{ - float mpp = czm_metersPerPixel(vec4(0.0, 0.0, -czm_currentFrustum.x, 1.0), 1.0); - vec2 xy = gl_FragCoord.xy / czm_viewport.zw * 2.0 - vec2(1.0); - xy *= czm_viewport.zw * mpp * 0.5; - - vec3 direction = normalize(vec3(xy, -czm_currentFrustum.x)); - czm_ray ray = czm_ray(vec3(0.0), direction); - - vec3 ellipsoid_center = czm_view[3].xyz; - - czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid_center, czm_ellipsoidInverseRadii); - - vec3 ellipsoidPosition = czm_pointAlongRay(ray, intersection.start); - return (czm_inverseView * vec4(ellipsoidPosition, 1.0)).xyz; -} -*/ - -vec3 czm_computeEllipsoidPosition() -{ - return vec3(0.0); -} From e0314cd45a00d40bef3ef71f077692de2ff0d09f Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 13:41:23 -0500 Subject: [PATCH 26/55] Add a uniform for whether the tile is in fog --- .../Source/Scene/GlobeSurfaceTileProvider.js | 4 +++- .../Source/Scene/Model/FogPipelineStage.js | 22 ++++++++++++++++++- .../Scene/Model/ModelRuntimePrimitive.js | 6 ----- .../Source/Scene/Model/ModelSceneGraph.js | 6 +++++ packages/engine/Source/Shaders/GlobeFS.glsl | 2 -- .../Source/Shaders/Model/FogStageFS.glsl | 6 +++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js index e32b797efc48..a6851b02e469 100644 --- a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js +++ b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js @@ -2502,7 +2502,9 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { uniformMapProperties.localizedTranslucencyRectangle ); - // For performance, use fog in the shader only when the tile is in fog. + // For performance, render fog only when fog is enabled and the effect of + // fog would be non-negligible. This prevents the shader from running when + // the camera is in space, for example. const applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index 09b72fedf068..2907582f07ad 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -1,5 +1,7 @@ -import FogStageFS from "../../Shaders/Model/FogStageFS.js"; +import Cartesian3 from "../../Core/Cartesian3.js"; +import CesiumMath from "../../Core/Math.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; +import FogStageFS from "../../Shaders/Model/FogStageFS.js"; /** * The fog color pipeline stage is responsible for applying fog to tiles in the distance in horizon views. @@ -17,6 +19,24 @@ FogPipelineStage.process = function (renderResources, model, frameState) { shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.FRAGMENT); shaderBuilder.addFragmentLines([FogStageFS]); + + // Add a uniform so fog is only calculated when the effect would + // be non-negligible For example when the camera is in space, fog density decreases + // to 0 so fog shouldn't be rendered. Since this state may change rapidly if + // the camera is moving, this is implemented as a uniform, not a define. + shaderBuilder.addUniform("bool", "u_isInFog", ShaderDestination.FRAGMENT); + renderResources.uniformMap.u_isInFog = function () { + // We only need a rough measure of distance to the model, so measure + // from the camera to the bounding sphere center. + const distance = Cartesian3.distance( + frameState.camera.position, + model.boundingSphere.center + ); + + return ( + CesiumMath.fog(distance, frameState.fog.density) > CesiumMath.EPSILON3 + ); + }; }; export default FogPipelineStage; diff --git a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js index e168b6b280e8..ed82ca255cf4 100644 --- a/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js +++ b/packages/engine/Source/Scene/Model/ModelRuntimePrimitive.js @@ -11,7 +11,6 @@ import CustomShaderMode from "./CustomShaderMode.js"; import CustomShaderPipelineStage from "./CustomShaderPipelineStage.js"; import DequantizationPipelineStage from "./DequantizationPipelineStage.js"; import FeatureIdPipelineStage from "./FeatureIdPipelineStage.js"; -import FogPipelineStage from "./FogPipelineStage.js"; import GeometryPipelineStage from "./GeometryPipelineStage.js"; import LightingPipelineStage from "./LightingPipelineStage.js"; import MaterialPipelineStage from "./MaterialPipelineStage.js"; @@ -200,7 +199,6 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { const mode = frameState.mode; const use2D = mode !== SceneMode.SCENE3D && !frameState.scene3DOnly && model._projectTo2D; - const fogRenderable = frameState.fog.enabled && frameState.fog.renderable; const exaggerateTerrain = frameState.verticalExaggeration !== 1.0; const hasMorphTargets = @@ -305,10 +303,6 @@ ModelRuntimePrimitive.prototype.configurePipeline = function (frameState) { pipelineStages.push(AlphaPipelineStage); - if (fogRenderable) { - pipelineStages.push(FogPipelineStage); - } - pipelineStages.push(PrimitiveStatisticsPipelineStage); return; diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 1ff305d3adcf..72e3cbf11d7b 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -9,6 +9,7 @@ import SceneMode from "../SceneMode.js"; import SplitDirection from "../SplitDirection.js"; import buildDrawCommand from "./buildDrawCommand.js"; import TilesetPipelineStage from "./TilesetPipelineStage.js"; +import FogPipelineStage from "./FogPipelineStage.js"; import ImageBasedLightingPipelineStage from "./ImageBasedLightingPipelineStage.js"; import ModelArticulation from "./ModelArticulation.js"; import ModelColorPipelineStage from "./ModelColorPipelineStage.js"; @@ -606,6 +607,7 @@ ModelSceneGraph.prototype.configurePipeline = function (frameState) { modelPipelineStages.length = 0; const model = this._model; + const fogRenderable = frameState.fog.enabled && frameState.fog.renderable; if (defined(model.color)) { modelPipelineStages.push(ModelColorPipelineStage); @@ -638,6 +640,10 @@ ModelSceneGraph.prototype.configurePipeline = function (frameState) { if (ModelType.is3DTiles(model.type)) { modelPipelineStages.push(TilesetPipelineStage); } + + if (fogRenderable) { + modelPipelineStages.push(FogPipelineStage); + } }; ModelSceneGraph.prototype.update = function (frameState, updateForAnimations) { diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 79d5960159ee..3d181e60ee0c 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -523,8 +523,6 @@ void main() finalColor.rgb = mix(finalColor.rgb, finalAtmosphereColor.rgb, fade); #endif - - //finalColor.rgb = computeEllipsoidPosition() / 1e7; } #endif diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index a6c6fa74d652..5d319ded73e7 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -76,6 +76,12 @@ vec3 computeFogColor(vec3 positionMC) { } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { + if (!u_isInFog) { + // Debugging + //color.rgb = vec3(1.0, 1.0, 0.0); + return; + } + vec3 fogColor = computeFogColor(attributes.positionMC); // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. From 7d4e2fa7a49957ef4272468feb0810e905ddaded Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 13:43:03 -0500 Subject: [PATCH 27/55] remove debugging code --- packages/engine/Source/Shaders/Model/FogStageFS.glsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 5d319ded73e7..5a7d0e471054 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -77,8 +77,6 @@ vec3 computeFogColor(vec3 positionMC) { void fogStage(inout vec4 color, in ProcessedAttributes attributes) { if (!u_isInFog) { - // Debugging - //color.rgb = vec3(1.0, 1.0, 0.0); return; } From d78360aab6cf9072fe92114d25aeaff9af067199 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 16:10:10 -0500 Subject: [PATCH 28/55] Scattering is to be computed in the vertex shader --- .../engine/Source/Renderer/UniformState.js | 12 +++ .../Source/Scene/Model/FogPipelineStage.js | 14 +++- packages/engine/Source/Shaders/GlobeFS.glsl | 2 + .../Source/Shaders/Model/FogStageFS.glsl | 82 +++++++++++-------- .../Source/Shaders/Model/FogStageVS.glsl | 12 +++ .../Source/Shaders/Model/GeometryStageFS.glsl | 2 +- .../Source/Shaders/Model/GeometryStageVS.glsl | 8 +- .../engine/Source/Shaders/Model/ModelVS.glsl | 12 ++- 8 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 packages/engine/Source/Shaders/Model/FogStageVS.glsl diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 75bfbde2d316..d4745818c480 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -161,6 +161,7 @@ function UniformState() { this._specularEnvironmentMapsMaximumLOD = undefined; this._fogDensity = undefined; + this._fogMinimumBrightness = undefined; this._atmosphereHsbShift = undefined; this._atmosphereLightIntensity = undefined; @@ -924,6 +925,17 @@ Object.defineProperties(UniformState.prototype, { }, }, + /** + * A scalar used as a minimum value when brightening fog + * @memberof UniformState.prototype + * @type {number} + */ + fogMinimumBrightness: { + get: function () { + return this._fogMinimumBrightness; + }, + }, + /** * A color shift to apply to the atmosphere color in HSB. * @memberof UniformState.prototype diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index 2907582f07ad..971f6cb03295 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -2,6 +2,7 @@ import Cartesian3 from "../../Core/Cartesian3.js"; import CesiumMath from "../../Core/Math.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import FogStageFS from "../../Shaders/Model/FogStageFS.js"; +import FogStageVS from "../../Shaders/Model/FogStageVS.js"; /** * The fog color pipeline stage is responsible for applying fog to tiles in the distance in horizon views. @@ -17,7 +18,18 @@ const FogPipelineStage = { FogPipelineStage.process = function (renderResources, model, frameState) { const shaderBuilder = renderResources.shaderBuilder; - shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.FRAGMENT); + shaderBuilder.addDefine( + "COMPUTE_POSITION_WC_ATMOSPHERE", + undefined, + ShaderDestination.BOTH + ); + + shaderBuilder.addVarying("vec3", "v_atmosphereRayleighColor"); + shaderBuilder.addVarying("vec3", "v_atmosphereMieColor"); + shaderBuilder.addVarying("float", "v_atmosphereOpacity"); + + shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.BOTH); + shaderBuilder.addVertexLines([FogStageVS]); shaderBuilder.addFragmentLines([FogStageFS]); // Add a uniform so fog is only calculated when the effect would diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 3d181e60ee0c..e344dfec2bfc 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -496,6 +496,8 @@ void main() finalColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor.rgb, modifier), finalColor.a); #else + // Apply ground atmosphere. This happens when the camera is far away from the earth. + // The transmittance is based on optical depth i.e. the length of segment of the ray inside the atmosphere. // This value is larger near the "circumference", as it is further away from the camera. We use it to // brighten up that area of the ground atmosphere. diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 5a7d0e471054..870e040d7132 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -39,24 +39,8 @@ vec3 computeEllipsoidPositionWC(vec3 positionMC) { return vec3(nearestPoint.x * normalize(positionWC.xy), nearestPoint.y); } -vec3 computeFogColor(vec3 positionMC) { - vec3 rayleighColor = vec3(0.0, 0.0, 1.0); - vec3 mieColor; - float opacity; - - vec3 ellipsoidPositionWC = computeEllipsoidPositionWC(positionMC); - vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(ellipsoidPositionWC, czm_atmosphereDynamicLighting); +void applyFog(inout vec4 color, vec4 groundAtmosphereColor, vec3 lightDirection, float distanceToCamera) { - // The fog color is derived from the ground atmosphere color - czm_computeGroundAtmosphereScattering( - ellipsoidPositionWC, - lightDirection, - rayleighColor, - mieColor, - opacity - ); - - vec4 groundAtmosphereColor = czm_computeAtmosphereColor(ellipsoidPositionWC, lightDirection, rayleighColor, mieColor, opacity); vec3 fogColor = groundAtmosphereColor.rgb; // If there is dynamic lighting, apply that to the fog. @@ -67,30 +51,58 @@ vec3 computeFogColor(vec3 positionMC) { } // Tonemap if HDR rendering is disabled -#ifndef HDR - fogColor.rgb = czm_acesTonemapping(fogColor.rgb); - fogColor.rgb = czm_inverseGamma(fogColor.rgb); -#endif + #ifndef HDR + fogColor.rgb = czm_acesTonemapping(fogColor.rgb); + fogColor.rgb = czm_inverseGamma(fogColor.rgb); + #endif - return fogColor.rgb; + // Matches the constant in GlobeFS.glsl. This makes the fog falloff + // more gradual. + const float fogModifier = 0.15; + vec3 withFog = czm_fog(distanceToCamera, color.rgb, fogColor, fogModifier); + color = vec4(withFog, color.a); } void fogStage(inout vec4 color, in ProcessedAttributes attributes) { - if (!u_isInFog) { - return; - } + vec3 rayleighColor; + vec3 mieColor; + float opacity; - vec3 fogColor = computeFogColor(attributes.positionMC); + vec3 positionWC; + vec3 lightDirection; + + // When the camera is in space, compute the position per-fragment for + // more accurate ground atmosphere. All other cases will use + // + // The if condition will be added in https://github.com/CesiumGS/cesium/issues/11717 + if (false) { + positionWC = computeEllipsoidPositionWC(attributes.positionMC); + lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC, czm_atmosphereDynamicLighting); + + // The fog color is derived from the ground atmosphere color + czm_computeGroundAtmosphereScattering( + positionWC, + lightDirection, + rayleighColor, + mieColor, + opacity + ); + } else { + positionWC = attributes.positionWC; + lightDirection = czm_getDynamicAtmosphereLightDirection(positionWC, czm_atmosphereDynamicLighting); + rayleighColor = v_atmosphereRayleighColor; + mieColor = v_atmosphereMieColor; + opacity = v_atmosphereOpacity; + } - // Note: camera is far away (distance > nightFadeOutDistance), scattering is computed in the fragment shader. - // otherwise in the vertex shader. but for prototyping, I'll do everything in the FS for simplicity + //color correct rayleigh and mie colors - // Matches the constant in GlobeFS.glsl. This makes the fog falloff - // more gradual. - const float fogModifier = 0.15; - float distanceToCamera = attributes.positionEC.z; - // where to get distance? - vec3 withFog = czm_fog(distanceToCamera, color.rgb, fogColor, fogModifier); + vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); - color = vec4(withFog, color.a); + if (u_isInFog) { + float distanceToCamera = length(attributes.positionEC); + applyFog(color, groundAtmosphereColor, lightDirection, distanceToCamera); + } else { + // Ground atmosphere + } } diff --git a/packages/engine/Source/Shaders/Model/FogStageVS.glsl b/packages/engine/Source/Shaders/Model/FogStageVS.glsl new file mode 100644 index 000000000000..6a81b426105f --- /dev/null +++ b/packages/engine/Source/Shaders/Model/FogStageVS.glsl @@ -0,0 +1,12 @@ +void fogStage(ProcessedAttributes attributes) { + vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(v_positionWC, czm_atmosphereDynamicLighting); + + czm_computeGroundAtmosphereScattering( + // This assumes the geometry stage came before this. + v_positionWC, + lightDirection, + v_atmosphereRayleighColor, + v_atmosphereMieColor, + v_atmosphereOpacity + ); +} diff --git a/packages/engine/Source/Shaders/Model/GeometryStageFS.glsl b/packages/engine/Source/Shaders/Model/GeometryStageFS.glsl index 4e52d7b2087d..6f5217a11ba3 100644 --- a/packages/engine/Source/Shaders/Model/GeometryStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/GeometryStageFS.glsl @@ -3,7 +3,7 @@ void geometryStage(out ProcessedAttributes attributes) attributes.positionMC = v_positionMC; attributes.positionEC = v_positionEC; - #ifdef COMPUTE_POSITION_WC_CUSTOM_SHADER + #if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE) attributes.positionWC = v_positionWC; #endif diff --git a/packages/engine/Source/Shaders/Model/GeometryStageVS.glsl b/packages/engine/Source/Shaders/Model/GeometryStageVS.glsl index 2cda604bb8d9..58ebdff53291 100644 --- a/packages/engine/Source/Shaders/Model/GeometryStageVS.glsl +++ b/packages/engine/Source/Shaders/Model/GeometryStageVS.glsl @@ -1,4 +1,4 @@ -vec4 geometryStage(inout ProcessedAttributes attributes, mat4 modelView, mat3 normal) +vec4 geometryStage(inout ProcessedAttributes attributes, mat4 modelView, mat3 normal) { vec4 computedPosition; @@ -16,7 +16,7 @@ vec4 geometryStage(inout ProcessedAttributes attributes, mat4 modelView, mat3 no #endif // Sometimes the custom shader and/or style needs this - #if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) + #if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE) // Note that this is a 32-bit position which may result in jitter on small // scales. v_positionWC = (czm_model * vec4(positionMC, 1.0)).xyz; @@ -27,7 +27,7 @@ vec4 geometryStage(inout ProcessedAttributes attributes, mat4 modelView, mat3 no #endif #ifdef HAS_TANGENTS - v_tangentEC = normalize(normal * attributes.tangentMC); + v_tangentEC = normalize(normal * attributes.tangentMC); #endif #ifdef HAS_BITANGENTS @@ -37,6 +37,6 @@ vec4 geometryStage(inout ProcessedAttributes attributes, mat4 modelView, mat3 no // All other varyings need to be dynamically generated in // GeometryPipelineStage setDynamicVaryings(attributes); - + return computedPosition; } diff --git a/packages/engine/Source/Shaders/Model/ModelVS.glsl b/packages/engine/Source/Shaders/Model/ModelVS.glsl index e9a4eb5e63ec..b16a634161d0 100644 --- a/packages/engine/Source/Shaders/Model/ModelVS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelVS.glsl @@ -7,7 +7,7 @@ czm_modelVertexOutput defaultVertexOutput(vec3 positionMC) { return vsOutput; } -void main() +void main() { // Initialize the attributes struct with all // attributes except quantized ones. @@ -66,14 +66,14 @@ void main() // Update the position for this instance in place #ifdef HAS_INSTANCING - // The legacy instance stage is used when rendering i3dm models that + // The legacy instance stage is used when rendering i3dm models that // encode instances transforms in world space, as opposed to glTF models // that use EXT_mesh_gpu_instancing, where instance transforms are encoded // in object space. #ifdef USE_LEGACY_INSTANCING mat4 instanceModelView; mat3 instanceModelViewInverseTranspose; - + legacyInstancingStage(attributes, instanceModelView, instanceModelViewInverseTranspose); modelView = instanceModelView; @@ -104,7 +104,11 @@ void main() // Compute the final position in each coordinate system needed. // This returns the value that will be assigned to gl_Position. - vec4 positionClip = geometryStage(attributes, modelView, normal); + vec4 positionClip = geometryStage(attributes, modelView, normal); + + #ifdef HAS_FOG + fogStage(attributes); + #endif #ifdef HAS_SILHOUETTE silhouetteStage(attributes, positionClip); From 59ab44d57dadb5c246e9a73a801e0a2e5e735952 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 9 Jan 2024 16:56:29 -0500 Subject: [PATCH 29/55] Apply HSB shift --- .../Builtin/Functions/applyHSBShift.glsl | 20 +++++++++++++++++++ packages/engine/Source/Shaders/GlobeFS.glsl | 6 ++++-- .../Source/Shaders/Model/FogStageFS.glsl | 2 ++ .../Source/Shaders/SkyAtmosphereFS.glsl | 9 +-------- 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl diff --git a/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl new file mode 100644 index 000000000000..81575fecab1f --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl @@ -0,0 +1,20 @@ +/** + * Apply a color shift in HSB color space + * + * + * @param {vec3} rgb The color in RGB space. + * @param {vec3} hsbShift The amount to shift each component. The xyz components correspond to hue, saturation, and brightness. Shifting the hue by +/- 1.0 corresponds to shifting the hue by a full cycle. Saturation and brightness are clamped between 0 and 1 after the adjustment + */ +vec3 czm_applyHSBShift(vec3 rgb, vec3 hsbShift) { + // Convert rgb color to hsb + vec3 hsb = czm_RGBToHSB(rgb); + + // Perform hsb shift + // Hue cycles around so no clamp is needed. + hsb.x += hsbShift.x; // hue + hsb.y = clamp(hsb.y + hsbShift.y, 0.0, 1.0); // saturation + hsb.z = clamp(hsb.z + hsbShift.z, 0.0, 1.0); // brightness + + // Convert shifted hsb back to rgb + return czm_HSBToRGB(hsb); +} diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index e344dfec2bfc..a2bf6b48811a 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -472,8 +472,10 @@ void main() opacity = v_atmosphereOpacity; #endif - rayleighColor = colorCorrect(rayleighColor); - mieColor = colorCorrect(mieColor); + #ifdef COLOR_CORRECT + rayleighColor = czm_applyHSBShift(rayleighColor, u_hsbShift); + mieColor = czm_applyHSBShift(mieColor, u_hsbShift); + #endif vec4 groundAtmosphereColor = computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/FogStageFS.glsl index 870e040d7132..5ac40c52f2af 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/FogStageFS.glsl @@ -96,6 +96,8 @@ void fogStage(inout vec4 color, in ProcessedAttributes attributes) { } //color correct rayleigh and mie colors + rayleighColor = czm_applyHSBShift(rayleighColor, czm_atmosphereHsbShift); + mieColor = czm_applyHSBShift(mieColor, czm_atmosphereHsbShift); vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index db07b7fbd1ec..639730bba374 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -43,14 +43,7 @@ void main (void) #endif #ifdef COLOR_CORRECT - // Convert rgb color to hsb - vec3 hsb = czm_RGBToHSB(color.rgb); - // Perform hsb shift - hsb.x += u_hsbShift.x; // hue - hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation - hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness - // Convert shifted hsb back to rgb - color.rgb = czm_HSBToRGB(hsb); + color.rgb = czm_applyHSBShift(color.rgb, u_hsbShift); #endif // For the parts of the sky atmosphere that are not behind a translucent globe, From 35294416e7ddec8663928051feb69670e4f4d375 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 11:55:02 -0500 Subject: [PATCH 30/55] Fix model matrix spec --- .../Scene/Model/ModelMatrixUpdateStageSpec.js | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js index eddd956faa54..19b3b9396841 100644 --- a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js @@ -131,6 +131,22 @@ describe( leafNode._transformDirty = true; } + function applyTransform(node, transform) { + const expectedOriginalTransform = Matrix4.clone(node.originalTransform); + expect(node._transformDirty).toEqual(false); + + node.transform = Matrix4.multiplyTransformation( + node.transform, + transform, + new Matrix4() + ); + expect(node._transformDirty).toEqual(true); + + expect( + Matrix4.equals(node.originalTransform, expectedOriginalTransform) + ).toBe(true); + } + it("updates leaf nodes using node transform setter", function () { return loadAndZoomToModelAsync( { @@ -138,24 +154,16 @@ describe( }, scene ).then(function (model) { + scene.renderForSpecs(); + const sceneGraph = model.sceneGraph; const node = getStaticLeafNode(model); const primitive = node.runtimePrimitives[0]; - const drawCommand = primitive.drawCommand; - const expectedOriginalTransform = Matrix4.clone(node.transform); - expect(node._transformDirty).toEqual(false); + let drawCommand = getDrawCommand(node); - const translation = new Cartesian3(0, 5, 0); - node.transform = Matrix4.multiplyByTranslation( - node.transform, - translation, - new Matrix4() - ); - expect(node._transformDirty).toEqual(true); - expect( - Matrix4.equals(node.originalTransform, expectedOriginalTransform) - ).toBe(true); + const transform = Matrix4.fromTranslation(new Cartesian3(0, 5, 0)); + applyTransform(node, transform); const expectedComputedTransform = Matrix4.multiplyTransformation( sceneGraph.computedModelMatrix, @@ -163,9 +171,9 @@ describe( new Matrix4() ); - const expectedModelMatrix = Matrix4.multiplyByTranslation( + const expectedModelMatrix = Matrix4.multiplyTransformation( drawCommand.modelMatrix, - translation, + transform, new Matrix4() ); @@ -176,6 +184,7 @@ describe( ); scene.renderForSpecs(); + drawCommand = getDrawCommand(node); expect( Matrix4.equalsEpsilon( @@ -193,22 +202,6 @@ describe( }); }); - function applyTransform(node, transform) { - const expectedOriginalTransform = Matrix4.clone(node.originalTransform); - expect(node._transformDirty).toEqual(false); - - node.transform = Matrix4.multiplyTransformation( - node.transform, - transform, - new Matrix4() - ); - expect(node._transformDirty).toEqual(true); - - expect( - Matrix4.equals(node.originalTransform, expectedOriginalTransform) - ).toBe(true); - } - it("updates nodes with children using node transform setter", function () { return loadAndZoomToModelAsync( { @@ -407,6 +400,8 @@ describe( model.modelMatrix = Matrix4.fromUniformScale(-1); scene.renderForSpecs(); + expect(rootPrimitive.drawCommand).toBe(rootDrawCommand); + expect(rootDrawCommand.cullFace).toBe(CullFace.FRONT); expect(childDrawCommand.cullFace).toBe(CullFace.FRONT); }); From f8d35605e37d6afcdde4eec21706d156f21ec588 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 11:59:22 -0500 Subject: [PATCH 31/55] Async-ify model matrix update stage --- .../Scene/Model/ModelMatrixUpdateStageSpec.js | 428 +++++++++--------- 1 file changed, 211 insertions(+), 217 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js index 19b3b9396841..17ffae777fe4 100644 --- a/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelMatrixUpdateStageSpec.js @@ -147,264 +147,258 @@ describe( ).toBe(true); } - it("updates leaf nodes using node transform setter", function () { - return loadAndZoomToModelAsync( + it("updates leaf nodes using node transform setter", async function () { + const model = await loadAndZoomToModelAsync( { gltf: simpleSkin, }, scene - ).then(function (model) { - scene.renderForSpecs(); + ); + scene.renderForSpecs(); + + const sceneGraph = model.sceneGraph; + const node = getStaticLeafNode(model); + const primitive = node.runtimePrimitives[0]; + + let drawCommand = getDrawCommand(node); + + const transform = Matrix4.fromTranslation(new Cartesian3(0, 5, 0)); + applyTransform(node, transform); - const sceneGraph = model.sceneGraph; - const node = getStaticLeafNode(model); - const primitive = node.runtimePrimitives[0]; + const expectedComputedTransform = Matrix4.multiplyTransformation( + sceneGraph.computedModelMatrix, + node.transform, + new Matrix4() + ); - let drawCommand = getDrawCommand(node); + const expectedModelMatrix = Matrix4.multiplyTransformation( + drawCommand.modelMatrix, + transform, + new Matrix4() + ); - const transform = Matrix4.fromTranslation(new Cartesian3(0, 5, 0)); - applyTransform(node, transform); + const expectedBoundingSphere = BoundingSphere.transform( + primitive.boundingSphere, + expectedComputedTransform, + new BoundingSphere() + ); - const expectedComputedTransform = Matrix4.multiplyTransformation( - sceneGraph.computedModelMatrix, - node.transform, - new Matrix4() - ); + scene.renderForSpecs(); + drawCommand = getDrawCommand(node); - const expectedModelMatrix = Matrix4.multiplyTransformation( + expect( + Matrix4.equalsEpsilon( drawCommand.modelMatrix, - transform, - new Matrix4() - ); - - const expectedBoundingSphere = BoundingSphere.transform( - primitive.boundingSphere, - expectedComputedTransform, - new BoundingSphere() - ); - - scene.renderForSpecs(); - drawCommand = getDrawCommand(node); - - expect( - Matrix4.equalsEpsilon( - drawCommand.modelMatrix, - expectedModelMatrix, - CesiumMath.EPSILON15 - ) - ).toBe(true); - expect( - BoundingSphere.equals( - drawCommand.boundingVolume, - expectedBoundingSphere - ) - ).toBe(true); - }); + expectedModelMatrix, + CesiumMath.EPSILON15 + ) + ).toBe(true); + expect( + BoundingSphere.equals( + drawCommand.boundingVolume, + expectedBoundingSphere + ) + ).toBe(true); }); - it("updates nodes with children using node transform setter", function () { - return loadAndZoomToModelAsync( + it("updates nodes with children using node transform setter", async function () { + const model = await loadAndZoomToModelAsync( { gltf: simpleSkin, }, scene - ).then(function (model) { - modifyModel(model); - scene.renderForSpecs(); - - const rootNode = getParentRootNode(model); - const staticLeafNode = getStaticLeafNode(model); - const transformedLeafNode = getChildLeafNode(model); - - let rootDrawCommand = getDrawCommand(rootNode); - let staticDrawCommand = getDrawCommand(staticLeafNode); - let transformedDrawCommand = getDrawCommand(transformedLeafNode); - - const childTransformation = Matrix4.fromTranslation( - new Cartesian3(0, 5, 0) - ); - applyTransform(transformedLeafNode, childTransformation); - - const rootTransformation = Matrix4.fromTranslation( - new Cartesian3(12, 5, 0) - ); - applyTransform(rootNode, rootTransformation); - - const expectedRootModelMatrix = Matrix4.multiplyTransformation( - rootTransformation, - rootDrawCommand.modelMatrix, - new Matrix4() - ); - const expectedStaticLeafModelMatrix = Matrix4.clone( - staticDrawCommand.modelMatrix, - new Matrix4() - ); - - const finalTransform = new Matrix4(); - Matrix4.multiply( - rootTransformation, - childTransformation, - finalTransform - ); - const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( - finalTransform, - transformedDrawCommand.modelMatrix, - new Matrix4() - ); - - scene.renderForSpecs(); - rootDrawCommand = getDrawCommand(rootNode); - staticDrawCommand = getDrawCommand(staticLeafNode); - transformedDrawCommand = getDrawCommand(transformedLeafNode); - - expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); - expect(staticDrawCommand.modelMatrix).toEqual( - expectedStaticLeafModelMatrix - ); - expect(transformedDrawCommand.modelMatrix).toEqual( - expectedTransformedLeafModelMatrix - ); - }); + ); + + modifyModel(model); + scene.renderForSpecs(); + + const rootNode = getParentRootNode(model); + const staticLeafNode = getStaticLeafNode(model); + const transformedLeafNode = getChildLeafNode(model); + + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); + + const childTransformation = Matrix4.fromTranslation( + new Cartesian3(0, 5, 0) + ); + applyTransform(transformedLeafNode, childTransformation); + + const rootTransformation = Matrix4.fromTranslation( + new Cartesian3(12, 5, 0) + ); + applyTransform(rootNode, rootTransformation); + + const expectedRootModelMatrix = Matrix4.multiplyTransformation( + rootTransformation, + rootDrawCommand.modelMatrix, + new Matrix4() + ); + const expectedStaticLeafModelMatrix = Matrix4.clone( + staticDrawCommand.modelMatrix, + new Matrix4() + ); + + const finalTransform = new Matrix4(); + Matrix4.multiply(rootTransformation, childTransformation, finalTransform); + const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( + finalTransform, + transformedDrawCommand.modelMatrix, + new Matrix4() + ); + + scene.renderForSpecs(); + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); + + expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); + expect(staticDrawCommand.modelMatrix).toEqual( + expectedStaticLeafModelMatrix + ); + expect(transformedDrawCommand.modelMatrix).toEqual( + expectedTransformedLeafModelMatrix + ); }); - it("updates with new model matrix", function () { - return loadAndZoomToModelAsync( + it("updates with new model matrix", async function () { + const model = await loadAndZoomToModelAsync( { gltf: simpleSkin, }, scene - ).then(function (model) { - modifyModel(model); - scene.renderForSpecs(); - - const rootNode = getParentRootNode(model); - const staticLeafNode = getStaticLeafNode(model); - const transformedLeafNode = getChildLeafNode(model); - - let rootDrawCommand = getDrawCommand(rootNode); - let staticDrawCommand = getDrawCommand(staticLeafNode); - let transformedDrawCommand = getDrawCommand(transformedLeafNode); - - const expectedRootModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - rootDrawCommand.modelMatrix, - new Matrix4() - ); - const expectedStaticLeafModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - staticDrawCommand.modelMatrix, - new Matrix4() - ); - const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( - modelMatrix, - transformedDrawCommand.modelMatrix, - new Matrix4() - ); - - model.modelMatrix = modelMatrix; - scene.renderForSpecs(); - - rootDrawCommand = getDrawCommand(rootNode); - staticDrawCommand = getDrawCommand(staticLeafNode); - transformedDrawCommand = getDrawCommand(transformedLeafNode); - - expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); - expect(staticDrawCommand.modelMatrix).toEqual( - expectedStaticLeafModelMatrix - ); - expect(transformedDrawCommand.modelMatrix).toEqual( - expectedTransformedLeafModelMatrix - ); - }); + ); + + modifyModel(model); + scene.renderForSpecs(); + + const rootNode = getParentRootNode(model); + const staticLeafNode = getStaticLeafNode(model); + const transformedLeafNode = getChildLeafNode(model); + + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); + + const expectedRootModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + rootDrawCommand.modelMatrix, + new Matrix4() + ); + const expectedStaticLeafModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + staticDrawCommand.modelMatrix, + new Matrix4() + ); + const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( + modelMatrix, + transformedDrawCommand.modelMatrix, + new Matrix4() + ); + + model.modelMatrix = modelMatrix; + scene.renderForSpecs(); + + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); + + expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); + expect(staticDrawCommand.modelMatrix).toEqual( + expectedStaticLeafModelMatrix + ); + expect(transformedDrawCommand.modelMatrix).toEqual( + expectedTransformedLeafModelMatrix + ); }); - it("updates with new model matrix and model scale", function () { - return loadAndZoomToModelAsync( + it("updates with new model matrix and model scale", async function () { + const model = await loadAndZoomToModelAsync( { gltf: simpleSkin, }, scene - ).then(function (model) { - modifyModel(model); - scene.renderForSpecs(); - - const modelScale = 5.0; - const scaledModelMatrix = Matrix4.multiplyByUniformScale( - modelMatrix, - modelScale, - new Matrix4() - ); - - const rootNode = getParentRootNode(model); - const staticLeafNode = getStaticLeafNode(model); - const transformedLeafNode = getChildLeafNode(model); - - let rootDrawCommand = getDrawCommand(rootNode); - let staticDrawCommand = getDrawCommand(staticLeafNode); - let transformedDrawCommand = getDrawCommand(transformedLeafNode); - - const expectedRootModelMatrix = Matrix4.multiplyTransformation( - scaledModelMatrix, - rootDrawCommand.modelMatrix, - new Matrix4() - ); - const expectedStaticLeafModelMatrix = Matrix4.multiplyTransformation( - scaledModelMatrix, - staticDrawCommand.modelMatrix, - new Matrix4() - ); - const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( - scaledModelMatrix, - transformedDrawCommand.modelMatrix, - new Matrix4() - ); - - model.modelMatrix = modelMatrix; - model.scale = modelScale; - scene.renderForSpecs(); - rootDrawCommand = getDrawCommand(rootNode); - staticDrawCommand = getDrawCommand(staticLeafNode); - transformedDrawCommand = getDrawCommand(transformedLeafNode); - - expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); - expect(staticDrawCommand.modelMatrix).toEqual( - expectedStaticLeafModelMatrix - ); - expect(transformedDrawCommand.modelMatrix).toEqual( - expectedTransformedLeafModelMatrix - ); - }); + ); + + modifyModel(model); + scene.renderForSpecs(); + + const modelScale = 5.0; + const scaledModelMatrix = Matrix4.multiplyByUniformScale( + modelMatrix, + modelScale, + new Matrix4() + ); + + const rootNode = getParentRootNode(model); + const staticLeafNode = getStaticLeafNode(model); + const transformedLeafNode = getChildLeafNode(model); + + let rootDrawCommand = getDrawCommand(rootNode); + let staticDrawCommand = getDrawCommand(staticLeafNode); + let transformedDrawCommand = getDrawCommand(transformedLeafNode); + + const expectedRootModelMatrix = Matrix4.multiplyTransformation( + scaledModelMatrix, + rootDrawCommand.modelMatrix, + new Matrix4() + ); + const expectedStaticLeafModelMatrix = Matrix4.multiplyTransformation( + scaledModelMatrix, + staticDrawCommand.modelMatrix, + new Matrix4() + ); + const expectedTransformedLeafModelMatrix = Matrix4.multiplyTransformation( + scaledModelMatrix, + transformedDrawCommand.modelMatrix, + new Matrix4() + ); + + model.modelMatrix = modelMatrix; + model.scale = modelScale; + scene.renderForSpecs(); + rootDrawCommand = getDrawCommand(rootNode); + staticDrawCommand = getDrawCommand(staticLeafNode); + transformedDrawCommand = getDrawCommand(transformedLeafNode); + + expect(rootDrawCommand.modelMatrix).toEqual(expectedRootModelMatrix); + expect(staticDrawCommand.modelMatrix).toEqual( + expectedStaticLeafModelMatrix + ); + expect(transformedDrawCommand.modelMatrix).toEqual( + expectedTransformedLeafModelMatrix + ); }); - it("updates render state cull face when scale is negative", function () { - return loadAndZoomToModelAsync( + it("updates render state cull face when scale is negative", async function () { + const model = await loadAndZoomToModelAsync( { gltf: simpleSkin, }, scene - ).then(function (model) { - modifyModel(model); + ); + modifyModel(model); - const rootNode = getParentRootNode(model); - const childNode = getChildLeafNode(model); + const rootNode = getParentRootNode(model); + const childNode = getChildLeafNode(model); - const rootPrimitive = rootNode.runtimePrimitives[0]; - const childPrimitive = childNode.runtimePrimitives[0]; + const rootPrimitive = rootNode.runtimePrimitives[0]; + const childPrimitive = childNode.runtimePrimitives[0]; - const rootDrawCommand = rootPrimitive.drawCommand; - const childDrawCommand = childPrimitive.drawCommand; + const rootDrawCommand = rootPrimitive.drawCommand; + const childDrawCommand = childPrimitive.drawCommand; - expect(rootDrawCommand.cullFace).toBe(CullFace.BACK); - expect(childDrawCommand.cullFace).toBe(CullFace.BACK); + expect(rootDrawCommand.cullFace).toBe(CullFace.BACK); + expect(childDrawCommand.cullFace).toBe(CullFace.BACK); - model.modelMatrix = Matrix4.fromUniformScale(-1); - scene.renderForSpecs(); + model.modelMatrix = Matrix4.fromUniformScale(-1); + scene.renderForSpecs(); - expect(rootPrimitive.drawCommand).toBe(rootDrawCommand); + expect(rootPrimitive.drawCommand).toBe(rootDrawCommand); - expect(rootDrawCommand.cullFace).toBe(CullFace.FRONT); - expect(childDrawCommand.cullFace).toBe(CullFace.FRONT); - }); + expect(rootDrawCommand.cullFace).toBe(CullFace.FRONT); + expect(childDrawCommand.cullFace).toBe(CullFace.FRONT); }); }, "WebGL" From af067852408eb617d633729998a0e92b662cd1de Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 12:41:08 -0500 Subject: [PATCH 32/55] Move pipeline stage specs to ModelSceneGraph --- .../Scene/Model/ModelRuntimePrimitiveSpec.js | 77 ------------------- .../Specs/Scene/Model/ModelSceneGraphSpec.js | 51 ++++++++++++ 2 files changed, 51 insertions(+), 77 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js index 8dd9ddb669b3..6e73cc3fbc8b 100644 --- a/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelRuntimePrimitiveSpec.js @@ -7,7 +7,6 @@ import { CustomShaderMode, CustomShaderPipelineStage, FeatureIdPipelineStage, - FogPipelineStage, CPUStylingPipelineStage, DequantizationPipelineStage, GeometryPipelineStage, @@ -993,80 +992,4 @@ describe("Scene/Model/ModelRuntimePrimitive", function () { primitive.configurePipeline(frameState); verifyExpectedStages(primitive.pipelineStages, expectedStages); }); - - it("does not add fog stage when fog is not enabled", function () { - const primitive = new ModelRuntimePrimitive({ - primitive: mockPrimitive, - node: mockNode, - model: mockModel, - }); - const frameState = createFrameState(mockWebgl2Context); - frameState.fog.enabled = false; - frameState.fog.renderable = false; - - const expectedStages = [ - GeometryPipelineStage, - MaterialPipelineStage, - FeatureIdPipelineStage, - MetadataPipelineStage, - LightingPipelineStage, - PickingPipelineStage, - AlphaPipelineStage, - PrimitiveStatisticsPipelineStage, - ]; - - primitive.configurePipeline(frameState); - verifyExpectedStages(primitive.pipelineStages, expectedStages); - }); - - it("does not add fog stage when fog is not renderable", function () { - const primitive = new ModelRuntimePrimitive({ - primitive: mockPrimitive, - node: mockNode, - model: mockModel, - }); - const frameState = createFrameState(mockWebgl2Context); - frameState.fog.enabled = true; - frameState.fog.renderable = false; - - const expectedStages = [ - GeometryPipelineStage, - MaterialPipelineStage, - FeatureIdPipelineStage, - MetadataPipelineStage, - LightingPipelineStage, - PickingPipelineStage, - AlphaPipelineStage, - PrimitiveStatisticsPipelineStage, - ]; - - primitive.configurePipeline(frameState); - verifyExpectedStages(primitive.pipelineStages, expectedStages); - }); - - it("configures pipeline stages when fog is enabled and renderable", function () { - const primitive = new ModelRuntimePrimitive({ - primitive: mockPrimitive, - node: mockNode, - model: mockModel, - }); - const frameState = createFrameState(mockWebgl2Context); - frameState.fog.enabled = true; - frameState.fog.renderable = true; - - const expectedStages = [ - GeometryPipelineStage, - MaterialPipelineStage, - FeatureIdPipelineStage, - MetadataPipelineStage, - LightingPipelineStage, - PickingPipelineStage, - AlphaPipelineStage, - FogPipelineStage, - PrimitiveStatisticsPipelineStage, - ]; - - primitive.configurePipeline(frameState); - verifyExpectedStages(primitive.pipelineStages, expectedStages); - }); }); diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index b5196ff355d3..c28d5b0aece0 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -4,6 +4,8 @@ import { Color, CustomShader, CustomShaderPipelineStage, + Fog, + FogPipelineStage, Math as CesiumMath, Matrix4, ModelColorPipelineStage, @@ -41,6 +43,7 @@ describe( afterEach(function () { scene.primitives.removeAll(); + scene.fog = new Fog(); ResourceCache.clearForSpecs(); }); @@ -353,6 +356,54 @@ describe( }); }); + it("does not add fog stage when fog is not enabled", function () { + spyOn(FogPipelineStage, "process"); + scene.fog.enabled = false; + scene.fog.renderable = false; + return loadAndZoomToModelAsync( + { + gltf: buildingsMetadata, + }, + scene + ).then(function (model) { + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).not.toHaveBeenCalled(); + }); + }); + + it("does not add fog stage when fog is not renderable", function () { + spyOn(FogPipelineStage, "process"); + scene.fog.enabled = true; + scene.fog.renderable = false; + return loadAndZoomToModelAsync( + { + gltf: buildingsMetadata, + }, + scene + ).then(function (model) { + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).not.toHaveBeenCalled(); + }); + }); + + it("adds FogPipelineStage when fog is enabled and renderable", function () { + spyOn(FogPipelineStage, "process"); + scene.fog.enabled = true; + scene.fog.renderable = true; + return loadAndZoomToModelAsync( + { + gltf: buildingsMetadata, + }, + scene + ).then(function (model) { + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).toHaveBeenCalled(); + }); + }); + it("pushDrawCommands ignores hidden nodes", function () { return loadAndZoomToModelAsync( { From 4e6b942c5bff73c704a0a8e6bc17af91f396d4da Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 12:44:48 -0500 Subject: [PATCH 33/55] fix auto imports --- packages/engine/Source/Scene/Atmosphere.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index d5b2c4321547..ca2ce6d727b2 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -1,5 +1,5 @@ -import Cartesian3 from "../Core/Cartesian3"; -import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType"; +import Cartesian3 from "../Core/Cartesian3.js"; +import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; function Atmosphere() { /** From cdc24fc57dbf0488adeb985d281f178886a2460c Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 13:25:25 -0500 Subject: [PATCH 34/55] Add test for czm_fogMinimumBrightness --- .../engine/Source/Renderer/UniformState.js | 1 + .../Specs/Renderer/AutomaticUniformSpec.js | 32 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index d4745818c480..7c0c56924c62 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1514,6 +1514,7 @@ UniformState.prototype.update = function (frameState) { } this._fogDensity = frameState.fog.density; + this._fogMinimumBrightness = frameState.fog.minimumBrightness; const atmosphere = frameState.atmosphere; diff --git a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js index 349687d5a1ba..c93eac8870d3 100644 --- a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js +++ b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js @@ -1793,7 +1793,7 @@ describe( undefined, undefined, undefined, - // Explicit position and direction as the default position of (0, 0, 0) + // Provide position and direction because the default position of (0, 0, 0) // will lead to a divide by zero when updating fog below. new Cartesian3(1.0, 0.0, 0.0), new Cartesian3(0.0, 1.0, 0.0) @@ -1816,6 +1816,36 @@ describe( }).contextToRender(); }); + it("has czm_fogMinimumBrightness", function () { + const frameState = createFrameState( + context, + createMockCamera( + undefined, + undefined, + undefined, + // Provide position and direction because the default position of (0, 0, 0) + // will lead to a divide by zero when updating fog below + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0) + ) + ); + const fog = new Fog(); + fog.minimumBrightness = 0.25; + fog.update(frameState); + + const us = context.uniformState; + us.update(frameState); + + const fs = + "void main() {" + + " out_FragColor = vec4(czm_fogMinimumBrightness == 0.25);" + + "}"; + expect({ + context, + fragmentShader: fs, + }).contextToRender(); + }); + it("has czm_atmosphereHsbShift", function () { const frameState = createFrameState(context, createMockCamera()); const atmosphere = new Atmosphere(); From 19cd032854c14cfd34ee05ca00483fcafe046a09 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 14:12:18 -0500 Subject: [PATCH 35/55] Add specs for FogPipelineStage --- .../Source/Scene/Model/FogPipelineStage.js | 4 +- .../Specs/Scene/Model/FogPipelineStageSpec.js | 113 ++++++++++++++++++ .../Specs/Scene/Model/ModelSceneGraphSpec.js | 13 +- 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/FogPipelineStage.js index 971f6cb03295..5a323e59b114 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/FogPipelineStage.js @@ -18,6 +18,7 @@ const FogPipelineStage = { FogPipelineStage.process = function (renderResources, model, frameState) { const shaderBuilder = renderResources.shaderBuilder; + shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.BOTH); shaderBuilder.addDefine( "COMPUTE_POSITION_WC_ATMOSPHERE", undefined, @@ -28,11 +29,10 @@ FogPipelineStage.process = function (renderResources, model, frameState) { shaderBuilder.addVarying("vec3", "v_atmosphereMieColor"); shaderBuilder.addVarying("float", "v_atmosphereOpacity"); - shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.BOTH); shaderBuilder.addVertexLines([FogStageVS]); shaderBuilder.addFragmentLines([FogStageFS]); - // Add a uniform so fog is only calculated when the effect would + // Add a uniform so fog is only calculated when the efcfect would // be non-negligible For example when the camera is in space, fog density decreases // to 0 so fog shouldn't be rendered. Since this state may change rapidly if // the camera is moving, this is implemented as a uniform, not a define. diff --git a/packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js new file mode 100644 index 000000000000..281ed762ab02 --- /dev/null +++ b/packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js @@ -0,0 +1,113 @@ +import { + _shadersFogStageFS, + _shadersFogStageVS, + Cartesian3, + FogPipelineStage, + ShaderBuilder, +} from "../../../index.js"; +import ShaderBuilderTester from "../../../../../Specs/ShaderBuilderTester.js"; + +describe("Scene/Model/FogPipelineStage", function () { + const mockModel = { + boundingSphere: { + center: Cartesian3.fromDegrees(0, 0, 0), + }, + }; + + function mockFrameState() { + return { + camera: { + // position the camera a little bit east of the model + // and slightly above + position: Cartesian3.fromDegrees(0.01, 0, 1), + }, + fog: { + density: 2e-4, + }, + }; + } + + function mockRenderResources() { + return { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + }; + } + + it("Configures shader", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + FogPipelineStage.process(renderResources, mockModel, frameState); + + const shaderBuilder = renderResources.shaderBuilder; + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "HAS_FOG", + "COMPUTE_POSITION_WC_ATMOSPHERE", + ]); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_FOG", + "COMPUTE_POSITION_WC_ATMOSPHERE", + ]); + + ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ + "vec3 v_atmosphereRayleighColor;", + "vec3 v_atmosphereMieColor;", + "float v_atmosphereOpacity;", + ]); + + ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [ + _shadersFogStageVS, + ]); + ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ + _shadersFogStageFS, + ]); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform bool u_isInFog;", + ]); + }); + + it("u_isInFog() is false if the camera is at the model center", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + frameState.camera.position = Cartesian3.clone( + mockModel.boundingSphere.center, + frameState.camera.position + ); + + FogPipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(false); + }); + + it("u_isInFog() is false if the camera is in space", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + // For this case, the fact that Fog decreases the density to 0 when + // the camera is far above the model is what causes u_isInFog to + // be false. + frameState.camera.position = Cartesian3.fromDegrees(0.001, 0, 100000); + frameState.fog.density = 0; + + FogPipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(false); + }); + + it("u_isInFog() is true when the tile is in fog", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + FogPipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(true); + }); +}); diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index c28d5b0aece0..2b8eea6ae815 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -388,20 +388,19 @@ describe( }); }); - it("adds FogPipelineStage when fog is enabled and renderable", function () { + it("adds fog stage when fog is enabled and renderable", async function () { spyOn(FogPipelineStage, "process"); scene.fog.enabled = true; scene.fog.renderable = true; - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.customShader = new CustomShader(); - model.update(scene.frameState); - expect(FogPipelineStage.process).toHaveBeenCalled(); - }); + ); + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).toHaveBeenCalled(); }); it("pushDrawCommands ignores hidden nodes", function () { From 7e4e6e1fc7a5ba96de68df8bd1c21ce4e12a8d72 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 14:23:45 -0500 Subject: [PATCH 36/55] async-ify model scene graph tests --- .../Specs/Scene/Model/ModelSceneGraphSpec.js | 500 +++++++++--------- 1 file changed, 242 insertions(+), 258 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index 2b8eea6ae815..05f86fcf865d 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -47,79 +47,77 @@ describe( ResourceCache.clearForSpecs(); }); - it("creates runtime nodes and runtime primitives from a model", function () { - return loadAndZoomToModelAsync({ gltf: vertexColorGltfUrl }, scene).then( - function (model) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; + it("creates runtime nodes and runtime primitives from a model", async function () { + const model = await loadAndZoomToModelAsync( + { gltf: vertexColorGltfUrl }, + scene + ); + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; - expect(sceneGraph).toBeDefined(); + expect(sceneGraph).toBeDefined(); - const runtimeNodes = sceneGraph._runtimeNodes; - expect(runtimeNodes.length).toEqual(components.nodes.length); + const runtimeNodes = sceneGraph._runtimeNodes; + expect(runtimeNodes.length).toEqual(components.nodes.length); - expect(runtimeNodes[0].runtimePrimitives.length).toEqual(1); - expect(runtimeNodes[1].runtimePrimitives.length).toEqual(1); - } - ); + expect(runtimeNodes[0].runtimePrimitives.length).toEqual(1); + expect(runtimeNodes[1].runtimePrimitives.length).toEqual(1); }); - it("builds draw commands for all opaque styled features", function () { + it("builds draw commands for all opaque styled features", async function () { const style = new Cesium3DTileStyle({ color: { conditions: [["${height} > 1", "color('red')"]], }, }); - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.style = style; + ); + model.style = style; - const frameState = scene.frameState; - const commandList = frameState.commandList; - commandList.length = 0; + const frameState = scene.frameState; + const commandList = frameState.commandList; + commandList.length = 0; - // Reset the draw commands so we can inspect the draw command generation. - model._drawCommandsBuilt = false; - scene.renderForSpecs(); + // Reset the draw commands so we can inspect the draw command generation. + model._drawCommandsBuilt = false; + scene.renderForSpecs(); - expect(commandList.length).toEqual(1); - expect(commandList[0].pass).toEqual(Pass.OPAQUE); - }); + expect(commandList.length).toEqual(1); + expect(commandList[0].pass).toEqual(Pass.OPAQUE); }); - it("builds draw commands for all translucent styled features", function () { + it("builds draw commands for all translucent styled features", async function () { const style = new Cesium3DTileStyle({ color: { conditions: [["${height} > 1", "color('red', 0.1)"]], }, }); - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.style = style; + ); + model.style = style; - const frameState = scene.frameState; - const commandList = frameState.commandList; - commandList.length = 0; + const frameState = scene.frameState; + const commandList = frameState.commandList; + commandList.length = 0; - // Reset the draw commands so we can inspect the draw command generation. - model._drawCommandsBuilt = false; - scene.renderForSpecs(); + // Reset the draw commands so we can inspect the draw command generation. + model._drawCommandsBuilt = false; + scene.renderForSpecs(); - expect(commandList.length).toEqual(1); - expect(commandList[0].pass).toEqual(Pass.TRANSLUCENT); - }); + expect(commandList.length).toEqual(1); + expect(commandList[0].pass).toEqual(Pass.TRANSLUCENT); }); - it("builds draw commands for both opaque and translucent styled features", function () { + it("builds draw commands for both opaque and translucent styled features", async function () { const style = new Cesium3DTileStyle({ color: { conditions: [ @@ -129,263 +127,250 @@ describe( }, }); - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.style = style; + ); + model.style = style; - const frameState = scene.frameState; - const commandList = frameState.commandList; - commandList.length = 0; + const frameState = scene.frameState; + const commandList = frameState.commandList; + commandList.length = 0; - // Reset the draw commands so we can inspect the draw command generation. - model._drawCommandsBuilt = false; - scene.renderForSpecs(); + // Reset the draw commands so we can inspect the draw command generation. + model._drawCommandsBuilt = false; + scene.renderForSpecs(); - expect(commandList.length).toEqual(2); - expect(commandList[0].pass).toEqual(Pass.TRANSLUCENT); - expect(commandList[1].pass).toEqual(Pass.OPAQUE); - }); + expect(commandList.length).toEqual(2); + expect(commandList[0].pass).toEqual(Pass.TRANSLUCENT); + expect(commandList[1].pass).toEqual(Pass.OPAQUE); }); - it("builds draw commands for each primitive", function () { + it("builds draw commands for each primitive", async function () { spyOn(ModelSceneGraph.prototype, "buildDrawCommands").and.callThrough(); spyOn(ModelSceneGraph.prototype, "pushDrawCommands").and.callThrough(); - return loadAndZoomToModelAsync({ gltf: parentGltfUrl }, scene).then( - function (model) { - const sceneGraph = model._sceneGraph; - const runtimeNodes = sceneGraph._runtimeNodes; - - let primitivesCount = 0; - for (let i = 0; i < runtimeNodes.length; i++) { - primitivesCount += runtimeNodes[i].runtimePrimitives.length; - } - - const frameState = scene.frameState; - frameState.commandList.length = 0; - scene.renderForSpecs(); - expect( - ModelSceneGraph.prototype.buildDrawCommands - ).toHaveBeenCalled(); - expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); - expect(frameState.commandList.length).toEqual(primitivesCount); - - // Reset the draw command list to see if they're re-built. - model._drawCommandsBuilt = false; - frameState.commandList.length = 0; - scene.renderForSpecs(); - expect( - ModelSceneGraph.prototype.buildDrawCommands - ).toHaveBeenCalled(); - expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); - expect(frameState.commandList.length).toEqual(primitivesCount); - } + const model = await loadAndZoomToModelAsync( + { gltf: parentGltfUrl }, + scene ); + + const sceneGraph = model._sceneGraph; + const runtimeNodes = sceneGraph._runtimeNodes; + + let primitivesCount = 0; + for (let i = 0; i < runtimeNodes.length; i++) { + primitivesCount += runtimeNodes[i].runtimePrimitives.length; + } + + const frameState = scene.frameState; + frameState.commandList.length = 0; + scene.renderForSpecs(); + expect(ModelSceneGraph.prototype.buildDrawCommands).toHaveBeenCalled(); + expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); + expect(frameState.commandList.length).toEqual(primitivesCount); + + // Reset the draw command list to see if they're re-built. + model._drawCommandsBuilt = false; + frameState.commandList.length = 0; + scene.renderForSpecs(); + expect(ModelSceneGraph.prototype.buildDrawCommands).toHaveBeenCalled(); + expect(ModelSceneGraph.prototype.pushDrawCommands).toHaveBeenCalled(); + expect(frameState.commandList.length).toEqual(primitivesCount); }); - it("stores runtime nodes correctly", function () { - return loadAndZoomToModelAsync({ gltf: parentGltfUrl }, scene).then( - function (model) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; - const runtimeNodes = sceneGraph._runtimeNodes; + it("stores runtime nodes correctly", async function () { + const model = await loadAndZoomToModelAsync( + { gltf: parentGltfUrl }, + scene + ); - expect(runtimeNodes[0].node).toEqual(components.nodes[0]); - expect(runtimeNodes[1].node).toEqual(components.nodes[1]); + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; + const runtimeNodes = sceneGraph._runtimeNodes; - const rootNodes = sceneGraph._rootNodes; - expect(rootNodes[0]).toEqual(0); - } - ); + expect(runtimeNodes[0].node).toEqual(components.nodes[0]); + expect(runtimeNodes[1].node).toEqual(components.nodes[1]); + + const rootNodes = sceneGraph._rootNodes; + expect(rootNodes[0]).toEqual(0); }); - it("propagates node transforms correctly", function () { - return loadAndZoomToModelAsync( + it("propagates node transforms correctly", async function () { + const model = await loadAndZoomToModelAsync( { gltf: parentGltfUrl, upAxis: Axis.Z, forwardAxis: Axis.X, }, scene - ).then(function (model) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; - const runtimeNodes = sceneGraph._runtimeNodes; - - expect(components.upAxis).toEqual(Axis.Z); - expect(components.forwardAxis).toEqual(Axis.X); - - const parentTransform = ModelUtility.getNodeTransform( - components.nodes[0] - ); - const childTransform = ModelUtility.getNodeTransform( - components.nodes[1] - ); - expect(runtimeNodes[0].transform).toEqual(parentTransform); - expect(runtimeNodes[0].transformToRoot).toEqual(Matrix4.IDENTITY); - expect(runtimeNodes[1].transform).toEqual(childTransform); - expect(runtimeNodes[1].transformToRoot).toEqual(parentTransform); - }); + ); + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; + const runtimeNodes = sceneGraph._runtimeNodes; + + expect(components.upAxis).toEqual(Axis.Z); + expect(components.forwardAxis).toEqual(Axis.X); + + const parentTransform = ModelUtility.getNodeTransform( + components.nodes[0] + ); + const childTransform = ModelUtility.getNodeTransform(components.nodes[1]); + expect(runtimeNodes[0].transform).toEqual(parentTransform); + expect(runtimeNodes[0].transformToRoot).toEqual(Matrix4.IDENTITY); + expect(runtimeNodes[1].transform).toEqual(childTransform); + expect(runtimeNodes[1].transformToRoot).toEqual(parentTransform); }); - it("creates runtime skin from model", function () { - return loadAndZoomToModelAsync({ gltf: simpleSkinGltfUrl }, scene).then( - function (model) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; - const runtimeNodes = sceneGraph._runtimeNodes; - - expect(runtimeNodes[0].node).toEqual(components.nodes[0]); - expect(runtimeNodes[1].node).toEqual(components.nodes[1]); - expect(runtimeNodes[2].node).toEqual(components.nodes[2]); - - const rootNodes = sceneGraph._rootNodes; - expect(rootNodes[0]).toEqual(0); - expect(rootNodes[1]).toEqual(1); - - const runtimeSkins = sceneGraph._runtimeSkins; - expect(runtimeSkins[0].skin).toEqual(components.skins[0]); - expect(runtimeSkins[0].joints).toEqual([ - runtimeNodes[1], - runtimeNodes[2], - ]); - expect(runtimeSkins[0].jointMatrices.length).toEqual(2); - - const skinnedNodes = sceneGraph._skinnedNodes; - expect(skinnedNodes[0]).toEqual(0); - - expect(runtimeNodes[0].computedJointMatrices.length).toEqual(2); - } + it("creates runtime skin from model", async function () { + const model = await loadAndZoomToModelAsync( + { gltf: simpleSkinGltfUrl }, + scene ); + + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; + const runtimeNodes = sceneGraph._runtimeNodes; + + expect(runtimeNodes[0].node).toEqual(components.nodes[0]); + expect(runtimeNodes[1].node).toEqual(components.nodes[1]); + expect(runtimeNodes[2].node).toEqual(components.nodes[2]); + + const rootNodes = sceneGraph._rootNodes; + expect(rootNodes[0]).toEqual(0); + expect(rootNodes[1]).toEqual(1); + + const runtimeSkins = sceneGraph._runtimeSkins; + expect(runtimeSkins[0].skin).toEqual(components.skins[0]); + expect(runtimeSkins[0].joints).toEqual([ + runtimeNodes[1], + runtimeNodes[2], + ]); + expect(runtimeSkins[0].jointMatrices.length).toEqual(2); + + const skinnedNodes = sceneGraph._skinnedNodes; + expect(skinnedNodes[0]).toEqual(0); + + expect(runtimeNodes[0].computedJointMatrices.length).toEqual(2); }); - it("creates articulation from model", function () { - return loadAndZoomToModelAsync({ gltf: boxArticulationsUrl }, scene).then( - function (model) { - const sceneGraph = model._sceneGraph; - const components = sceneGraph._components; - const runtimeNodes = sceneGraph._runtimeNodes; - - expect(runtimeNodes[0].node).toEqual(components.nodes[0]); - - const rootNodes = sceneGraph._rootNodes; - expect(rootNodes[0]).toEqual(0); - - const runtimeArticulations = sceneGraph._runtimeArticulations; - const runtimeArticulation = - runtimeArticulations["SampleArticulation"]; - expect(runtimeArticulation).toBeDefined(); - expect(runtimeArticulation.name).toBe("SampleArticulation"); - expect(runtimeArticulation.runtimeNodes.length).toBe(1); - expect(runtimeArticulation.runtimeStages.length).toBe(10); - } + it("creates articulation from model", async function () { + const model = await loadAndZoomToModelAsync( + { gltf: boxArticulationsUrl }, + scene ); + + const sceneGraph = model._sceneGraph; + const components = sceneGraph._components; + const runtimeNodes = sceneGraph._runtimeNodes; + + expect(runtimeNodes[0].node).toEqual(components.nodes[0]); + + const rootNodes = sceneGraph._rootNodes; + expect(rootNodes[0]).toEqual(0); + + const runtimeArticulations = sceneGraph._runtimeArticulations; + const runtimeArticulation = runtimeArticulations["SampleArticulation"]; + expect(runtimeArticulation).toBeDefined(); + expect(runtimeArticulation.name).toBe("SampleArticulation"); + expect(runtimeArticulation.runtimeNodes.length).toBe(1); + expect(runtimeArticulation.runtimeStages.length).toBe(10); }); - it("applies articulations", function () { - return loadAndZoomToModelAsync( + it("applies articulations", async function () { + const model = await loadAndZoomToModelAsync( { gltf: boxArticulationsUrl, }, scene - ).then(function (model) { - const sceneGraph = model._sceneGraph; - const runtimeNodes = sceneGraph._runtimeNodes; - const rootNode = runtimeNodes[0]; - - expect(rootNode.transform).toEqual(rootNode.originalTransform); - - sceneGraph.setArticulationStage("SampleArticulation MoveX", 1.0); - sceneGraph.setArticulationStage("SampleArticulation MoveY", 2.0); - sceneGraph.setArticulationStage("SampleArticulation MoveZ", 3.0); - sceneGraph.setArticulationStage("SampleArticulation Yaw", 4.0); - sceneGraph.setArticulationStage("SampleArticulation Pitch", 5.0); - sceneGraph.setArticulationStage("SampleArticulation Roll", 6.0); - sceneGraph.setArticulationStage("SampleArticulation Size", 0.9); - sceneGraph.setArticulationStage("SampleArticulation SizeX", 0.8); - sceneGraph.setArticulationStage("SampleArticulation SizeY", 0.7); - sceneGraph.setArticulationStage("SampleArticulation SizeZ", 0.6); - - // Articulations shouldn't affect the node until applyArticulations is called. - expect(rootNode.transform).toEqual(rootNode.originalTransform); - - sceneGraph.applyArticulations(); - - // prettier-ignore - const expected = [ - 0.714769048324, -0.0434061192623, -0.074974104652, 0, - -0.061883302957, 0.0590679731276, -0.624164586760, 0, - 0.037525155822, 0.5366347296529, 0.047064101083, 0, - 1, 3, -2, 1, - ]; - - expect(rootNode.transform).toEqualEpsilon( - expected, - CesiumMath.EPSILON10 - ); - }); + ); + const sceneGraph = model._sceneGraph; + const runtimeNodes = sceneGraph._runtimeNodes; + const rootNode = runtimeNodes[0]; + + expect(rootNode.transform).toEqual(rootNode.originalTransform); + + sceneGraph.setArticulationStage("SampleArticulation MoveX", 1.0); + sceneGraph.setArticulationStage("SampleArticulation MoveY", 2.0); + sceneGraph.setArticulationStage("SampleArticulation MoveZ", 3.0); + sceneGraph.setArticulationStage("SampleArticulation Yaw", 4.0); + sceneGraph.setArticulationStage("SampleArticulation Pitch", 5.0); + sceneGraph.setArticulationStage("SampleArticulation Roll", 6.0); + sceneGraph.setArticulationStage("SampleArticulation Size", 0.9); + sceneGraph.setArticulationStage("SampleArticulation SizeX", 0.8); + sceneGraph.setArticulationStage("SampleArticulation SizeY", 0.7); + sceneGraph.setArticulationStage("SampleArticulation SizeZ", 0.6); + + // Articulations shouldn't affect the node until applyArticulations is called. + expect(rootNode.transform).toEqual(rootNode.originalTransform); + + sceneGraph.applyArticulations(); + + // prettier-ignore + const expected = [ + 0.714769048324, -0.0434061192623, -0.074974104652, 0, + -0.061883302957, 0.0590679731276, -0.624164586760, 0, + 0.037525155822, 0.5366347296529, 0.047064101083, 0, + 1, 3, -2, 1, + ]; + + expect(rootNode.transform).toEqualEpsilon(expected, CesiumMath.EPSILON10); }); - it("adds ModelColorPipelineStage when color is set on the model", function () { + it("adds ModelColorPipelineStage when color is set on the model", async function () { spyOn(ModelColorPipelineStage, "process"); - return loadAndZoomToModelAsync( + await loadAndZoomToModelAsync( { color: Color.RED, gltf: parentGltfUrl, }, scene - ).then(function () { - expect(ModelColorPipelineStage.process).toHaveBeenCalled(); - }); + ); + expect(ModelColorPipelineStage.process).toHaveBeenCalled(); }); - it("adds CustomShaderPipelineStage when customShader is set on the model", function () { + it("adds CustomShaderPipelineStage when customShader is set on the model", async function () { spyOn(CustomShaderPipelineStage, "process"); - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.customShader = new CustomShader(); - model.update(scene.frameState); - expect(CustomShaderPipelineStage.process).toHaveBeenCalled(); - }); + ); + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(CustomShaderPipelineStage.process).toHaveBeenCalled(); }); - it("does not add fog stage when fog is not enabled", function () { + it("does not add fog stage when fog is not enabled", async function () { spyOn(FogPipelineStage, "process"); scene.fog.enabled = false; scene.fog.renderable = false; - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.customShader = new CustomShader(); - model.update(scene.frameState); - expect(FogPipelineStage.process).not.toHaveBeenCalled(); - }); + ); + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).not.toHaveBeenCalled(); }); - it("does not add fog stage when fog is not renderable", function () { + it("does not add fog stage when fog is not renderable", async function () { spyOn(FogPipelineStage, "process"); scene.fog.enabled = true; scene.fog.renderable = false; - return loadAndZoomToModelAsync( + const model = await loadAndZoomToModelAsync( { gltf: buildingsMetadata, }, scene - ).then(function (model) { - model.customShader = new CustomShader(); - model.update(scene.frameState); - expect(FogPipelineStage.process).not.toHaveBeenCalled(); - }); + ); + model.customShader = new CustomShader(); + model.update(scene.frameState); + expect(FogPipelineStage.process).not.toHaveBeenCalled(); }); it("adds fog stage when fog is enabled and renderable", async function () { @@ -403,41 +388,40 @@ describe( expect(FogPipelineStage.process).toHaveBeenCalled(); }); - it("pushDrawCommands ignores hidden nodes", function () { - return loadAndZoomToModelAsync( + it("pushDrawCommands ignores hidden nodes", async function () { + const model = await loadAndZoomToModelAsync( { gltf: duckUrl, }, scene - ).then(function (model) { - const frameState = scene.frameState; - const commandList = frameState.commandList; - - const sceneGraph = model._sceneGraph; - const rootNode = sceneGraph._runtimeNodes[0]; - const meshNode = sceneGraph._runtimeNodes[2]; - - expect(rootNode.show).toBe(true); - expect(meshNode.show).toBe(true); - - sceneGraph.pushDrawCommands(frameState); - const originalLength = commandList.length; - expect(originalLength).not.toEqual(0); - - commandList.length = 0; - meshNode.show = false; - sceneGraph.pushDrawCommands(frameState); - expect(commandList.length).toEqual(0); - - meshNode.show = true; - rootNode.show = false; - sceneGraph.pushDrawCommands(frameState); - expect(commandList.length).toEqual(0); - - rootNode.show = true; - sceneGraph.pushDrawCommands(frameState); - expect(commandList.length).toEqual(originalLength); - }); + ); + const frameState = scene.frameState; + const commandList = frameState.commandList; + + const sceneGraph = model._sceneGraph; + const rootNode = sceneGraph._runtimeNodes[0]; + const meshNode = sceneGraph._runtimeNodes[2]; + + expect(rootNode.show).toBe(true); + expect(meshNode.show).toBe(true); + + sceneGraph.pushDrawCommands(frameState); + const originalLength = commandList.length; + expect(originalLength).not.toEqual(0); + + commandList.length = 0; + meshNode.show = false; + sceneGraph.pushDrawCommands(frameState); + expect(commandList.length).toEqual(0); + + meshNode.show = true; + rootNode.show = false; + sceneGraph.pushDrawCommands(frameState); + expect(commandList.length).toEqual(0); + + rootNode.show = true; + sceneGraph.pushDrawCommands(frameState); + expect(commandList.length).toEqual(originalLength); }); it("throws for undefined options.model", function () { From 393e7fc59cfa44133334aeb619aad0bb67e538dd Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 14:28:00 -0500 Subject: [PATCH 37/55] Update documentation --- packages/engine/Source/Scene/SkyAtmosphere.js | 2 +- .../Source/Shaders/Builtin/Functions/applyHSBShift.glsl | 5 +++-- .../Source/Shaders/Builtin/Functions/approximateTanh.glsl | 2 +- .../Source/Shaders/Builtin/Functions/computeScattering.glsl | 2 -- packages/engine/Source/Shaders/Model/ModelVS.glsl | 1 + 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Scene/SkyAtmosphere.js b/packages/engine/Source/Scene/SkyAtmosphere.js index c97a7a88e098..628737ce5610 100644 --- a/packages/engine/Source/Scene/SkyAtmosphere.js +++ b/packages/engine/Source/Scene/SkyAtmosphere.js @@ -218,7 +218,7 @@ Object.defineProperties(SkyAtmosphere.prototype, { /** * Set the dynamic lighting enum value for the shader - * @param {DynamicAtmosphereLightingType} lightingEnum + * @param {DynamicAtmosphereLightingType} lightingEnum The enum that determines the dynamic atmosphere light source * * @private */ diff --git a/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl index 81575fecab1f..30b9424eb4ea 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl @@ -1,9 +1,10 @@ /** - * Apply a color shift in HSB color space - * + * Apply a HSB color shift to an RGB color. * * @param {vec3} rgb The color in RGB space. * @param {vec3} hsbShift The amount to shift each component. The xyz components correspond to hue, saturation, and brightness. Shifting the hue by +/- 1.0 corresponds to shifting the hue by a full cycle. Saturation and brightness are clamped between 0 and 1 after the adjustment + * + * @return {vec3} The RGB color after shifting in HSB space and clamping saturation and brightness to a valid range. */ vec3 czm_applyHSBShift(vec3 rgb, vec3 hsbShift) { // Convert rgb color to hsb diff --git a/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl b/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl index f956b14274c3..4c8132762cf9 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/approximateTanh.glsl @@ -6,5 +6,5 @@ */ float czm_approximateTanh(float x) { float x2 = x * x; - return max(-1.0, min(+1.0, x * (27.0 + x2) / (27.0 + 9.0 * x2))); + return max(-1.0, min(1.0, x * (27.0 + x2) / (27.0 + 9.0 * x2))); } diff --git a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl index f3c5bb9c5d57..76edcf9ed525 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/computeScattering.glsl @@ -133,8 +133,6 @@ void czm_computeScattering( // Compute attenuation via the primary ray and the light ray. vec3 attenuation = exp(-((czm_atmosphereMieCoefficient * (opticalDepth.y + lightOpticalDepth.y)) + (czm_atmosphereRayleighCoefficient * (opticalDepth.x + lightOpticalDepth.x)))); - //lastAttenuation = vec3(rayStepLength, lightStepLength); - // Accumulate the scattering. rayleighAccumulation += sampleDensity.x * attenuation; mieAccumulation += sampleDensity.y * attenuation; diff --git a/packages/engine/Source/Shaders/Model/ModelVS.glsl b/packages/engine/Source/Shaders/Model/ModelVS.glsl index b16a634161d0..fde15b377409 100644 --- a/packages/engine/Source/Shaders/Model/ModelVS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelVS.glsl @@ -106,6 +106,7 @@ void main() // This returns the value that will be assigned to gl_Position. vec4 positionClip = geometryStage(attributes, modelView, normal); + // This must go after the geometry stage as it needs v_positionWC #ifdef HAS_FOG fogStage(attributes); #endif From 9b4c8468d160942e2d3934dee4250a94aa16dab7 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 14:38:11 -0500 Subject: [PATCH 38/55] Add specs for DynamicAtmosphereLightingType --- .../DynamicAtmosphereLightingTypeSpec.js | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js diff --git a/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js b/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js new file mode 100644 index 000000000000..e2fcc864ddd6 --- /dev/null +++ b/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js @@ -0,0 +1,47 @@ +import { DynamicAtmosphereLightingType } from "../../index.js"; + +describe("Scene/DynamicAtmosphereLightingType", function () { + function mockGlobe() { + return { + enableLighting: false, + dynamicAtmosphereLighting: false, + dynamicAtmosphereLightingFromSun: false, + }; + } + + it("returns OFF when lighting is disabled", function () { + const globe = mockGlobe(); + + expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( + DynamicAtmosphereLightingType.OFF + ); + + globe.enableLighting = true; + + expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( + DynamicAtmosphereLightingType.OFF + ); + + globe.enableLighting = false; + globe.dynamicAtmosphereLighting = true; + expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( + DynamicAtmosphereLightingType.OFF + ); + }); + + it("selects a light type depending on globe.dynamicAtmosphereLightingFromSun", function () { + const globe = mockGlobe(); + globe.enableLighting = true; + globe.dynamicAtmosphereLighting = true; + + globe.dynamicAtmosphereLightingFromSun = true; + expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( + DynamicAtmosphereLightingType.SUNLIGHT + ); + + globe.dynamicAtmosphereLightingFromSun = false; + expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( + DynamicAtmosphereLightingType.SCENE_LIGHT + ); + }); +}); From e0f9b33a3ab2a85f61b1ccc7e9f0834bdc02b50e Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Wed, 10 Jan 2024 14:50:48 -0500 Subject: [PATCH 39/55] Rename FogPipelineStage -> AtmospherePipelineStage --- ...ineStage.js => AtmospherePipelineStage.js} | 27 +++++++++++-------- .../Source/Scene/Model/ModelSceneGraph.js | 4 +-- ...FogStageFS.glsl => AtmosphereStageFS.glsl} | 2 +- ...FogStageVS.glsl => AtmosphereStageVS.glsl} | 2 +- .../engine/Source/Shaders/Model/ModelFS.glsl | 4 +-- .../engine/Source/Shaders/Model/ModelVS.glsl | 4 +-- ...Spec.js => AtmospherePipelineStageSpec.js} | 26 +++++++++--------- .../Specs/Scene/Model/ModelSceneGraphSpec.js | 14 +++++----- 8 files changed, 44 insertions(+), 39 deletions(-) rename packages/engine/Source/Scene/Model/{FogPipelineStage.js => AtmospherePipelineStage.js} (65%) rename packages/engine/Source/Shaders/Model/{FogStageFS.glsl => AtmosphereStageFS.glsl} (98%) rename packages/engine/Source/Shaders/Model/{FogStageVS.glsl => AtmosphereStageVS.glsl} (86%) rename packages/engine/Specs/Scene/Model/{FogPipelineStageSpec.js => AtmospherePipelineStageSpec.js} (81%) diff --git a/packages/engine/Source/Scene/Model/FogPipelineStage.js b/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js similarity index 65% rename from packages/engine/Source/Scene/Model/FogPipelineStage.js rename to packages/engine/Source/Scene/Model/AtmospherePipelineStage.js index 5a323e59b114..672e5fccc316 100644 --- a/packages/engine/Source/Scene/Model/FogPipelineStage.js +++ b/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js @@ -1,24 +1,29 @@ import Cartesian3 from "../../Core/Cartesian3.js"; import CesiumMath from "../../Core/Math.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; -import FogStageFS from "../../Shaders/Model/FogStageFS.js"; -import FogStageVS from "../../Shaders/Model/FogStageVS.js"; +import AtmosphereStageFS from "../../Shaders/Model/AtmosphereStageFS.js"; +import AtmosphereStageVS from "../../Shaders/Model/AtmosphereStageVS.js"; /** - * The fog color pipeline stage is responsible for applying fog to tiles in the distance in horizon views. + * The atmosphere pipeline stage applies all earth atmosphere effects that apply + * to models, including fog. * - * @namespace FogColorPipelineStage + * @namespace AtmospherePipelineStage * * @private */ -const FogPipelineStage = { - name: "FogPipelineStage", // Helps with debugging +const AtmospherePipelineStage = { + name: "AtmospherePipelineStage", // Helps with debugging }; -FogPipelineStage.process = function (renderResources, model, frameState) { +AtmospherePipelineStage.process = function ( + renderResources, + model, + frameState +) { const shaderBuilder = renderResources.shaderBuilder; - shaderBuilder.addDefine("HAS_FOG", undefined, ShaderDestination.BOTH); + shaderBuilder.addDefine("HAS_ATMOSPHERE", undefined, ShaderDestination.BOTH); shaderBuilder.addDefine( "COMPUTE_POSITION_WC_ATMOSPHERE", undefined, @@ -29,8 +34,8 @@ FogPipelineStage.process = function (renderResources, model, frameState) { shaderBuilder.addVarying("vec3", "v_atmosphereMieColor"); shaderBuilder.addVarying("float", "v_atmosphereOpacity"); - shaderBuilder.addVertexLines([FogStageVS]); - shaderBuilder.addFragmentLines([FogStageFS]); + shaderBuilder.addVertexLines([AtmosphereStageVS]); + shaderBuilder.addFragmentLines([AtmosphereStageFS]); // Add a uniform so fog is only calculated when the efcfect would // be non-negligible For example when the camera is in space, fog density decreases @@ -51,4 +56,4 @@ FogPipelineStage.process = function (renderResources, model, frameState) { }; }; -export default FogPipelineStage; +export default AtmospherePipelineStage; diff --git a/packages/engine/Source/Scene/Model/ModelSceneGraph.js b/packages/engine/Source/Scene/Model/ModelSceneGraph.js index 72e3cbf11d7b..0dacc4c85b5d 100644 --- a/packages/engine/Source/Scene/Model/ModelSceneGraph.js +++ b/packages/engine/Source/Scene/Model/ModelSceneGraph.js @@ -9,7 +9,7 @@ import SceneMode from "../SceneMode.js"; import SplitDirection from "../SplitDirection.js"; import buildDrawCommand from "./buildDrawCommand.js"; import TilesetPipelineStage from "./TilesetPipelineStage.js"; -import FogPipelineStage from "./FogPipelineStage.js"; +import AtmospherePipelineStage from "./AtmospherePipelineStage.js"; import ImageBasedLightingPipelineStage from "./ImageBasedLightingPipelineStage.js"; import ModelArticulation from "./ModelArticulation.js"; import ModelColorPipelineStage from "./ModelColorPipelineStage.js"; @@ -642,7 +642,7 @@ ModelSceneGraph.prototype.configurePipeline = function (frameState) { } if (fogRenderable) { - modelPipelineStages.push(FogPipelineStage); + modelPipelineStages.push(AtmospherePipelineStage); } }; diff --git a/packages/engine/Source/Shaders/Model/FogStageFS.glsl b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl similarity index 98% rename from packages/engine/Source/Shaders/Model/FogStageFS.glsl rename to packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl index 5ac40c52f2af..1b91e3f87c5f 100644 --- a/packages/engine/Source/Shaders/Model/FogStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl @@ -63,7 +63,7 @@ void applyFog(inout vec4 color, vec4 groundAtmosphereColor, vec3 lightDirection, color = vec4(withFog, color.a); } -void fogStage(inout vec4 color, in ProcessedAttributes attributes) { +void atmosphereStage(inout vec4 color, in ProcessedAttributes attributes) { vec3 rayleighColor; vec3 mieColor; float opacity; diff --git a/packages/engine/Source/Shaders/Model/FogStageVS.glsl b/packages/engine/Source/Shaders/Model/AtmosphereStageVS.glsl similarity index 86% rename from packages/engine/Source/Shaders/Model/FogStageVS.glsl rename to packages/engine/Source/Shaders/Model/AtmosphereStageVS.glsl index 6a81b426105f..ad5809f50d9f 100644 --- a/packages/engine/Source/Shaders/Model/FogStageVS.glsl +++ b/packages/engine/Source/Shaders/Model/AtmosphereStageVS.glsl @@ -1,4 +1,4 @@ -void fogStage(ProcessedAttributes attributes) { +void atmosphereStage(ProcessedAttributes attributes) { vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(v_positionWC, czm_atmosphereDynamicLighting); czm_computeGroundAtmosphereScattering( diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 534c7f2e78f2..13d2aec6663e 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -79,8 +79,8 @@ void main() silhouetteStage(color); #endif - #ifdef HAS_FOG - fogStage(color, attributes); + #ifdef HAS_ATMOSPHERE + atmosphereStage(color, attributes); #endif out_FragColor = color; diff --git a/packages/engine/Source/Shaders/Model/ModelVS.glsl b/packages/engine/Source/Shaders/Model/ModelVS.glsl index fde15b377409..36a65de3e1e3 100644 --- a/packages/engine/Source/Shaders/Model/ModelVS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelVS.glsl @@ -107,8 +107,8 @@ void main() vec4 positionClip = geometryStage(attributes, modelView, normal); // This must go after the geometry stage as it needs v_positionWC - #ifdef HAS_FOG - fogStage(attributes); + #ifdef HAS_ATMOSPHERE + atmosphereStage(attributes); #endif #ifdef HAS_SILHOUETTE diff --git a/packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js similarity index 81% rename from packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js rename to packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js index 281ed762ab02..a806709784bd 100644 --- a/packages/engine/Specs/Scene/Model/FogPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js @@ -1,13 +1,13 @@ import { - _shadersFogStageFS, - _shadersFogStageVS, + _shadersAtmosphereStageFS, + _shadersAtmosphereStageVS, Cartesian3, - FogPipelineStage, + AtmospherePipelineStage, ShaderBuilder, } from "../../../index.js"; import ShaderBuilderTester from "../../../../../Specs/ShaderBuilderTester.js"; -describe("Scene/Model/FogPipelineStage", function () { +describe("Scene/Model/AtmospherePipelineStage", function () { const mockModel = { boundingSphere: { center: Cartesian3.fromDegrees(0, 0, 0), @@ -34,20 +34,20 @@ describe("Scene/Model/FogPipelineStage", function () { }; } - it("Configures shader", function () { + it("configures shader", function () { const renderResources = mockRenderResources(); const frameState = mockFrameState(); - FogPipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, mockModel, frameState); const shaderBuilder = renderResources.shaderBuilder; ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ - "HAS_FOG", + "HAS_ATMOSPHERE", "COMPUTE_POSITION_WC_ATMOSPHERE", ]); ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_FOG", + "HAS_ATMOSPHERE", "COMPUTE_POSITION_WC_ATMOSPHERE", ]); @@ -58,10 +58,10 @@ describe("Scene/Model/FogPipelineStage", function () { ]); ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [ - _shadersFogStageVS, + _shadersAtmosphereStageVS, ]); ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ - _shadersFogStageFS, + _shadersAtmosphereStageFS, ]); ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); @@ -79,7 +79,7 @@ describe("Scene/Model/FogPipelineStage", function () { frameState.camera.position ); - FogPipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, mockModel, frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(false); @@ -95,7 +95,7 @@ describe("Scene/Model/FogPipelineStage", function () { frameState.camera.position = Cartesian3.fromDegrees(0.001, 0, 100000); frameState.fog.density = 0; - FogPipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, mockModel, frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(false); @@ -105,7 +105,7 @@ describe("Scene/Model/FogPipelineStage", function () { const renderResources = mockRenderResources(); const frameState = mockFrameState(); - FogPipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, mockModel, frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(true); diff --git a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js index 05f86fcf865d..8de1acdc3ee6 100644 --- a/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSceneGraphSpec.js @@ -5,7 +5,7 @@ import { CustomShader, CustomShaderPipelineStage, Fog, - FogPipelineStage, + AtmospherePipelineStage, Math as CesiumMath, Matrix4, ModelColorPipelineStage, @@ -344,7 +344,7 @@ describe( }); it("does not add fog stage when fog is not enabled", async function () { - spyOn(FogPipelineStage, "process"); + spyOn(AtmospherePipelineStage, "process"); scene.fog.enabled = false; scene.fog.renderable = false; const model = await loadAndZoomToModelAsync( @@ -355,11 +355,11 @@ describe( ); model.customShader = new CustomShader(); model.update(scene.frameState); - expect(FogPipelineStage.process).not.toHaveBeenCalled(); + expect(AtmospherePipelineStage.process).not.toHaveBeenCalled(); }); it("does not add fog stage when fog is not renderable", async function () { - spyOn(FogPipelineStage, "process"); + spyOn(AtmospherePipelineStage, "process"); scene.fog.enabled = true; scene.fog.renderable = false; const model = await loadAndZoomToModelAsync( @@ -370,11 +370,11 @@ describe( ); model.customShader = new CustomShader(); model.update(scene.frameState); - expect(FogPipelineStage.process).not.toHaveBeenCalled(); + expect(AtmospherePipelineStage.process).not.toHaveBeenCalled(); }); it("adds fog stage when fog is enabled and renderable", async function () { - spyOn(FogPipelineStage, "process"); + spyOn(AtmospherePipelineStage, "process"); scene.fog.enabled = true; scene.fog.renderable = true; const model = await loadAndZoomToModelAsync( @@ -385,7 +385,7 @@ describe( ); model.customShader = new CustomShader(); model.update(scene.frameState); - expect(FogPipelineStage.process).toHaveBeenCalled(); + expect(AtmospherePipelineStage.process).toHaveBeenCalled(); }); it("pushDrawCommands ignores hidden nodes", async function () { From 5236406612fe7dbdd96ee4ccccdab635699b7dfa Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 11:17:43 -0500 Subject: [PATCH 40/55] add render tests for model fog --- .../Scene/Model/AtmospherePipelineStage.js | 2 +- .../engine/Specs/Scene/Model/ModelSpec.js | 438 ++++++++++++++++++ 2 files changed, 439 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js b/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js index 672e5fccc316..46b9e97debda 100644 --- a/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js +++ b/packages/engine/Source/Scene/Model/AtmospherePipelineStage.js @@ -46,7 +46,7 @@ AtmospherePipelineStage.process = function ( // We only need a rough measure of distance to the model, so measure // from the camera to the bounding sphere center. const distance = Cartesian3.distance( - frameState.camera.position, + frameState.camera.positionWC, model.boundingSphere.center ); diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 25c1f5c802a2..68b68d77d918 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -12,11 +12,14 @@ import { Credit, defaultValue, defined, + DirectionalLight, DistanceDisplayCondition, + DynamicAtmosphereLightingType, DracoLoader, Ellipsoid, Event, FeatureDetection, + Fog, HeadingPitchRange, HeadingPitchRoll, HeightReference, @@ -39,6 +42,7 @@ import { ShadowMode, SplitDirection, StyleCommandsNeeded, + SunLight, Transforms, WireframeIndexGenerator, } from "../../../index.js"; @@ -4411,6 +4415,440 @@ describe( }); }); + describe("fog", function () { + const sunnyDate = JulianDate.fromIso8601("2024-01-11T15:00:00Z"); + const darkDate = JulianDate.fromIso8601("2024-01-11T00:00:00Z"); + + afterEach(function () { + scene.fog = new Fog(); + scene.light = new SunLight(); + scene.camera.switchToPerspectiveFrustum(); + }); + + function viewFog(scene, model) { + // In order for fog to create a visible change, the camera needs to be + // further away from the model. This would make the box sub-pixel + // so to make it fill the canvas, use an ortho camera the same + // width of the box to make the scene look 2D. + const center = model.boundingSphere.center; + scene.camera.lookAt(center, new Cartesian3(1000, 0, 0)); + scene.camera.switchToOrthographicFrustum(); + scene.camera.frustum.width = 1; + } + + it("renders a model in fog", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + // Increase the brightness to make the fog color + // stand out more for this test + scene.atmosphere.brightnessShift = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + const renderOptions = { + scene, + time: sunnyDate, + }; + + // First, turn off the fog to capture the original color + let originalColor; + scene.fog.enabled = false; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(originalColor).not.toEqual([0, 0, 0, 255]); + }); + + // Now turn on fog. The result should be bluish + // than before due to scattering. + scene.fog.enabled = true; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + + // The result should have a bluish tint + const [r, g, b, a] = rgba; + expect(b).toBeGreaterThan(r); + expect(b).toBeGreaterThan(g); + expect(a).toBe(255); + }); + }); + + it("renders a model in fog (sunlight)", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + // Increase the brightness to make the fog color + // stand out more for this test + scene.atmosphere.brightnessShift = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + // In order for fog to render, the camera needs to be + // further away from the model. This would make the box sub-pixel + // so to make it fill the canvas, use an ortho camera the same + // width of the box to make the scene look 2D. + const center = model.boundingSphere.center; + scene.camera.lookAt(center, new Cartesian3(1000, 0, 0)); + scene.camera.switchToOrthographicFrustum(); + scene.camera.frustum.width = 1; + + // Grab the color when dynamic lighting is off for comparison + scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.OFF; + const renderOptions = { + scene, + time: sunnyDate, + }; + let originalColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(originalColor).not.toEqual([0, 0, 0, 255]); + }); + + // switch the lighting model to sunlight + scene.atmosphere.dynamicLighting = + DynamicAtmosphereLightingType.SUNLIGHT; + + // Render in the sun, it should be a different color than the + // original + let sunnyColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + sunnyColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + }); + + // Render in the dark, it should be a different color and + // darker than in sun + renderOptions.time = darkDate; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + expect(rgba).not.toEqual(sunnyColor); + + const [sunR, sunG, sunB, sunA] = sunnyColor; + const [r, g, b, a] = rgba; + expect(r).toBeLessThan(sunR); + expect(g).toBeLessThan(sunG); + expect(b).toBeLessThan(sunB); + expect(a).toBe(sunA); + }); + }); + + it("renders a model in fog (scene light)", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + // Increase the brightness to make the fog color + // stand out more for this test + scene.atmosphere.brightnessShift = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + // Grab the color when dynamic lighting is off for comparison + scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.OFF; + const renderOptions = { + scene, + time: sunnyDate, + }; + let originalColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(originalColor).not.toEqual([0, 0, 0, 255]); + }); + + // Also grab the color in sunlight for comparison + scene.atmosphere.dynamicLighting = + DynamicAtmosphereLightingType.SUNLIGHT; + let sunnyColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + sunnyColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + }); + + // Set a light on the scene, but since dynamicLighting is SUNLIGHT, + // it should have no effect yet + scene.light = new DirectionalLight({ + direction: new Cartesian3(0, 1, 0), + }); + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).toEqual(sunnyColor); + }); + + // Set dynamic lighting to use the scene light, now it should + // render a different color from the other light sources + scene.atmosphere.dynamicLighting = + DynamicAtmosphereLightingType.SCENE_LIGHT; + + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + expect(rgba).not.toEqual(sunnyColor); + }); + }); + + it("adjusts atmosphere light intensity", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + // Increase the brightness to make the fog color + // stand out more. We'll use the light intensity to + // modulate this. + scene.atmosphere.brightnessShift = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + const renderOptions = { + scene, + time: sunnyDate, + }; + + // Grab the original color. + let originalColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + + // The result should have a bluish tint from the atmosphere + const [r, g, b, a] = rgba; + expect(b).toBeGreaterThan(r); + expect(b).toBeGreaterThan(g); + expect(a).toBe(255); + }); + + // Turn down the light intensity + scene.atmosphere.lightIntensity = 5.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + + // Check that each component (except alpha) is darker than before + const [oldR, oldG, oldB, oldA] = originalColor; + const [r, g, b, a] = rgba; + expect(r).toBeLessThan(oldR); + expect(g).toBeLessThan(oldG); + expect(b).toBeLessThan(oldB); + expect(a).toBe(oldA); + }); + }); + + it("applies a hue shift", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + // Increase the brightness to make the fog color + // stand out more for this test + scene.atmosphere.brightnessShift = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + const renderOptions = { + scene, + time: sunnyDate, + }; + + // Grab the original color. + let originalColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + + // The result should have a bluish tint from the atmosphere + const [r, g, b, a] = rgba; + expect(b).toBeGreaterThan(r); + expect(b).toBeGreaterThan(g); + expect(a).toBe(255); + }); + + // Shift the fog color to be reddish + scene.atmosphere.hueShift = 0.4; + let redColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + redColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(redColor).not.toEqual(originalColor); + + // Check for a reddish tint + const [r, g, b, a] = rgba; + expect(r).toBeGreaterThan(g); + expect(r).toBeGreaterThan(b); + expect(a).toBe(255); + }); + + // ...now greenish + scene.atmosphere.hueShift = 0.7; + let greenColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + greenColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(greenColor).not.toEqual(originalColor); + expect(greenColor).not.toEqual(redColor); + + // Check for a greenish tint + const [r, g, b, a] = rgba; + expect(g).toBeGreaterThan(r); + expect(g).toBeGreaterThan(b); + expect(a).toBe(255); + }); + + // ...and all the way around the color wheel back to bluish + scene.atmosphere.hueShift = 1.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).toEqual(originalColor); + }); + }); + + it("applies a brightness shift", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + const renderOptions = { + scene, + time: sunnyDate, + }; + + // Grab the original color. + let originalColor; + scene.atmosphere.brightnessShift = 1.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + + // The result should have a bluish tint from the atmosphere + const [r, g, b, a] = rgba; + expect(b).toBeGreaterThan(r); + expect(b).toBeGreaterThan(g); + expect(a).toBe(255); + }); + + // Turn down the brightness + scene.atmosphere.brightnessShift = 0.5; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + + // Check that each component (except alpha) is darker than before + const [oldR, oldG, oldB, oldA] = originalColor; + const [r, g, b, a] = rgba; + expect(r).toBeLessThan(oldR); + expect(g).toBeLessThan(oldG); + expect(b).toBeLessThan(oldB); + expect(a).toBe(oldA); + }); + }); + + it("applies a saturation shift", async function () { + // Move the fog very close to the camera; + scene.fog.density = 1.0; + + const model = await loadAndZoomToModelAsync( + { + url: boxTexturedGltfUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame( + Cartesian3.fromDegrees(0, 0, 10.0) + ), + }, + scene + ); + + viewFog(scene, model); + + const renderOptions = { + scene, + time: sunnyDate, + }; + + // Grab the original color. + let originalColor; + expect(renderOptions).toRenderAndCall(function (rgba) { + originalColor = rgba; + expect(rgba).not.toEqual([0, 0, 0, 255]); + + // The result should have a bluish tint from the atmosphere + const [r, g, b, a] = rgba; + expect(b).toBeGreaterThan(r); + expect(b).toBeGreaterThan(g); + expect(a).toBe(255); + }); + + // Turn down the saturation all the way + scene.atmosphere.saturationShift = -1.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual(originalColor); + + // Check that each component (except alpha) is the same + // as grey values have R = G = B + const [r, g, b, a] = rgba; + expect(g).toBe(r); + expect(b).toBe(g); + expect(a).toBe(255); + }); + }); + }); + it("pick returns position of intersection between ray and model surface", async function () { const model = await loadAndZoomToModelAsync( { From 920ed281ab159e0b761837a8d99dbaa5a4984a42 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 11:30:25 -0500 Subject: [PATCH 41/55] Fix atmosphere pipeline specs --- .../Specs/Scene/Model/AtmospherePipelineStageSpec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js index a806709784bd..7a72c724e295 100644 --- a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js @@ -19,7 +19,7 @@ describe("Scene/Model/AtmospherePipelineStage", function () { camera: { // position the camera a little bit east of the model // and slightly above - position: Cartesian3.fromDegrees(0.01, 0, 1), + positionWC: Cartesian3.fromDegrees(0.01, 0, 1), }, fog: { density: 2e-4, @@ -74,9 +74,9 @@ describe("Scene/Model/AtmospherePipelineStage", function () { const renderResources = mockRenderResources(); const frameState = mockFrameState(); - frameState.camera.position = Cartesian3.clone( + frameState.camera.positionWC = Cartesian3.clone( mockModel.boundingSphere.center, - frameState.camera.position + frameState.camera.positionWC ); AtmospherePipelineStage.process(renderResources, mockModel, frameState); @@ -92,7 +92,7 @@ describe("Scene/Model/AtmospherePipelineStage", function () { // For this case, the fact that Fog decreases the density to 0 when // the camera is far above the model is what causes u_isInFog to // be false. - frameState.camera.position = Cartesian3.fromDegrees(0.001, 0, 100000); + frameState.camera.positionWC = Cartesian3.fromDegrees(0.001, 0, 100000); frameState.fog.density = 0; AtmospherePipelineStage.process(renderResources, mockModel, frameState); From dbbd50b9bcac6fc4f6809c10aa97f68a1577783d Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 11:33:36 -0500 Subject: [PATCH 42/55] Update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index ecc231f785dc..4a3f6b0c11e1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ - Added `Cesium3DTileset.getHeight` to sample height values of the loaded tiles. If using WebGL 1, the `enablePick` option must be set to true to use this function. [#11581](https://github.com/CesiumGS/cesium/pull/11581) - Added `Cesium3DTileset.disableCollision` to allow the camera from to go inside or below a 3D tileset, for instance, to be used with 3D Tiles interiors. [#11581](https://github.com/CesiumGS/cesium/pull/11581) - The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. Furthermore, the default settings of this feature were tuned for improved performance. `Cesium3DTileset.dynamicScreenSpaceErrorDensity` was changed from 0.00278 to 0.0002. `Cesium3DTileset.dynamicScreenSpaceErrorFactor` was changed from 4 to 24. [#11718](https://github.com/CesiumGS/cesium/pull/11718) +- Fog rendering now applies to glTF models and 3D Tiles. This can be configured using `scene.fog` and the new `scene.atmosphere`. [#11744](https://github.com/CesiumGS/cesium/pull/11744) ##### Fixes :wrench: From 060eb471683c45f5cd318c146bbf77667b6fd68f Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 13:53:15 -0500 Subject: [PATCH 43/55] Update documentation based on PR feedback --- packages/engine/Source/Scene/Atmosphere.js | 34 +++++++++++++++++++ .../Scene/DynamicAtmosphereLightingType.js | 4 +-- packages/engine/Source/Scene/Scene.js | 9 +++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index ca2ce6d727b2..6913e2fe7577 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -1,6 +1,40 @@ import Cartesian3 from "../Core/Cartesian3.js"; import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; +/** + * Common atmosphere settings used by sky atmosphere, ground atmosphere, and fog. + * + *

+ * This class is not to be confused with {@link SkyAtmosphere}, which is responsible for rendering the sky. + *

+ *

+ * Currently, these settings only apply to 3D Tiles and models, but will eventually affect the sky atmosphere and globe. See {@link https://github.com/CesiumGS/cesium/issues/11681|issue #11681}. + *

+ *

+ * While the atmosphere settings affect the color of fog, see {@link Fog} to control how fog is rendered. + *

+ * + * @example + * // Turn on dynamic atmosphere lighting using the sun direction + * scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SUNLIGHT; + * + * @example + * // Turn on dynamic lighting using whatever light source is in the scene + * scene.light = new Cesium.DirectionalLight({ + * direction: new Cesium.Cartesian3(1, 0, 0); + * }); + * scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SCENE_LIGHT; + * + * @example + * // Adjust the color of the atmosphere effects. + * scene.atmosphere.hueShift = 0.4; // Cycle 40% around the color wheel + * scene.atmosphere.brightnessShift = 0.25; // Increase the brightness + * scene.atmosphere.saturationShift = -0.1; // Desaturate the colors + * + * @see SkyAtmosphere + * @see Globe + * @see Fog + */ function Atmosphere() { /** * The intensity of the light that is used for computing the ground atmosphere color. diff --git a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js index 892e072a5f08..a018222add16 100644 --- a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js +++ b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js @@ -7,8 +7,8 @@ */ const DynamicAtmosphereLightingType = { /** - * Do not use dynamic atmosphere lighting. Anything that uses atmosphere - * lighting will be lit from directly above the vertex/fragment + * Do not use dynamic atmosphere lighting. Atmosphere lighting effects will + * be lit from directly above rather than using the scene's light source. * * @type {number} * @constant diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index da4417c9e70b..d481993bf70f 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -514,6 +514,15 @@ function Scene(options) { */ this.cameraEventWaitTime = 500.0; + /** + * Settings for atmosphere lighting effects. This is not to be confused with + * {@link Scene#skyAtmosphere} which is responsible for rendering the sky. + *

+ * Currently these settings only apply to 3D Tiles and models. In the future this will apply to the globe as well, see {@link https://github.com/CesiumGS/cesium/issues/11681|issue #11681}. + *

+ * + * @type {Atmosphere} + */ this.atmosphere = new Atmosphere(); /** From 7592fa48fd48e1a8a304f0ed6e56ad89df74a81a Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 14:32:02 -0500 Subject: [PATCH 44/55] Add missing JSDoc tags --- packages/engine/Source/Scene/Atmosphere.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index 6913e2fe7577..cbdbecfc65ba 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -14,6 +14,9 @@ import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; * While the atmosphere settings affect the color of fog, see {@link Fog} to control how fog is rendered. *

* + * @alias Atmosphere + * @constructor + * * @example * // Turn on dynamic atmosphere lighting using the sun direction * scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SUNLIGHT; @@ -21,7 +24,7 @@ import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; * @example * // Turn on dynamic lighting using whatever light source is in the scene * scene.light = new Cesium.DirectionalLight({ - * direction: new Cesium.Cartesian3(1, 0, 0); + * direction: new Cesium.Cartesian3(1, 0, 0) * }); * scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SCENE_LIGHT; * From 7b930e2df3fb9895e5b0789c374fb20ab9c5d857 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 15:07:19 -0500 Subject: [PATCH 45/55] Mark specs as requiring WebGL --- packages/engine/Specs/Scene/AtmosphereSpec.js | 124 ++++++----- .../Model/AtmospherePipelineStageSpec.js | 206 +++++++++--------- 2 files changed, 169 insertions(+), 161 deletions(-) diff --git a/packages/engine/Specs/Scene/AtmosphereSpec.js b/packages/engine/Specs/Scene/AtmosphereSpec.js index 9758be52837c..d23a1db0f4f5 100644 --- a/packages/engine/Specs/Scene/AtmosphereSpec.js +++ b/packages/engine/Specs/Scene/AtmosphereSpec.js @@ -2,68 +2,72 @@ import { Cartesian3, DynamicAtmosphereLightingType } from "../../index.js"; import createScene from "../../../../Specs/createScene"; -describe("scene/Atmosphere", function () { - let scene; - beforeEach(function () { - scene = createScene(); - }); +describe( + "scene/Atmosphere", + function () { + let scene; + beforeEach(function () { + scene = createScene(); + }); - afterEach(function () { - scene.destroyForSpecs(); - }); + afterEach(function () { + scene.destroyForSpecs(); + }); - it("updates frameState each frame", function () { - const atmosphere = scene.atmosphere; - const frameStateAtmosphere = scene.frameState.atmosphere; + it("updates frameState each frame", function () { + const atmosphere = scene.atmosphere; + const frameStateAtmosphere = scene.frameState.atmosphere; - // Render and check that scene.atmosphere updated - // frameState.atmosphere. For the first frame this should - // be the default settings. - scene.renderForSpecs(); - expect(frameStateAtmosphere.hsbShift).toEqual(new Cartesian3()); - expect(frameStateAtmosphere.lightIntensity).toEqual(10.0); - expect(frameStateAtmosphere.rayleighCoefficient).toEqual( - new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) - ); - expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(10000.0); - expect(frameStateAtmosphere.mieCoefficient).toEqual( - new Cartesian3(21e-6, 21e-6, 21e-6) - ); - expect(frameStateAtmosphere.mieScaleHeight).toEqual(3200.0); - expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.9); - expect(frameStateAtmosphere.dynamicLighting).toEqual( - DynamicAtmosphereLightingType.OFF - ); + // Render and check that scene.atmosphere updated + // frameState.atmosphere. For the first frame this should + // be the default settings. + scene.renderForSpecs(); + expect(frameStateAtmosphere.hsbShift).toEqual(new Cartesian3()); + expect(frameStateAtmosphere.lightIntensity).toEqual(10.0); + expect(frameStateAtmosphere.rayleighCoefficient).toEqual( + new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) + ); + expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(10000.0); + expect(frameStateAtmosphere.mieCoefficient).toEqual( + new Cartesian3(21e-6, 21e-6, 21e-6) + ); + expect(frameStateAtmosphere.mieScaleHeight).toEqual(3200.0); + expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.9); + expect(frameStateAtmosphere.dynamicLighting).toEqual( + DynamicAtmosphereLightingType.OFF + ); - // Now change the settings, render again and check that - // the frame state was updated. - atmosphere.hueShift = 0.5; - atmosphere.saturationShift = -0.5; - atmosphere.brightnessShift = 0.25; - atmosphere.lightIntensity = 5.0; - atmosphere.rayleighCoefficient = new Cartesian3(1.0, 1.0, 1.0); - atmosphere.rayleighScaleHeight = 1000; - atmosphere.mieCoefficient = new Cartesian3(2.0, 2.0, 2.0); - atmosphere.mieScaleHeight = 100; - atmosphere.mieAnisotropy = 0.5; - atmosphere.dynamicLighting = DynamicAtmosphereLightingType.SUNLIGHT; + // Now change the settings, render again and check that + // the frame state was updated. + atmosphere.hueShift = 0.5; + atmosphere.saturationShift = -0.5; + atmosphere.brightnessShift = 0.25; + atmosphere.lightIntensity = 5.0; + atmosphere.rayleighCoefficient = new Cartesian3(1.0, 1.0, 1.0); + atmosphere.rayleighScaleHeight = 1000; + atmosphere.mieCoefficient = new Cartesian3(2.0, 2.0, 2.0); + atmosphere.mieScaleHeight = 100; + atmosphere.mieAnisotropy = 0.5; + atmosphere.dynamicLighting = DynamicAtmosphereLightingType.SUNLIGHT; - scene.renderForSpecs(); - expect(frameStateAtmosphere.hsbShift).toEqual( - new Cartesian3(0.5, -0.5, 0.25) - ); - expect(frameStateAtmosphere.lightIntensity).toEqual(5.0); - expect(frameStateAtmosphere.rayleighCoefficient).toEqual( - new Cartesian3(1.0, 1.0, 1.0) - ); - expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(1000); - expect(frameStateAtmosphere.mieCoefficient).toEqual( - new Cartesian3(2.0, 2.0, 2.0) - ); - expect(frameStateAtmosphere.mieScaleHeight).toEqual(100.0); - expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.5); - expect(frameStateAtmosphere.dynamicLighting).toEqual( - DynamicAtmosphereLightingType.SUNLIGHT - ); - }); -}); + scene.renderForSpecs(); + expect(frameStateAtmosphere.hsbShift).toEqual( + new Cartesian3(0.5, -0.5, 0.25) + ); + expect(frameStateAtmosphere.lightIntensity).toEqual(5.0); + expect(frameStateAtmosphere.rayleighCoefficient).toEqual( + new Cartesian3(1.0, 1.0, 1.0) + ); + expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(1000); + expect(frameStateAtmosphere.mieCoefficient).toEqual( + new Cartesian3(2.0, 2.0, 2.0) + ); + expect(frameStateAtmosphere.mieScaleHeight).toEqual(100.0); + expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.5); + expect(frameStateAtmosphere.dynamicLighting).toEqual( + DynamicAtmosphereLightingType.SUNLIGHT + ); + }); + }, + "WebGL" +); diff --git a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js index 7a72c724e295..2a17311dccc1 100644 --- a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js @@ -7,107 +7,111 @@ import { } from "../../../index.js"; import ShaderBuilderTester from "../../../../../Specs/ShaderBuilderTester.js"; -describe("Scene/Model/AtmospherePipelineStage", function () { - const mockModel = { - boundingSphere: { - center: Cartesian3.fromDegrees(0, 0, 0), - }, - }; - - function mockFrameState() { - return { - camera: { - // position the camera a little bit east of the model - // and slightly above - positionWC: Cartesian3.fromDegrees(0.01, 0, 1), - }, - fog: { - density: 2e-4, +describe( + "Scene/Model/AtmospherePipelineStage", + function () { + const mockModel = { + boundingSphere: { + center: Cartesian3.fromDegrees(0, 0, 0), }, }; - } - function mockRenderResources() { - return { - shaderBuilder: new ShaderBuilder(), - uniformMap: {}, - }; - } - - it("configures shader", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); - - AtmospherePipelineStage.process(renderResources, mockModel, frameState); - - const shaderBuilder = renderResources.shaderBuilder; - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ - "HAS_ATMOSPHERE", - "COMPUTE_POSITION_WC_ATMOSPHERE", - ]); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_ATMOSPHERE", - "COMPUTE_POSITION_WC_ATMOSPHERE", - ]); - - ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ - "vec3 v_atmosphereRayleighColor;", - "vec3 v_atmosphereMieColor;", - "float v_atmosphereOpacity;", - ]); - - ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [ - _shadersAtmosphereStageVS, - ]); - ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ - _shadersAtmosphereStageFS, - ]); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform bool u_isInFog;", - ]); - }); - - it("u_isInFog() is false if the camera is at the model center", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); - - frameState.camera.positionWC = Cartesian3.clone( - mockModel.boundingSphere.center, - frameState.camera.positionWC - ); - - AtmospherePipelineStage.process(renderResources, mockModel, frameState); - - const uniformMap = renderResources.uniformMap; - expect(uniformMap.u_isInFog()).toBe(false); - }); - - it("u_isInFog() is false if the camera is in space", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); - - // For this case, the fact that Fog decreases the density to 0 when - // the camera is far above the model is what causes u_isInFog to - // be false. - frameState.camera.positionWC = Cartesian3.fromDegrees(0.001, 0, 100000); - frameState.fog.density = 0; - - AtmospherePipelineStage.process(renderResources, mockModel, frameState); - - const uniformMap = renderResources.uniformMap; - expect(uniformMap.u_isInFog()).toBe(false); - }); - - it("u_isInFog() is true when the tile is in fog", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); - - AtmospherePipelineStage.process(renderResources, mockModel, frameState); - - const uniformMap = renderResources.uniformMap; - expect(uniformMap.u_isInFog()).toBe(true); - }); -}); + function mockFrameState() { + return { + camera: { + // position the camera a little bit east of the model + // and slightly above + positionWC: Cartesian3.fromDegrees(0.01, 0, 1), + }, + fog: { + density: 2e-4, + }, + }; + } + + function mockRenderResources() { + return { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + }; + } + + it("configures shader", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + AtmospherePipelineStage.process(renderResources, mockModel, frameState); + + const shaderBuilder = renderResources.shaderBuilder; + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "HAS_ATMOSPHERE", + "COMPUTE_POSITION_WC_ATMOSPHERE", + ]); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_ATMOSPHERE", + "COMPUTE_POSITION_WC_ATMOSPHERE", + ]); + + ShaderBuilderTester.expectHasVaryings(shaderBuilder, [ + "vec3 v_atmosphereRayleighColor;", + "vec3 v_atmosphereMieColor;", + "float v_atmosphereOpacity;", + ]); + + ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, [ + _shadersAtmosphereStageVS, + ]); + ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ + _shadersAtmosphereStageFS, + ]); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform bool u_isInFog;", + ]); + }); + + it("u_isInFog() is false if the camera is at the model center", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + frameState.camera.positionWC = Cartesian3.clone( + mockModel.boundingSphere.center, + frameState.camera.positionWC + ); + + AtmospherePipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(false); + }); + + it("u_isInFog() is false if the camera is in space", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + // For this case, the fact that Fog decreases the density to 0 when + // the camera is far above the model is what causes u_isInFog to + // be false. + frameState.camera.positionWC = Cartesian3.fromDegrees(0.001, 0, 100000); + frameState.fog.density = 0; + + AtmospherePipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(false); + }); + + it("u_isInFog() is true when the tile is in fog", function () { + const renderResources = mockRenderResources(); + const frameState = mockFrameState(); + + AtmospherePipelineStage.process(renderResources, mockModel, frameState); + + const uniformMap = renderResources.uniformMap; + expect(uniformMap.u_isInFog()).toBe(true); + }); + }, + "WebGL" +); From 84e77ea38ee8f6b90404b0d0042cc9a923d15c42 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 15:09:08 -0500 Subject: [PATCH 46/55] Reset atmosphere after each render test --- packages/engine/Specs/Scene/Model/ModelSpec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 68b68d77d918..3a760805c223 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -1,4 +1,5 @@ import { + Atmosphere, Axis, Cartesian2, Cartesian3, @@ -4420,6 +4421,7 @@ describe( const darkDate = JulianDate.fromIso8601("2024-01-11T00:00:00Z"); afterEach(function () { + scene.atmosphere = new Atmosphere(); scene.fog = new Fog(); scene.light = new SunLight(); scene.camera.switchToPerspectiveFrustum(); From 6c2af2f4ef8adfa7328295fecf91b76ba643e0e8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 16:10:42 -0500 Subject: [PATCH 47/55] Add a separate bullet for adding scene.atmosphere --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4a3f6b0c11e1..e4376dcab8c7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,8 @@ - Added `Cesium3DTileset.getHeight` to sample height values of the loaded tiles. If using WebGL 1, the `enablePick` option must be set to true to use this function. [#11581](https://github.com/CesiumGS/cesium/pull/11581) - Added `Cesium3DTileset.disableCollision` to allow the camera from to go inside or below a 3D tileset, for instance, to be used with 3D Tiles interiors. [#11581](https://github.com/CesiumGS/cesium/pull/11581) - The `Cesium3DTileset.dynamicScreenSpaceError` optimization is now enabled by default, as this improves performance for street-level horizon views. Furthermore, the default settings of this feature were tuned for improved performance. `Cesium3DTileset.dynamicScreenSpaceErrorDensity` was changed from 0.00278 to 0.0002. `Cesium3DTileset.dynamicScreenSpaceErrorFactor` was changed from 4 to 24. [#11718](https://github.com/CesiumGS/cesium/pull/11718) -- Fog rendering now applies to glTF models and 3D Tiles. This can be configured using `scene.fog` and the new `scene.atmosphere`. [#11744](https://github.com/CesiumGS/cesium/pull/11744) +- Fog rendering now applies to glTF models and 3D Tiles. This can be configured using `scene.fog` and `scene.atmosphere`. [#11744](https://github.com/CesiumGS/cesium/pull/11744) +- Added `scene.atmosphere` to store common atmosphere lighting parameters. [#11744](https://github.com/CesiumGS/cesium/pull/11744) and [#11681](https://github.com/CesiumGS/cesium/issues/11681) ##### Fixes :wrench: From defd31098b104f9ccee8e08021d1d612638ad6bb Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 11 Jan 2024 16:53:55 -0500 Subject: [PATCH 48/55] Don't mock in AtmospherePipelineStageSpec --- packages/engine/Source/Scene/Fog.js | 1 + .../Model/AtmospherePipelineStageSpec.js | 95 ++++++++++--------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/packages/engine/Source/Scene/Fog.js b/packages/engine/Source/Scene/Fog.js index 217ef14afcb2..877d45b44f05 100644 --- a/packages/engine/Source/Scene/Fog.js +++ b/packages/engine/Source/Scene/Fog.js @@ -173,6 +173,7 @@ Fog.prototype.update = function (frameState) { frameState.mode !== SceneMode.SCENE3D ) { frameState.fog.enabled = false; + frameState.fog.density = 0; return; } diff --git a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js index 2a17311dccc1..753fbcbcb5e8 100644 --- a/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/AtmospherePipelineStageSpec.js @@ -3,44 +3,53 @@ import { _shadersAtmosphereStageVS, Cartesian3, AtmospherePipelineStage, - ShaderBuilder, + ModelRenderResources, + Transforms, } from "../../../index.js"; +import createScene from "../../../../../Specs/createScene.js"; import ShaderBuilderTester from "../../../../../Specs/ShaderBuilderTester.js"; +import loadAndZoomToModelAsync from "./loadAndZoomToModelAsync.js"; describe( "Scene/Model/AtmospherePipelineStage", function () { - const mockModel = { - boundingSphere: { - center: Cartesian3.fromDegrees(0, 0, 0), - }, - }; - - function mockFrameState() { - return { - camera: { - // position the camera a little bit east of the model - // and slightly above - positionWC: Cartesian3.fromDegrees(0.01, 0, 1), + const boxTexturedGlbUrl = + "./Data/Models/glTF-2.0/BoxTextured/glTF-Binary/BoxTextured.glb"; + + let scene; + let model; + beforeAll(async function () { + scene = createScene(); + + const center = Cartesian3.fromDegrees(0, 0, 0); + model = await loadAndZoomToModelAsync( + { + url: boxTexturedGlbUrl, + modelMatrix: Transforms.eastNorthUpToFixedFrame(center), }, - fog: { - density: 2e-4, - }, - }; - } + scene + ); + }); - function mockRenderResources() { - return { - shaderBuilder: new ShaderBuilder(), - uniformMap: {}, - }; - } + let renderResources; + beforeEach(async function () { + renderResources = new ModelRenderResources(model); - it("configures shader", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); + // position the camera a little bit east of the model + // and slightly above it. + scene.frameState.camera.position = Cartesian3.fromDegrees(0.01, 0, 1000); + scene.frameState.camera.direction = new Cartesian3(0, -1, 0); + + // Reset the fog density + scene.fog.density = 2e-4; + }); - AtmospherePipelineStage.process(renderResources, mockModel, frameState); + afterAll(async function () { + scene.destroyForSpecs(); + }); + + it("configures shader", function () { + AtmospherePipelineStage.process(renderResources, model, scene.frameState); const shaderBuilder = renderResources.shaderBuilder; @@ -73,41 +82,35 @@ describe( }); it("u_isInFog() is false if the camera is at the model center", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); - - frameState.camera.positionWC = Cartesian3.clone( - mockModel.boundingSphere.center, - frameState.camera.positionWC + const frameState = scene.frameState; + frameState.camera.position = Cartesian3.clone( + model.boundingSphere.center, + frameState.camera.position ); + scene.renderForSpecs(); - AtmospherePipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, model, frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(false); }); it("u_isInFog() is false if the camera is in space", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); + const frameState = scene.frameState; - // For this case, the fact that Fog decreases the density to 0 when - // the camera is far above the model is what causes u_isInFog to - // be false. - frameState.camera.positionWC = Cartesian3.fromDegrees(0.001, 0, 100000); - frameState.fog.density = 0; + frameState.camera.position = Cartesian3.fromDegrees(0.01, 0, 900000); + scene.renderForSpecs(); - AtmospherePipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, model, frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(false); }); it("u_isInFog() is true when the tile is in fog", function () { - const renderResources = mockRenderResources(); - const frameState = mockFrameState(); + scene.renderForSpecs(); - AtmospherePipelineStage.process(renderResources, mockModel, frameState); + AtmospherePipelineStage.process(renderResources, model, scene.frameState); const uniformMap = renderResources.uniformMap; expect(uniformMap.u_isInFog()).toBe(true); From dca2dcfa10865c49f3d0e4d8566040ccc5db53e8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Tue, 16 Jan 2024 10:06:09 -0500 Subject: [PATCH 49/55] address PR feedback --- packages/engine/Source/Scene/Atmosphere.js | 11 ++++------- .../Source/Scene/DynamicAtmosphereLightingType.js | 4 ++-- packages/engine/Source/Scene/Scene.js | 5 +---- .../Functions/getDynamicAtmosphereLightDirection.glsl | 4 ++-- .../Source/Shaders/Model/AtmosphereStageFS.glsl | 4 ++-- packages/engine/Specs/Scene/AtmosphereSpec.js | 2 +- .../Specs/Scene/DynamicAtmosphereLightingTypeSpec.js | 6 +++--- packages/engine/Specs/Scene/Model/ModelSpec.js | 4 ++-- packages/engine/Specs/Scene/SkyAtmosphereSpec.js | 2 +- 9 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index cbdbecfc65ba..399cff147ecb 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -2,15 +2,12 @@ import Cartesian3 from "../Core/Cartesian3.js"; import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js"; /** - * Common atmosphere settings used by sky atmosphere, ground atmosphere, and fog. + * Common atmosphere settings used by 3D Tiles and models for rendering sky atmosphere, ground atmosphere, and fog. * *

* This class is not to be confused with {@link SkyAtmosphere}, which is responsible for rendering the sky. *

*

- * Currently, these settings only apply to 3D Tiles and models, but will eventually affect the sky atmosphere and globe. See {@link https://github.com/CesiumGS/cesium/issues/11681|issue #11681}. - *

- *

* While the atmosphere settings affect the color of fog, see {@link Fog} to control how fog is rendered. *

* @@ -118,13 +115,13 @@ function Atmosphere() { this.brightnessShift = 0.0; /** - * When not DynamicAtmosphereLightingType.OFF, the selected light source will + * When not DynamicAtmosphereLightingType.NONE, the selected light source will * be used for dynamically lighting all atmosphere-related rendering effects. * * @type {DynamicAtmosphereLightingType} - * @default DynamicAtmosphereLightingType.OFF + * @default DynamicAtmosphereLightingType.NONE */ - this.dynamicLighting = DynamicAtmosphereLightingType.OFF; + this.dynamicLighting = DynamicAtmosphereLightingType.NONE; } Atmosphere.prototype.update = function (frameState) { diff --git a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js index a018222add16..d1d9967a730e 100644 --- a/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js +++ b/packages/engine/Source/Scene/DynamicAtmosphereLightingType.js @@ -13,7 +13,7 @@ const DynamicAtmosphereLightingType = { * @type {number} * @constant */ - OFF: 0, + NONE: 0, /** * Use the scene's current light source for dynamic atmosphere lighting. * @@ -42,7 +42,7 @@ const DynamicAtmosphereLightingType = { DynamicAtmosphereLightingType.fromGlobeFlags = function (globe) { const lightingOn = globe.enableLighting && globe.dynamicAtmosphereLighting; if (!lightingOn) { - return DynamicAtmosphereLightingType.OFF; + return DynamicAtmosphereLightingType.NONE; } // Force sunlight diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index d481993bf70f..03f46e1ccbd2 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -515,11 +515,8 @@ function Scene(options) { this.cameraEventWaitTime = 500.0; /** - * Settings for atmosphere lighting effects. This is not to be confused with + * Settings for atmosphere lighting effects affecting 3D Tiles and model rendering. This is not to be confused with * {@link Scene#skyAtmosphere} which is responsible for rendering the sky. - *

- * Currently these settings only apply to 3D Tiles and models. In the future this will apply to the globe as well, see {@link https://github.com/CesiumGS/cesium/issues/11681|issue #11681}. - *

* * @type {Atmosphere} */ diff --git a/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl index 9b934362b090..70063302734f 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/getDynamicAtmosphereLightDirection.glsl @@ -10,12 +10,12 @@ * @return {vec3} The normalized light direction vector. Depending on the enum value, it is either positionWC, czm_lightDirectionWC or czm_sunDirectionWC */ vec3 czm_getDynamicAtmosphereLightDirection(vec3 positionWC, float lightEnum) { - const float OFF = 0.0; + const float NONE = 0.0; const float SCENE_LIGHT = 1.0; const float SUNLIGHT = 2.0; vec3 lightDirection = - positionWC * float(lightEnum == OFF) + + positionWC * float(lightEnum == NONE) + czm_lightDirectionWC * float(lightEnum == SCENE_LIGHT) + czm_sunDirectionWC * float(lightEnum == SUNLIGHT); return normalize(lightDirection); diff --git a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl index 1b91e3f87c5f..7b2dc693415b 100644 --- a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl @@ -44,8 +44,8 @@ void applyFog(inout vec4 color, vec4 groundAtmosphereColor, vec3 lightDirection, vec3 fogColor = groundAtmosphereColor.rgb; // If there is dynamic lighting, apply that to the fog. - const float OFF = 0.0; - if (czm_atmosphereDynamicLighting != OFF) { + const float NONE = 0.0; + if (czm_atmosphereDynamicLighting != NONE) { float darken = clamp(dot(normalize(czm_viewerPositionWC), lightDirection), czm_fogMinimumBrightness, 1.0); fogColor *= darken; } diff --git a/packages/engine/Specs/Scene/AtmosphereSpec.js b/packages/engine/Specs/Scene/AtmosphereSpec.js index d23a1db0f4f5..a3ac1165e296 100644 --- a/packages/engine/Specs/Scene/AtmosphereSpec.js +++ b/packages/engine/Specs/Scene/AtmosphereSpec.js @@ -34,7 +34,7 @@ describe( expect(frameStateAtmosphere.mieScaleHeight).toEqual(3200.0); expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.9); expect(frameStateAtmosphere.dynamicLighting).toEqual( - DynamicAtmosphereLightingType.OFF + DynamicAtmosphereLightingType.NONE ); // Now change the settings, render again and check that diff --git a/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js b/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js index e2fcc864ddd6..71c1802eeca8 100644 --- a/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js +++ b/packages/engine/Specs/Scene/DynamicAtmosphereLightingTypeSpec.js @@ -13,19 +13,19 @@ describe("Scene/DynamicAtmosphereLightingType", function () { const globe = mockGlobe(); expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( - DynamicAtmosphereLightingType.OFF + DynamicAtmosphereLightingType.NONE ); globe.enableLighting = true; expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( - DynamicAtmosphereLightingType.OFF + DynamicAtmosphereLightingType.NONE ); globe.enableLighting = false; globe.dynamicAtmosphereLighting = true; expect(DynamicAtmosphereLightingType.fromGlobeFlags(globe)).toBe( - DynamicAtmosphereLightingType.OFF + DynamicAtmosphereLightingType.NONE ); }); diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 3a760805c223..bec39b42978a 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -4514,7 +4514,7 @@ describe( scene.camera.frustum.width = 1; // Grab the color when dynamic lighting is off for comparison - scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.OFF; + scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.NONE; const renderOptions = { scene, time: sunnyDate, @@ -4576,7 +4576,7 @@ describe( viewFog(scene, model); // Grab the color when dynamic lighting is off for comparison - scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.OFF; + scene.atmosphere.dynamicLighting = DynamicAtmosphereLightingType.NONE; const renderOptions = { scene, time: sunnyDate, diff --git a/packages/engine/Specs/Scene/SkyAtmosphereSpec.js b/packages/engine/Specs/Scene/SkyAtmosphereSpec.js index f3d62d2f4546..b7be045cfd39 100644 --- a/packages/engine/Specs/Scene/SkyAtmosphereSpec.js +++ b/packages/engine/Specs/Scene/SkyAtmosphereSpec.js @@ -85,7 +85,7 @@ describe( it("draws sky with dynamic lighting off", function () { const s = new SkyAtmosphere(); - s.setDynamicLighting(DynamicAtmosphereLightingType.OFF); + s.setDynamicLighting(DynamicAtmosphereLightingType.NONE); expect(scene).toRender([0, 0, 0, 255]); scene.render(); From 7adf61de2e96f0297a11727e314e3dab9478cfc7 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 10:30:22 -0500 Subject: [PATCH 50/55] add ignoreBlackPixels option to czm_applyHSBShift --- .../Builtin/Functions/applyHSBShift.glsl | 15 +++++++++++++-- packages/engine/Source/Shaders/GlobeFS.glsl | 19 +++---------------- .../Shaders/Model/AtmosphereStageFS.glsl | 5 +++-- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl index 30b9424eb4ea..55ee1592a8d6 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/applyHSBShift.glsl @@ -3,10 +3,11 @@ * * @param {vec3} rgb The color in RGB space. * @param {vec3} hsbShift The amount to shift each component. The xyz components correspond to hue, saturation, and brightness. Shifting the hue by +/- 1.0 corresponds to shifting the hue by a full cycle. Saturation and brightness are clamped between 0 and 1 after the adjustment + * @param {bool} ignoreBlackPixels If true, black pixels will be unchanged. This is necessary in some shaders such as atmosphere-related effects. * * @return {vec3} The RGB color after shifting in HSB space and clamping saturation and brightness to a valid range. */ -vec3 czm_applyHSBShift(vec3 rgb, vec3 hsbShift) { +vec3 czm_applyHSBShift(vec3 rgb, vec3 hsbShift, bool ignoreBlackPixels) { // Convert rgb color to hsb vec3 hsb = czm_RGBToHSB(rgb); @@ -14,7 +15,17 @@ vec3 czm_applyHSBShift(vec3 rgb, vec3 hsbShift) { // Hue cycles around so no clamp is needed. hsb.x += hsbShift.x; // hue hsb.y = clamp(hsb.y + hsbShift.y, 0.0, 1.0); // saturation - hsb.z = clamp(hsb.z + hsbShift.z, 0.0, 1.0); // brightness + + // brightness + // + // Some shaders such as atmosphere-related effects need to leave black + // pixels unchanged + if (ignoreBlackPixels) { + hsb.z = hsb.z > czm_epsilon7 ? hsb.z + hsbShift.z : 0.0; + } else { + hsb.z = hsb.z + hsbShift.z; + } + hsb.z = clamp(hsb.z, 0.0, 1.0); // Convert shifted hsb back to rgb return czm_HSBToRGB(hsb); diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index a2bf6b48811a..74c568efb406 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -268,20 +268,6 @@ vec4 sampleAndBlend( return vec4(outColor, max(outAlpha, 0.0)); } -vec3 colorCorrect(vec3 rgb) { -#ifdef COLOR_CORRECT - // Convert rgb color to hsb - vec3 hsb = czm_RGBToHSB(rgb); - // Perform hsb shift - hsb.x += u_hsbShift.x; // hue - hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation - hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness - // Convert shifted hsb back to rgb - rgb = czm_HSBToRGB(hsb); -#endif - return rgb; -} - vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates, float nightBlend); vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float specularMapValue, float fade); @@ -473,8 +459,9 @@ void main() #endif #ifdef COLOR_CORRECT - rayleighColor = czm_applyHSBShift(rayleighColor, u_hsbShift); - mieColor = czm_applyHSBShift(mieColor, u_hsbShift); + const bool ignoreBlackPixels = true; + rayleighColor = czm_applyHSBShift(rayleighColor, u_hsbShift, ignoreBlackPixels); + mieColor = czm_applyHSBShift(mieColor, u_hsbShift, ignoreBlackPixels); #endif vec4 groundAtmosphereColor = computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); diff --git a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl index 7b2dc693415b..3a0b20e86fa0 100644 --- a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl @@ -96,8 +96,9 @@ void atmosphereStage(inout vec4 color, in ProcessedAttributes attributes) { } //color correct rayleigh and mie colors - rayleighColor = czm_applyHSBShift(rayleighColor, czm_atmosphereHsbShift); - mieColor = czm_applyHSBShift(mieColor, czm_atmosphereHsbShift); + const bool ignoreBlackPixels = true; + rayleighColor = czm_applyHSBShift(rayleighColor, czm_atmosphereHsbShift, ignoreBlackPixels); + mieColor = czm_applyHSBShift(mieColor, czm_atmosphereHsbShift, ignoreBlackPixels); vec4 groundAtmosphereColor = czm_computeAtmosphereColor(positionWC, lightDirection, rayleighColor, mieColor, opacity); From 6cefbe174af31db1a2cf23ea22904c5205749584 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 10:43:34 -0500 Subject: [PATCH 51/55] use czm_tanh() --- packages/engine/Source/Shaders/AtmosphereCommon.glsl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/engine/Source/Shaders/AtmosphereCommon.glsl b/packages/engine/Source/Shaders/AtmosphereCommon.glsl index e0e23eab78cb..d68274fa5381 100644 --- a/packages/engine/Source/Shaders/AtmosphereCommon.glsl +++ b/packages/engine/Source/Shaders/AtmosphereCommon.glsl @@ -11,14 +11,6 @@ const float ATMOSPHERE_THICKNESS = 111e3; // The thickness of the atmosphere in const int PRIMARY_STEPS_MAX = 16; // Maximum number of times the ray from the camera to the world position (primary ray) is sampled. const int LIGHT_STEPS_MAX = 4; // Maximum number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray. -/** - * Rational approximation to tanh(x) -*/ -float approximateTanh(float x) { - float x2 = x * x; - return max(-1.0, min(+1.0, x * (27.0 + x2) / (27.0 + 9.0 * x2))); -} - /** * This function computes the colors contributed by Rayliegh and Mie scattering on a given ray, as well as * the transmittance value for the ray. @@ -65,7 +57,7 @@ void computeScattering( float x = 1e-7 * primaryRayAtmosphereIntersect.stop / length(primaryRayLength); // Value close to 0.0: close to the horizon // Value close to 1.0: above in the sky - float w_stop_gt_lprl = 0.5 * (1.0 + approximateTanh(x)); + float w_stop_gt_lprl = 0.5 * (1.0 + czm_approximateTanh(x)); // The ray should start from the first intersection with the outer atmopshere, or from the camera position, if it is inside the atmosphere. float start_0 = primaryRayAtmosphereIntersect.start; @@ -77,7 +69,7 @@ void computeScattering( // (1) from outer space we have to use more ray steps to get a realistic rendering // (2) within atmosphere we need fewer steps for faster rendering float x_o_a = start_0 - ATMOSPHERE_THICKNESS; // ATMOSPHERE_THICKNESS used as an ad-hoc constant, no precise meaning here, only the order of magnitude matters - float w_inside_atmosphere = 1.0 - 0.5 * (1.0 + approximateTanh(x_o_a)); + float w_inside_atmosphere = 1.0 - 0.5 * (1.0 + czm_approximateTanh(x_o_a)); int PRIMARY_STEPS = PRIMARY_STEPS_MAX - int(w_inside_atmosphere * 12.0); // Number of times the ray from the camera to the world position (primary ray) is sampled. int LIGHT_STEPS = LIGHT_STEPS_MAX - int(w_inside_atmosphere * 2.0); // Number of times the light is sampled from the light source's intersection with the atmosphere to a sample position on the primary ray. From ac17f63da8231f23e7bf96929d8707c1c632c825 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 12:26:24 -0500 Subject: [PATCH 52/55] Store the Atmosphere in the FrameState directly --- .../engine/Source/Renderer/UniformState.js | 6 ++-- packages/engine/Source/Scene/Atmosphere.js | 21 ------------- packages/engine/Source/Scene/FrameState.js | 30 +++---------------- packages/engine/Source/Scene/Scene.js | 2 +- 4 files changed, 9 insertions(+), 50 deletions(-) diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 7c0c56924c62..12e71c5b1d8b 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1518,8 +1518,10 @@ UniformState.prototype.update = function (frameState) { const atmosphere = frameState.atmosphere; - this._atmosphereHsbShift = Cartesian3.clone( - atmosphere.hsbShift, + this._atmosphereHsbShift = Cartesian3.fromElements( + atmosphere.hueShift, + atmosphere.saturationShift, + atmosphere.brightnessShift, this._atmosphereHsbShift ); this._atmosphereLightIntensity = atmosphere.lightIntensity; diff --git a/packages/engine/Source/Scene/Atmosphere.js b/packages/engine/Source/Scene/Atmosphere.js index 399cff147ecb..296ad0f19d25 100644 --- a/packages/engine/Source/Scene/Atmosphere.js +++ b/packages/engine/Source/Scene/Atmosphere.js @@ -124,25 +124,4 @@ function Atmosphere() { this.dynamicLighting = DynamicAtmosphereLightingType.NONE; } -Atmosphere.prototype.update = function (frameState) { - const atmosphere = frameState.atmosphere; - atmosphere.hsbShift.x = this.hueShift; - atmosphere.hsbShift.y = this.saturationShift; - atmosphere.hsbShift.z = this.brightnessShift; - atmosphere.lightIntensity = this.lightIntensity; - atmosphere.rayleighCoefficient = Cartesian3.clone( - this.rayleighCoefficient, - atmosphere.rayleighCoefficient - ); - atmosphere.rayleighScaleHeight = this.rayleighScaleHeight; - atmosphere.mieCoefficient = Cartesian3.clone( - this.mieCoefficient, - atmosphere.mieCoefficient - ); - atmosphere.mieScaleHeight = this.mieScaleHeight; - atmosphere.mieAnisotropy = this.mieAnisotropy; - - atmosphere.dynamicLighting = this.dynamicLighting; -}; - export default Atmosphere; diff --git a/packages/engine/Source/Scene/FrameState.js b/packages/engine/Source/Scene/FrameState.js index 0ad600f948ba..adfffba7958a 100644 --- a/packages/engine/Source/Scene/FrameState.js +++ b/packages/engine/Source/Scene/FrameState.js @@ -1,5 +1,4 @@ import SceneMode from "./SceneMode.js"; -import Cartesian3 from "../Core/Cartesian3.js"; /** * State information about the current frame. An instance of this class @@ -278,31 +277,10 @@ function FrameState(context, creditDisplay, jobScheduler) { }; /** - * @typedef FrameState.Atmosphere - * @type {object} - * @property {Cartesian3} hsbShift A color shift to apply to the atmosphere color in HSB. - * @property {number} lightIntensity The intensity of the light that is used for computing the atmosphere color - * @property {Cartesian3} rayleighCoefficient The Rayleigh scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. - * @property {number} rayleighScaleHeight The Rayleigh scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. - * @property {Cartesian3} mieCoefficient The Mie scattering coefficient used in the atmospheric scattering equations for the sky atmosphere. - * @property {number} mieScaleHeight The Mie scale height used in the atmospheric scattering equations for the sky atmosphere, in meters. - * @property {number} mieAnisotropy The anisotropy of the medium to consider for Mie scattering. - * @property {DynamicAtmosphereLightingType} dynamicLighting An enum value determining what light source to use for dynamic lighting the atmosphere (if enabled) - */ - - /** - * @type {FrameState.Atmosphere} - */ - this.atmosphere = { - hsbShift: new Cartesian3(), - lightIntensity: undefined, - rayleighCoefficient: new Cartesian3(), - rayleighScaleHeight: undefined, - mieCoefficient: new Cartesian3(), - mieScaleHeight: undefined, - mieAnisotropy: undefined, - dynamicLighting: undefined, - }; + * The current Atmosphere + * @type {Atmosphere} + */ + this.atmosphere = undefined; /** * A scalar used to vertically exaggerate the scene diff --git a/packages/engine/Source/Scene/Scene.js b/packages/engine/Source/Scene/Scene.js index 03f46e1ccbd2..1d56d15d9333 100644 --- a/packages/engine/Source/Scene/Scene.js +++ b/packages/engine/Source/Scene/Scene.js @@ -3770,7 +3770,7 @@ function render(scene) { } frameState.backgroundColor = backgroundColor; - scene.atmosphere.update(frameState); + frameState.atmosphere = scene.atmosphere; scene.fog.update(frameState); us.update(frameState); From bca110fba369ff9c9ee322081d90606729eb019a Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 13:55:15 -0500 Subject: [PATCH 53/55] Fix automatic uniform specs --- Specs/createFrameState.js | 3 ++ .../engine/Source/Renderer/UniformState.js | 41 ++++++++++--------- .../Specs/Renderer/AutomaticUniformSpec.js | 25 ++++------- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/Specs/createFrameState.js b/Specs/createFrameState.js index 672a2376fe58..8f4ba1c1f6b2 100644 --- a/Specs/createFrameState.js +++ b/Specs/createFrameState.js @@ -1,4 +1,5 @@ import { + Atmosphere, defaultValue, GeographicProjection, JulianDate, @@ -51,6 +52,8 @@ function createFrameState(context, camera, frameNumber, time) { frameState.minimumDisableDepthTestDistance = 0.0; + frameState.atmosphere = new Atmosphere(); + return frameState; } export default createFrameState; diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 12e71c5b1d8b..cc5030e6f5d6 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1517,26 +1517,27 @@ UniformState.prototype.update = function (frameState) { this._fogMinimumBrightness = frameState.fog.minimumBrightness; const atmosphere = frameState.atmosphere; - - this._atmosphereHsbShift = Cartesian3.fromElements( - atmosphere.hueShift, - atmosphere.saturationShift, - atmosphere.brightnessShift, - this._atmosphereHsbShift - ); - this._atmosphereLightIntensity = atmosphere.lightIntensity; - this._atmosphereRayleighCoefficient = Cartesian3.clone( - atmosphere.rayleighCoefficient, - this._atmosphereRayleighCoefficient - ); - this._atmosphereRayleighScaleHeight = atmosphere.rayleighScaleHeight; - this._atmosphereMieCoefficient = Cartesian3.clone( - atmosphere.mieCoefficient, - this._atmosphereMieCoefficient - ); - this._atmosphereMieScaleHeight = atmosphere.mieScaleHeight; - this._atmosphereMieAnisotropy = atmosphere.mieAnisotropy; - this._atmosphereDynamicLighting = atmosphere.dynamicLighting; + if (defined(atmosphere)) { + this._atmosphereHsbShift = Cartesian3.fromElements( + atmosphere.hueShift, + atmosphere.saturationShift, + atmosphere.brightnessShift, + this._atmosphereHsbShift + ); + this._atmosphereLightIntensity = atmosphere.lightIntensity; + this._atmosphereRayleighCoefficient = Cartesian3.clone( + atmosphere.rayleighCoefficient, + this._atmosphereRayleighCoefficient + ); + this._atmosphereRayleighScaleHeight = atmosphere.rayleighScaleHeight; + this._atmosphereMieCoefficient = Cartesian3.clone( + atmosphere.mieCoefficient, + this._atmosphereMieCoefficient + ); + this._atmosphereMieScaleHeight = atmosphere.mieScaleHeight; + this._atmosphereMieAnisotropy = atmosphere.mieAnisotropy; + this._atmosphereDynamicLighting = atmosphere.dynamicLighting; + } this._invertClassificationColor = frameState.invertClassificationColor; diff --git a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js index c93eac8870d3..f6381a39f9ef 100644 --- a/packages/engine/Specs/Renderer/AutomaticUniformSpec.js +++ b/packages/engine/Specs/Renderer/AutomaticUniformSpec.js @@ -1,5 +1,4 @@ import { - Atmosphere, Cartesian2, Cartesian3, Cartographic, @@ -1848,11 +1847,10 @@ describe( it("has czm_atmosphereHsbShift", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.hueShift = 1.0; atmosphere.saturationShift = 2.0; atmosphere.brightnessShift = 3.0; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1869,9 +1867,8 @@ describe( it("has czm_atmosphereLightIntensity", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.lightIntensity = 2.0; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1888,9 +1885,8 @@ describe( it("has czm_atmosphereRayleighCoefficient", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.rayleighCoefficient = new Cartesian3(1.0, 2.0, 3.0); - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1907,9 +1903,8 @@ describe( it("has czm_atmosphereRayleighScaleHeight", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.rayleighScaleHeight = 100.0; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1926,9 +1921,8 @@ describe( it("has czm_atmosphereMieCoefficient", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.mieCoefficient = new Cartesian3(1.0, 2.0, 3.0); - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1945,9 +1939,8 @@ describe( it("has czm_atmosphereMieScaleHeight", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.mieScaleHeight = 100.0; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1964,9 +1957,8 @@ describe( it("has czm_atmosphereMieAnisotropy", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; atmosphere.mieAnisotropy = 100.0; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); @@ -1983,10 +1975,9 @@ describe( it("has czm_atmosphereDynamicLighting", function () { const frameState = createFrameState(context, createMockCamera()); - const atmosphere = new Atmosphere(); + const atmosphere = frameState.atmosphere; const enumValue = DynamicAtmosphereLightingType.SCENE_LIGHT; atmosphere.dynamicLighting = enumValue; - atmosphere.update(frameState); const us = context.uniformState; us.update(frameState); From 3a25627e350d41abd5dccb4c4c7039ce86bc2316 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 13:56:20 -0500 Subject: [PATCH 54/55] Fix sky atmosphere --- packages/engine/Source/Shaders/SkyAtmosphereFS.glsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index 639730bba374..154964b0d643 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -43,7 +43,8 @@ void main (void) #endif #ifdef COLOR_CORRECT - color.rgb = czm_applyHSBShift(color.rgb, u_hsbShift); + const float ignoreBlackPixels = true; + color.rgb = czm_applyHSBShift(color.rgb, u_hsbShift, ignoreBlackPixels); #endif // For the parts of the sky atmosphere that are not behind a translucent globe, From ab972808d700b8f0bef66a6ecff4da479d8590e8 Mon Sep 17 00:00:00 2001 From: Peter Gagliardi Date: Thu, 18 Jan 2024 14:30:36 -0500 Subject: [PATCH 55/55] Update unit tests --- .../Source/Shaders/SkyAtmosphereFS.glsl | 2 +- packages/engine/Specs/Scene/AtmosphereSpec.js | 73 ------------------- packages/engine/Specs/Scene/SceneSpec.js | 23 +++++- 3 files changed, 23 insertions(+), 75 deletions(-) delete mode 100644 packages/engine/Specs/Scene/AtmosphereSpec.js diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index 154964b0d643..d8f213396452 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -43,7 +43,7 @@ void main (void) #endif #ifdef COLOR_CORRECT - const float ignoreBlackPixels = true; + const bool ignoreBlackPixels = true; color.rgb = czm_applyHSBShift(color.rgb, u_hsbShift, ignoreBlackPixels); #endif diff --git a/packages/engine/Specs/Scene/AtmosphereSpec.js b/packages/engine/Specs/Scene/AtmosphereSpec.js deleted file mode 100644 index a3ac1165e296..000000000000 --- a/packages/engine/Specs/Scene/AtmosphereSpec.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Cartesian3, DynamicAtmosphereLightingType } from "../../index.js"; - -import createScene from "../../../../Specs/createScene"; - -describe( - "scene/Atmosphere", - function () { - let scene; - beforeEach(function () { - scene = createScene(); - }); - - afterEach(function () { - scene.destroyForSpecs(); - }); - - it("updates frameState each frame", function () { - const atmosphere = scene.atmosphere; - const frameStateAtmosphere = scene.frameState.atmosphere; - - // Render and check that scene.atmosphere updated - // frameState.atmosphere. For the first frame this should - // be the default settings. - scene.renderForSpecs(); - expect(frameStateAtmosphere.hsbShift).toEqual(new Cartesian3()); - expect(frameStateAtmosphere.lightIntensity).toEqual(10.0); - expect(frameStateAtmosphere.rayleighCoefficient).toEqual( - new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6) - ); - expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(10000.0); - expect(frameStateAtmosphere.mieCoefficient).toEqual( - new Cartesian3(21e-6, 21e-6, 21e-6) - ); - expect(frameStateAtmosphere.mieScaleHeight).toEqual(3200.0); - expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.9); - expect(frameStateAtmosphere.dynamicLighting).toEqual( - DynamicAtmosphereLightingType.NONE - ); - - // Now change the settings, render again and check that - // the frame state was updated. - atmosphere.hueShift = 0.5; - atmosphere.saturationShift = -0.5; - atmosphere.brightnessShift = 0.25; - atmosphere.lightIntensity = 5.0; - atmosphere.rayleighCoefficient = new Cartesian3(1.0, 1.0, 1.0); - atmosphere.rayleighScaleHeight = 1000; - atmosphere.mieCoefficient = new Cartesian3(2.0, 2.0, 2.0); - atmosphere.mieScaleHeight = 100; - atmosphere.mieAnisotropy = 0.5; - atmosphere.dynamicLighting = DynamicAtmosphereLightingType.SUNLIGHT; - - scene.renderForSpecs(); - expect(frameStateAtmosphere.hsbShift).toEqual( - new Cartesian3(0.5, -0.5, 0.25) - ); - expect(frameStateAtmosphere.lightIntensity).toEqual(5.0); - expect(frameStateAtmosphere.rayleighCoefficient).toEqual( - new Cartesian3(1.0, 1.0, 1.0) - ); - expect(frameStateAtmosphere.rayleighScaleHeight).toEqual(1000); - expect(frameStateAtmosphere.mieCoefficient).toEqual( - new Cartesian3(2.0, 2.0, 2.0) - ); - expect(frameStateAtmosphere.mieScaleHeight).toEqual(100.0); - expect(frameStateAtmosphere.mieAnisotropy).toEqual(0.5); - expect(frameStateAtmosphere.dynamicLighting).toEqual( - DynamicAtmosphereLightingType.SUNLIGHT - ); - }); - }, - "WebGL" -); diff --git a/packages/engine/Specs/Scene/SceneSpec.js b/packages/engine/Specs/Scene/SceneSpec.js index 74a8a66a7efb..55ebadab6fe0 100644 --- a/packages/engine/Specs/Scene/SceneSpec.js +++ b/packages/engine/Specs/Scene/SceneSpec.js @@ -1,4 +1,5 @@ import { + Atmosphere, BoundingSphere, Cartesian2, Cartesian3, @@ -577,7 +578,7 @@ describe( }); }); - it("renders sky atmopshere without a globe", function () { + it("renders sky atmosphere without a globe", function () { s.globe = new Globe(Ellipsoid.UNIT_SPHERE); s.globe.show = false; s.camera.position = new Cartesian3(1.02, 0.0, 0.0); @@ -2471,6 +2472,26 @@ describe( scene.destroyForSpecs(); }); }); + + it("updates frameState.atmosphere", function () { + const scene = createScene(); + const frameState = scene.frameState; + + // Before the first render, the atmosphere has not yet been set + expect(frameState.atmosphere).toBeUndefined(); + + // On the first render, the atmosphere settings are propagated to the + // frame state + const originalAtmosphere = scene.atmosphere; + scene.renderForSpecs(); + expect(frameState.atmosphere).toBe(originalAtmosphere); + + // If we change the atmosphere to a new object + const anotherAtmosphere = new Atmosphere(); + scene.atmosphere = anotherAtmosphere; + scene.renderForSpecs(); + expect(frameState.atmosphere).toBe(anotherAtmosphere); + }); }, "WebGL"