diff --git a/CHANGES.md b/CHANGES.md index 44bb08df1a30..07073defba24 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ - Added `token`, `mapServerData`, and `parameters` properties to `ArcGisMapServerImageryProvider.ConstructorOptions`. - Added `Ion.defaultTokenMessage` to customize the credit message shown on the map when using default Ion token. +- Added split terrain feature. ## 1.139.1 - 2026-03-05 diff --git a/packages/engine/Source/Scene/Globe.js b/packages/engine/Source/Scene/Globe.js index 65fc101d926d..414a2da68f93 100644 --- a/packages/engine/Source/Scene/Globe.js +++ b/packages/engine/Source/Scene/Globe.js @@ -27,6 +27,7 @@ import ImageryLayerCollection from "./ImageryLayerCollection.js"; import QuadtreePrimitive from "./QuadtreePrimitive.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; +import SplitDirection from "./SplitDirection.js"; import CesiumMath from "../Core/Math.js"; /** @@ -370,6 +371,15 @@ function Globe(ellipsoid) { * @default 0.3 */ this.vertexShadowDarkness = 0.3; + + /** + * The {@link SplitDirection} to apply, showing the terrain only on + * the left or right of the splitter control. + * + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + this.splitDirection = SplitDirection.NONE; } Object.defineProperties(Globe.prototype, { @@ -1055,6 +1065,7 @@ Globe.prototype.beginFrame = function (frameState) { tileProvider.undergroundColorAlphaByDistance = this._undergroundColorAlphaByDistance; tileProvider.lambertDiffuseMultiplier = this.lambertDiffuseMultiplier; + tileProvider.splitDirection = this.splitDirection; surface.beginFrame(frameState); } diff --git a/packages/engine/Source/Scene/GlobeSurfaceShaderSet.js b/packages/engine/Source/Scene/GlobeSurfaceShaderSet.js index 23668fbf8ea9..5f7cf9c26bbd 100644 --- a/packages/engine/Source/Scene/GlobeSurfaceShaderSet.js +++ b/packages/engine/Source/Scene/GlobeSurfaceShaderSet.js @@ -134,6 +134,7 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { const hasExaggeration = options.hasExaggeration; const showUndergroundColor = options.showUndergroundColor; const translucent = options.translucent; + const splitTerrain = options.splitTerrain; let quantization = 0; let quantizationDefine = ""; @@ -197,7 +198,8 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { (showUndergroundColor << 30) | (translucent << 31)) >>> 0) + - (applyDayNightAlpha ? 0x100000000 : 0); + (applyDayNightAlpha ? 0x100000000 : 0) + + (splitTerrain ? 0x200000000 : 0); let currentClippingShaderState = 0; if (defined(clippingPlanes) && clippingPlanes.length > 0) { @@ -382,6 +384,10 @@ GlobeSurfaceShaderSet.prototype.getShaderProgram = function (options) { vs.defines.push("EXAGGERATION"); } + if (splitTerrain) { + fs.defines.push("SPLIT_TERRAIN"); + } + let computeDayColor = "\ vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates, float nightBlend)\n\ diff --git a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js index cb53223ec999..ade5a2ef7e07 100644 --- a/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js +++ b/packages/engine/Source/Scene/GlobeSurfaceTileProvider.js @@ -47,6 +47,7 @@ import Primitive from "./Primitive.js"; import QuadtreeTileLoadState from "./QuadtreeTileLoadState.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; +import SplitDirection from "./SplitDirection.js"; import TerrainFillMesh from "./TerrainFillMesh.js"; import TerrainState from "./TerrainState.js"; import TileBoundingRegion from "./TileBoundingRegion.js"; @@ -91,6 +92,7 @@ function GlobeSurfaceTileProvider(options) { this.showGroundAtmosphere = false; this.shadows = ShadowMode.RECEIVE_ONLY; this.vertexShadowDarkness = 0.3; + this.splitDirection = SplitDirection.NONE; /** * The color to use to highlight terrain fill tiles. If undefined, fill tiles are not @@ -1859,6 +1861,9 @@ function createTileUniformMap(frameState, globeSurfaceTileProvider) { u_vertexShadowDarkness: function () { return this.properties.vertexShadowDarkness; }, + u_terrainSplitDirection: function () { + return globeSurfaceTileProvider.splitDirection; + }, // make a separate object so that changes to the properties are seen on // derived commands that combine another uniform map with this one. @@ -2361,6 +2366,8 @@ function addDrawCommandsForTile(tileProvider, tile, frameState) { surfaceShaderSetOptions.clippedByBoundaries = surfaceTile.clippedByBoundaries; surfaceShaderSetOptions.hasGeodeticSurfaceNormals = hasGeodeticSurfaceNormals; surfaceShaderSetOptions.hasExaggeration = hasExaggeration; + surfaceShaderSetOptions.splitTerrain = + tileProvider.splitDirection !== SplitDirection.NONE; const tileImageryCollection = surfaceTile.imagery; let imageryIndex = 0; diff --git a/packages/engine/Source/Scene/SkyAtmosphere.js b/packages/engine/Source/Scene/SkyAtmosphere.js index 9863027c96c8..eeec775944e6 100644 --- a/packages/engine/Source/Scene/SkyAtmosphere.js +++ b/packages/engine/Source/Scene/SkyAtmosphere.js @@ -21,6 +21,7 @@ import Axis from "./Axis.js"; import BlendingState from "./BlendingState.js"; import CullFace from "./CullFace.js"; import SceneMode from "./SceneMode.js"; +import SplitDirection from "./SplitDirection.js"; /** * An atmosphere drawn around the limb of the provided ellipsoid. Based on @@ -153,6 +154,16 @@ function SkyAtmosphere(ellipsoid) { */ this.brightnessShift = 0.0; + /** + * The {@link SplitDirection} to apply, showing the atmosphere only on + * the left or right of the splitter control. + * + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + this.splitDirection = SplitDirection.NONE; + this._splitDirection = undefined; + this._hueSaturationBrightness = new Cartesian3(); // outer radius, inner radius, dynamic atmosphere color flag @@ -197,6 +208,9 @@ function SkyAtmosphere(ellipsoid) { u_atmosphereMieAnisotropy: function () { return that.atmosphereMieAnisotropy; }, + u_splitDirection: function () { + return that.splitDirection; + }, }; } @@ -298,8 +312,15 @@ SkyAtmosphere.prototype.update = function (frameState, globe) { }); } + // Note that the splitDirection flag requires _two_ bits. + const splitDirectionFlag = + this.splitDirection === 0 ? 0 : this.splitDirection < 0.0 ? 1 : 2; + const flags = - colorCorrect | (perFragmentAtmosphere << 2) | (translucent << 3); + colorCorrect | + (perFragmentAtmosphere << 2) | + (translucent << 3) | + (splitDirectionFlag << 4); if (flags !== this._flags) { this._flags = flags; @@ -318,6 +339,10 @@ SkyAtmosphere.prototype.update = function (frameState, globe) { defines.push("GLOBE_TRANSLUCENT"); } + if (this.splitDirection !== SplitDirection.NONE) { + defines.push("SPLIT_ATMOSPHERE"); + } + const vs = new ShaderSource({ defines: defines, sources: [AtmosphereCommon, SkyAtmosphereCommon, SkyAtmosphereVS], diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 81b394de7ae3..b5f2a53be4fa 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -83,6 +83,10 @@ in vec2 v_clippingPosition; flat in int v_regionIndex; #endif +#ifdef SPLIT_TERRAIN +uniform float u_terrainSplitDirection; +#endif + #if defined(GROUND_ATMOSPHERE) || defined(FOG) && defined(DYNAMIC_ATMOSPHERE_LIGHTING) && (defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING)) uniform float u_minimumBrightness; #endif @@ -337,6 +341,15 @@ void main() } #endif +#ifdef SPLIT_TERRAIN + float splitPosition = czm_splitPosition; + if (u_terrainSplitDirection < 0.0 && gl_FragCoord.x > splitPosition) { + discard; + } else if (u_terrainSplitDirection > 0.0 && gl_FragCoord.x < splitPosition) { + discard; + } +#endif + #ifdef ENABLE_CLIPPING_PLANES float clipDistance = clip(gl_FragCoord, u_clippingPlanes, u_clippingPlanesMatrix); #endif diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index 06a3f630c327..82a689e5bc7d 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -2,6 +2,10 @@ in vec3 v_outerPositionWC; uniform vec3 u_hsbShift; +#ifdef SPLIT_ATMOSPHERE +uniform float u_splitDirection; +#endif + #ifndef PER_FRAGMENT_ATMOSPHERE in vec3 v_mieColor; in vec3 v_rayleighColor; @@ -11,6 +15,15 @@ in float v_translucent; void main (void) { + #ifdef SPLIT_ATMOSPHERE + float splitPosition = czm_splitPosition; + if (u_splitDirection < 0.0 && gl_FragCoord.x > splitPosition) { + discard; + } else if (u_splitDirection > 0.0 && gl_FragCoord.x < splitPosition) { + discard; + } + #endif + float lightEnum = u_radiiAndDynamicAtmosphereColor.z; vec3 lightDirection = czm_getDynamicAtmosphereLightDirection(v_outerPositionWC, lightEnum);