diff --git a/CHANGELOG.md b/CHANGELOG.md index f57d47a8ce..c5df143680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - "Accept" headers set in Request Transformers are not overwritten ([#4210](https://github.com/maplibre/maplibre-gl-js/pull/4210)) - ⚠️ Rename projMatrix to modelViewProjectionMatrix. Also rename invProjMatrix, alignedProjMatrix accordingly ([#4215](https://github.com/maplibre/maplibre-gl-js/pull/4215)) - Publish an unminified prod build ([#4265](https://github.com/maplibre/maplibre-gl-js/pull/4265)) +- Add option to display a realistic atmosphere when using a Globe projection ([#3888](https://github.com/maplibre/maplibre-gl-js/issues/3888)) ### 🐞 Bug fixes diff --git a/build/generate-struct-arrays.ts b/build/generate-struct-arrays.ts index ab2dc0effb..bd26b8a209 100644 --- a/build/generate-struct-arrays.ts +++ b/build/generate-struct-arrays.ts @@ -22,6 +22,7 @@ import fillExtrusionAttributes from '../src/data/bucket/fill_extrusion_attribute import {lineLayoutAttributes} from '../src/data/bucket/line_attributes'; import {lineLayoutAttributesExt} from '../src/data/bucket/line_attributes_ext'; import {patternAttributes} from '../src/data/bucket/pattern_attributes'; +import {atmosphereAttributes} from '../src/data/atmosphere_attributes'; // symbol layer specific arrays import { symbolLayoutAttributes, @@ -191,6 +192,9 @@ createStructArrayType('line_strip_index', createLayout([ {type: 'Uint16', name: 'vertices', components: 1} ])); +// atmosphere bounds array +createStructArrayType('atmosphere_bounds', atmosphereAttributes); + // paint vertex arrays // used by SourceBinder for float properties diff --git a/src/data/atmosphere_attributes.ts b/src/data/atmosphere_attributes.ts new file mode 100644 index 0000000000..322e637470 --- /dev/null +++ b/src/data/atmosphere_attributes.ts @@ -0,0 +1,5 @@ +import {createLayout} from '../util/struct_array'; + +export const atmosphereAttributes = createLayout([ + {name: 'a_pos', type: 'Float32', components: 4} +]); diff --git a/src/geo/projection/globe.ts b/src/geo/projection/globe.ts index d818be5960..add1f4baf4 100644 --- a/src/geo/projection/globe.ts +++ b/src/geo/projection/globe.ts @@ -72,11 +72,19 @@ export class GlobeProjection implements Projection { private _globeProjectionOverride = true; + private _globeMatrix: mat4 = mat4.create(); + private _globeMatrixNoCorrection: mat4 = mat4.create(); private _globeProjMatrix: mat4 = mat4.create(); private _globeProjMatrixNoCorrection: mat4 = mat4.create(); private _cameraPosition: vec3 = [0, 0, 0]; + private _globePosition: vec3 = [0, 0, 0]; + private _globeRadiusPixels: number = 0.0; + + private _projMatrix: mat4 = mat4.create(); + private _invProjMatrix: mat4 = mat4.create(); + get name(): string { return 'globe'; } @@ -129,6 +137,18 @@ export class GlobeProjection implements Projection { return granularitySettingsGlobe; } + get worldCenterPosition(): vec3 { + return this._globePosition; + } + + get worldSize(): number { + return this._globeRadiusPixels; + } + + get invProjMatrix(): mat4 { + return this._invProjMatrix; + } + /** * Returns whether globe view is allowed. * When allowed, globe fill function as normal, displaying a 3D planet, @@ -193,41 +213,55 @@ export class GlobeProjection implements Projection { // We want zoom levels to be consistent between globe and flat views. // This means that the pixel size of features at the map center point // should be the same for both globe and flat view. - const globeRadiusPixels = transform.worldSize / (2.0 * Math.PI) / Math.cos(transform.center.lat * Math.PI / 180); + this._globeRadiusPixels = transform.worldSize / (2.0 * Math.PI) / Math.cos(transform.center.lat * Math.PI / 180); + + mat4.perspective(this._projMatrix, transform.fov * Math.PI / 180, transform.width / transform.height, 0.5, transform.cameraToCenterDistance + this._globeRadiusPixels * 2.0); // just set the far plane far enough - we will calculate our own z in the vertex shader anyway + const invProjMatrix = mat4.create(); + mat4.invert(invProjMatrix, this._projMatrix); + this._invProjMatrix = invProjMatrix; // Construct a completely separate matrix for globe view - const globeMatrix = new Float64Array(16) as any; - const globeMatrixUncorrected = new Float64Array(16) as any; - mat4.perspective(globeMatrix, transform.fov * Math.PI / 180, transform.width / transform.height, 0.5, transform.cameraToCenterDistance + globeRadiusPixels * 2.0); // just set the far plane far enough - we will calculate our own z in the vertex shader anyway + const globeMatrix = mat4.identity(new Float64Array(16) as any); mat4.translate(globeMatrix, globeMatrix, [0, 0, -transform.cameraToCenterDistance]); mat4.rotateX(globeMatrix, globeMatrix, -transform.pitch * Math.PI / 180); mat4.rotateZ(globeMatrix, globeMatrix, -transform.angle); - mat4.translate(globeMatrix, globeMatrix, [0.0, 0, -globeRadiusPixels]); + mat4.translate(globeMatrix, globeMatrix, [0.0, 0, -this._globeRadiusPixels]); // Rotate the sphere to center it on viewed coordinates // Keep a atan-correction-free matrix for transformations done on the CPU with accurate math + const globeMatrixUncorrected = new Float64Array(16) as any; mat4.rotateX(globeMatrixUncorrected, globeMatrix, transform.center.lat * Math.PI / 180.0); mat4.rotateY(globeMatrixUncorrected, globeMatrixUncorrected, -transform.center.lng * Math.PI / 180.0); - mat4.scale(globeMatrixUncorrected, globeMatrixUncorrected, [globeRadiusPixels, globeRadiusPixels, globeRadiusPixels]); // Scale the unit sphere to a sphere with diameter of 1 - this._globeProjMatrixNoCorrection = globeMatrix; + mat4.scale(globeMatrixUncorrected, globeMatrixUncorrected, [this._globeRadiusPixels, this._globeRadiusPixels, this._globeRadiusPixels]); // Scale the unit sphere to a sphere with diameter of 1 + mat4.copy(this._globeMatrixNoCorrection, globeMatrixUncorrected); + mat4.mul(this._globeProjMatrixNoCorrection, this._projMatrix, this._globeMatrixNoCorrection); mat4.rotateX(globeMatrix, globeMatrix, transform.center.lat * Math.PI / 180.0 - this._errorCorrectionUsable); mat4.rotateY(globeMatrix, globeMatrix, -transform.center.lng * Math.PI / 180.0); - mat4.scale(globeMatrix, globeMatrix, [globeRadiusPixels, globeRadiusPixels, globeRadiusPixels]); // Scale the unit sphere to a sphere with diameter of 1 + mat4.scale(this._globeMatrix, globeMatrix, [this._globeRadiusPixels, this._globeRadiusPixels, this._globeRadiusPixels]); // Scale the unit sphere to a sphere with diameter of 1 + mat4.mul(globeMatrix, this._projMatrix, this._globeMatrix); this._globeProjMatrix = globeMatrix; - const invProj = mat4.create(); - mat4.invert(invProj, globeMatrix); + const invGlobeProj = mat4.create(); + mat4.invert(invGlobeProj, this._globeProjMatrix); const cameraPos: vec4 = [0, 0, -1, 1]; - vec4.transformMat4(cameraPos, cameraPos, invProj); + vec4.transformMat4(cameraPos, cameraPos, invGlobeProj); this._cameraPosition = [ cameraPos[0] / cameraPos[3], cameraPos[1] / cameraPos[3], cameraPos[2] / cameraPos[3] ]; - this._cachedClippingPlane = this._computeClippingPlane(transform, globeRadiusPixels); + const globePos: vec4 = [0, 0, 0, 1]; + vec4.transformMat4(globePos, globePos, this._globeMatrix); + this._globePosition = [ + globePos[0] / globePos[3], + globePos[1] / globePos[3], + globePos[2] / globePos[3] + ]; + + this._cachedClippingPlane = this._computeClippingPlane(transform, this._globeRadiusPixels); } public getProjectionData(canonicalTileCoords: {x: number; y: number; z: number}, tilePosMatrix: mat4, useAtanCorrection: boolean = true): ProjectionData { @@ -391,7 +425,7 @@ export class GlobeProjection implements Projection { } public transformLightDirection(transform: { center: LngLat }, dir: vec3): vec3 { - const sphereX = transform.center.lng * Math.PI / 180.0; + const sphereX = -transform.center.lng * Math.PI / 180.0; const sphereY = transform.center.lat * Math.PI / 180.0; const len = Math.cos(sphereY); diff --git a/src/geo/projection/mercator.ts b/src/geo/projection/mercator.ts index 2f32d10d39..7117858315 100644 --- a/src/geo/projection/mercator.ts +++ b/src/geo/projection/mercator.ts @@ -19,8 +19,11 @@ export const MercatorShaderDefine = '#define PROJECTION_MERCATOR'; export const MercatorShaderVariantKey = 'mercator'; export class MercatorProjection implements Projection { - private _cachedMesh: Mesh = null; + private _cachedMesh: Mesh | null = null; private _cameraPosition: vec3 = [0, 0, 0]; + private _worldCenterPosition: vec3 = [0, 0, 0]; + private _worldSize: number = 0; + private _invProjMatrix: mat4 = mat4.create(); get name(): string { return 'mercator'; @@ -64,6 +67,18 @@ export class MercatorProjection implements Projection { return SubdivisionGranularitySetting.noSubdivision; } + get worldCenterPosition(): vec3 { + return this._worldCenterPosition; + } + + get worldSize(): number { + return this._worldSize; + } + + get invProjMatrix(): mat4 { + return this._invProjMatrix; + } + public isRenderingDirty(): boolean { // Mercator projection does no animations of its own, so rendering is never dirty from its perspective. return false; @@ -77,14 +92,25 @@ export class MercatorProjection implements Projection { // Do nothing. } - public updateProjection(t: { invModelViewProjectionMatrix: mat4 }): void { + public updateProjection(transform: TransformLike): void { + this._worldSize = transform.worldSize; + this._invProjMatrix = mat4.clone(transform.invModelViewProjectionMatrix); + const cameraPos: vec4 = [0, 0, -1, 1]; - vec4.transformMat4(cameraPos, cameraPos, t.invModelViewProjectionMatrix); + vec4.transformMat4(cameraPos, cameraPos, transform.invModelViewProjectionMatrix); this._cameraPosition = [ cameraPos[0] / cameraPos[3], cameraPos[1] / cameraPos[3], cameraPos[2] / cameraPos[3] ]; + + const worldPos: vec4 = [0, 0, 0, 1]; + vec4.transformMat4(worldPos, worldPos, transform.invModelViewProjectionMatrix); + this._worldCenterPosition = [ + worldPos[0] / worldPos[3], + worldPos[1] / worldPos[3], + worldPos[2] / worldPos[3] + ]; } public getProjectionData(canonicalTileCoords: {x: number; y: number; z: number}, tilePosMatrix: mat4): ProjectionData { diff --git a/src/geo/projection/projection.ts b/src/geo/projection/projection.ts index 48104fc19f..9836f64dbb 100644 --- a/src/geo/projection/projection.ts +++ b/src/geo/projection/projection.ts @@ -92,6 +92,21 @@ export interface Projection { */ get vertexShaderPreludeCode(): string; + /** + * World center in camera frame. + */ + get worldCenterPosition(): vec3; + + /** + * World size in pixel. + */ + get worldSize(): number; + + /** + * Inverse projection matrix from camera to clip plane. + */ + get invProjMatrix(): mat4; + /** * @internal * An object describing how much subdivision should be applied to rendered geometry. diff --git a/src/render/draw_atmosphere.ts b/src/render/draw_atmosphere.ts new file mode 100644 index 0000000000..0f788a54d3 --- /dev/null +++ b/src/render/draw_atmosphere.ts @@ -0,0 +1,80 @@ +import {StencilMode} from '../gl/stencil_mode'; +import {DepthMode} from '../gl/depth_mode'; +import {CullFaceMode} from '../gl/cull_face_mode'; +import {atmosphereUniformValues} from './program/atmosphere_program'; + +import type {Painter} from './painter'; +import {ColorMode} from '../gl/color_mode'; +import Sky from '../style/sky'; +import {Light} from '../style/light'; +import {AtmosphereBoundsArray, TriangleIndexArray} from '../data/array_types.g'; +import {atmosphereAttributes} from '../data/atmosphere_attributes'; +import {Mesh} from './mesh'; +import {SegmentVector} from '../data/segment'; +import {Transform} from '../geo/transform'; +import {mat4, vec3} from 'gl-matrix'; + +function getSunPos(light: Light, transform: Transform): vec3 { + const _lp = light.properties.get('position'); + const lightPos = [-_lp.x, -_lp.y, -_lp.z] as vec3; + + const lightMat = mat4.identity(new Float64Array(16) as any); + + if (light.properties.get('anchor') === 'map') { + mat4.rotateX(lightMat, lightMat, -transform.pitch * Math.PI / 180); + mat4.rotateZ(lightMat, lightMat, -transform.angle); + mat4.rotateX(lightMat, lightMat, transform.center.lat * Math.PI / 180.0); + mat4.rotateY(lightMat, lightMat, -transform.center.lng * Math.PI / 180.0); + } + + vec3.transformMat4(lightPos, lightPos, lightMat); + + return lightPos; +} + +export function drawAtmosphere(painter: Painter, sky: Sky, light: Light) { + const context = painter.context; + const gl = context.gl; + const program = painter.useProgram('atmosphere'); + const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadOnly, [0, 1]); + + const projection = painter.style.projection; + const projectionData = projection.getProjectionData(null, null); + + const sunPos = getSunPos(light, painter.transform); + + const atmosphereBlend = sky.getAtmosphereBlend(); + if (atmosphereBlend === 0) { + // Don't draw anythink if atmosphere is fully transparent + return; + } + + const globePosition = projection.worldCenterPosition; + const globeRadius = projection.worldSize; + const invProjMatrix = projection.invProjMatrix; + + const uniformValues = atmosphereUniformValues(sunPos, atmosphereBlend, globePosition, globeRadius, invProjMatrix); + + // Create the atmosphere mesh the first time we need it + if (!sky.atmosphereMesh) { + const vertexArray = new AtmosphereBoundsArray(); + vertexArray.emplaceBack(-1, -1, 0.0, 1.0); + vertexArray.emplaceBack(+1, -1, 0.0, 1.0); + vertexArray.emplaceBack(+1, +1, 0.0, 1.0); + vertexArray.emplaceBack(-1, +1, 0.0, 1.0); + + const indexArray = new TriangleIndexArray(); + indexArray.emplaceBack(0, 1, 2); + indexArray.emplaceBack(0, 2, 3); + + sky.atmosphereMesh = new Mesh( + context.createVertexBuffer(vertexArray, atmosphereAttributes.members), + context.createIndexBuffer(indexArray), + SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length) + ); + } + + const mesh = sky.atmosphereMesh; + + program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, ColorMode.alphaBlended, CullFaceMode.disabled, uniformValues, null, projectionData, 'atmosphere', mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); +} diff --git a/src/render/painter.ts b/src/render/painter.ts index be233f55b1..46247ab879 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -27,6 +27,7 @@ import {drawFillExtrusion} from './draw_fill_extrusion'; import {drawHillshade} from './draw_hillshade'; import {drawRaster} from './draw_raster'; import {drawBackground} from './draw_background'; +import {drawAtmosphere} from './draw_atmosphere'; import {drawDebug, drawDebugPadding, selectDebugSource} from './draw_debug'; import {drawCustom} from './draw_custom'; import {drawDepth, drawCoords} from './draw_terrain'; @@ -536,6 +537,11 @@ export class Painter { this.renderLayer(this, sourceCache, layer, coords); } + // Render atmosphere, only for Globe projection + if (this.style.projection.name === 'globe') { + drawAtmosphere(this, this.style.sky, this.style.light); + } + if (this.options.showTileBoundaries) { const selectedSource = selectDebugSource(this.style, this.transform.zoom); if (selectedSource) { diff --git a/src/render/program/atmosphere_program.ts b/src/render/program/atmosphere_program.ts new file mode 100644 index 0000000000..b0d4f301b5 --- /dev/null +++ b/src/render/program/atmosphere_program.ts @@ -0,0 +1,35 @@ +import type {Context} from '../../gl/context'; +import {UniformValues, UniformLocations, Uniform1f, Uniform3f, UniformMatrix4f} from '../uniform_binding'; +import {mat4, vec3} from 'gl-matrix'; + +export type atmosphereUniformsType = { + 'u_sun_pos': Uniform3f; + 'u_atmosphere_blend': Uniform1f; + 'u_globe_position': Uniform3f; + 'u_globe_radius': Uniform1f; + 'u_inv_proj_matrix': UniformMatrix4f; +}; + +const atmosphereUniforms = (context: Context, locations: UniformLocations): atmosphereUniformsType => ({ + 'u_sun_pos': new Uniform3f(context, locations.u_sun_pos), + 'u_atmosphere_blend': new Uniform1f(context, locations.u_atmosphere_blend), + 'u_globe_position': new Uniform3f(context, locations.u_globe_position), + 'u_globe_radius': new Uniform1f(context, locations.u_globe_radius), + 'u_inv_proj_matrix': new UniformMatrix4f(context, locations.u_inv_proj_matrix), +}); + +const atmosphereUniformValues = ( + sunPos: vec3, + atmosphereBlend: number, + globePosition: vec3, + globeRadius: number, + invProjMatrix: mat4, +): UniformValues => ({ + 'u_sun_pos': sunPos, + 'u_atmosphere_blend': atmosphereBlend, + 'u_globe_position': globePosition, + 'u_globe_radius': globeRadius, + 'u_inv_proj_matrix': invProjMatrix, +}); + +export {atmosphereUniforms, atmosphereUniformValues}; diff --git a/src/render/program/program_uniforms.ts b/src/render/program/program_uniforms.ts index 1ee3e76780..9d34331e41 100644 --- a/src/render/program/program_uniforms.ts +++ b/src/render/program/program_uniforms.ts @@ -11,6 +11,7 @@ import {symbolIconUniforms, symbolSDFUniforms, symbolTextAndIconUniforms} from ' import {backgroundUniforms, backgroundPatternUniforms} from './background_program'; import {terrainUniforms, terrainDepthUniforms, terrainCoordsUniforms} from './terrain_program'; import {projectionErrorMeasurementUniforms} from './projection_error_measurement_program'; +import {atmosphereUniforms} from './atmosphere_program'; const emptyUniforms = (_: any, __: any): any => {}; @@ -44,4 +45,5 @@ export const programUniforms = { terrainDepth: terrainDepthUniforms, terrainCoords: terrainCoordsUniforms, projectionErrorMeasurement: projectionErrorMeasurementUniforms, + atmosphere: atmosphereUniforms, }; diff --git a/src/render/program/projection_program.ts b/src/render/program/projection_program.ts index 27e391037e..378502bf79 100644 --- a/src/render/program/projection_program.ts +++ b/src/render/program/projection_program.ts @@ -1,6 +1,6 @@ -import {Uniform1f, Uniform4f, UniformLocations, UniformMatrix4f} from '../uniform_binding'; +import {Uniform1f, Uniform3f, Uniform4f, UniformLocations, UniformMatrix4f} from '../uniform_binding'; import {Context} from '../../gl/context'; -import {mat4} from 'gl-matrix'; +import {mat4, vec3} from 'gl-matrix'; export type ProjectionPreludeUniformsType = { 'u_projection_matrix': UniformMatrix4f; @@ -15,7 +15,7 @@ export const projectionUniforms = (context: Context, locations: UniformLocations 'u_projection_tile_mercator_coords': new Uniform4f(context, locations.u_projection_tile_mercator_coords), 'u_projection_clipping_plane': new Uniform4f(context, locations.u_projection_clipping_plane), 'u_projection_transition': new Uniform1f(context, locations.u_projection_transition), - 'u_projection_fallback_matrix': new UniformMatrix4f(context, locations.u_projection_fallback_matrix) + 'u_projection_fallback_matrix': new UniformMatrix4f(context, locations.u_projection_fallback_matrix), }); export type ProjectionData = { diff --git a/src/shaders/atmosphere.fragment.glsl b/src/shaders/atmosphere.fragment.glsl new file mode 100644 index 0000000000..813b16de1e --- /dev/null +++ b/src/shaders/atmosphere.fragment.glsl @@ -0,0 +1,168 @@ +in vec3 view_direction; + +uniform vec3 u_sun_pos; +uniform vec3 u_globe_position; +uniform float u_globe_radius; +uniform float u_atmosphere_blend; + +/* + * Shader use from https://github.com/wwwtyro/glsl-atmosphere + * Made some change to adapt to MapLibre Globe geometry + */ + +const float PI = 3.141592653589793; +const int iSteps = 5; +const int jSteps = 3; + +/* radius of the planet */ +const float EARTH_RADIUS = 6371e3; +/* radius of the atmosphere */ +const float ATMOS_RADIUS = 6471e3; + +vec2 rsi(vec3 r0, vec3 rd, float sr) { + // ray-sphere intersection that assumes + // the sphere is centered at the origin. + // No intersection when result.x > result.y + float a = dot(rd, rd); + float b = 2.0 * dot(rd, r0); + float c = dot(r0, r0) - (sr * sr); + float d = (b*b) - 4.0*a*c; + if (d < 0.0) return vec2(1e5,-1e5); + return vec2( + (-b - sqrt(d))/(2.0*a), + (-b + sqrt(d))/(2.0*a) + ); +} + + +vec4 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g) { + // Normalize the sun and view directions. + pSun = normalize(pSun); + r = normalize(r); + + // Calculate the step size of the primary ray. + vec2 p = rsi(r0, r, rAtmos); + if (p.x > p.y) return vec4(0,0,0,0); + + if (p.x < 0.0) { + p.x = 0.0; + } + + vec3 pos = r0 + r * p.x; + + vec2 p2 = rsi(r0, r, rPlanet); + if (p2.x <= p2.y && p2.x > 0.0) { + p.y = min(p.y, p2.x); + } + float iStepSize = (p.y - p.x) / float(iSteps); + + // Initialize the primary ray time. + float iTime = p.x + iStepSize * 0.5; + + // Initialize accumulators for Rayleigh and Mie scattering. + vec3 totalRlh = vec3(0,0,0); + vec3 totalMie = vec3(0,0,0); + + // Initialize optical depth accumulators for the primary ray. + float iOdRlh = 0.0; + float iOdMie = 0.0; + + // Calculate the Rayleigh and Mie phases. + float mu = dot(r, pSun); + float mumu = mu * mu; + float gg = g * g; + float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); + float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (pow(1.0 + gg - 2.0 * mu * g, 1.5) * (2.0 + gg)); + + // Sample the primary ray. + for (int i = 0; i < iSteps; i++) { + + // Calculate the primary ray sample position. + vec3 iPos = r0 + r * iTime; + + // Calculate the height of the sample. + float iHeight = length(iPos) - rPlanet; + + // Calculate the optical depth of the Rayleigh and Mie scattering for this step. + float odStepRlh = exp(-iHeight / shRlh) * iStepSize; + float odStepMie = exp(-iHeight / shMie) * iStepSize; + + // Accumulate optical depth. + iOdRlh += odStepRlh; + iOdMie += odStepMie; + + // Calculate the step size of the secondary ray. + float jStepSize = rsi(iPos, pSun, rAtmos).y / float(jSteps); + + // Initialize the secondary ray time. + float jTime = jStepSize * 0.5; + + // Initialize optical depth accumulators for the secondary ray. + float jOdRlh = 0.0; + float jOdMie = 0.0; + + // Sample the secondary ray. + for (int j = 0; j < jSteps; j++) { + + // Calculate the secondary ray sample position. + vec3 jPos = iPos + pSun * jTime; + + // Calculate the height of the sample. + float jHeight = length(jPos) - rPlanet; + + // Accumulate the optical depth. + jOdRlh += exp(-jHeight / shRlh) * jStepSize; + jOdMie += exp(-jHeight / shMie) * jStepSize; + + // Increment the secondary ray time. + jTime += jStepSize; + } + + // Calculate attenuation. + vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh))); + + // Accumulate scattering. + totalRlh += odStepRlh * attn; + totalMie += odStepMie * attn; + + // Increment the primary ray time. + iTime += iStepSize; + } + + // Calculate opacity + //float opacity = exp(-(length(kRlh) * iOdRlh + kMie * iOdMie)); + float opacity = min(0.75, exp(-(length(kRlh) * length(totalRlh) + kMie * length(totalMie)))); + + // Calculate the final color. + vec3 color = iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie); + + return vec4(color, opacity); +} + +void main() { + // The globe is small compare to real Earth. + // To still have a correct atmosphere rendering, we scale the whole world to the EARTH_RADIUS. + // Change camera position accordingly. + vec3 scale_camera_pos = -u_globe_position * EARTH_RADIUS / u_globe_radius; + + vec4 color = atmosphere( + normalize(view_direction), // ray direction + scale_camera_pos, // ray origin + u_sun_pos, // position of the sun + 22.0, // intensity of the sun + EARTH_RADIUS, // radius of the planet in meters + ATMOS_RADIUS, // radius of the atmosphere in meters + vec3(5.5e-6, 13.0e-6, 22.4e-6), // Rayleigh scattering coefficient + 21e-6, // Mie scattering coefficient + 8e3, // Rayleigh scale height + 1.2e3, // Mie scale height + 0.758 // Mie preferred scattering direction + ); + + // Apply exposure. + color.xyz = 1.0 - exp(-1.0 * color.xyz); + + vec4 no_effect_color = vec4(0, 0, 0, 0); + + gl_FragColor = mix(color, no_effect_color, 1.0 - u_atmosphere_blend); +} diff --git a/src/shaders/atmosphere.vertex.glsl b/src/shaders/atmosphere.vertex.glsl new file mode 100644 index 0000000000..e766372aaa --- /dev/null +++ b/src/shaders/atmosphere.vertex.glsl @@ -0,0 +1,11 @@ +in vec4 a_pos; + +uniform mat4 u_inv_proj_matrix; + +out vec3 view_direction; + +void main() { + // Compute each camera ray + view_direction = (u_inv_proj_matrix * a_pos).xyz; + gl_Position = a_pos; +} \ No newline at end of file diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index f7bfc2e4b2..2710eebd58 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -61,6 +61,8 @@ import projectionErrorMeasurementVert from './projection_error_measurement.verte import projectionErrorMeasurementFrag from './projection_error_measurement.fragment.glsl.g'; import projectionMercatorVert from './_projection_mercator.vertex.glsl.g'; import projectionGlobeVert from './_projection_globe.vertex.glsl.g'; +import atmosphereFrag from './atmosphere.fragment.glsl.g'; +import atmosphereVert from './atmosphere.vertex.glsl.g'; export type PreparedShader = { fragmentSource: string; @@ -101,7 +103,8 @@ export const shaders = { terrain: compile(terrainFrag, terrainVert), terrainDepth: compile(terrainDepthFrag, terrainVert), terrainCoords: compile(terrainCoordsFrag, terrainVert), - projectionErrorMeasurement: compile(projectionErrorMeasurementFrag, projectionErrorMeasurementVert) + projectionErrorMeasurement: compile(projectionErrorMeasurementFrag, projectionErrorMeasurementVert), + atmosphere: compile(atmosphereFrag, atmosphereVert), }; // Expand #pragmas to #ifdefs. diff --git a/src/style/sky.test.ts b/src/style/sky.test.ts new file mode 100644 index 0000000000..037e0f4af4 --- /dev/null +++ b/src/style/sky.test.ts @@ -0,0 +1,85 @@ +import Sky from './sky'; +import {latest as styleSpec, SkySpecification} from '@maplibre/maplibre-gl-style-spec'; +import {EvaluationParameters} from './evaluation_parameters'; +import {TransitionParameters} from './properties'; + +const spec = styleSpec.sky; + +test('Sky with defaults', () => { + const sky = new Sky({}); + sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters); + + expect(sky.properties.get('atmosphere-blend')).toEqual(spec['atmosphere-blend'].default); +}); + +test('Sky with options', () => { + const sky = new Sky({ + 'atmosphere-blend': 0.4 + }); + sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters); + + expect(sky.properties.get('atmosphere-blend')).toBe(0.4); +}); + +test('Sky with interpolate function', () => { + const sky = new Sky({ + 'atmosphere-blend': [ + 'interpolate', + ['linear'], + ['zoom'], + 0, 1, + 5, 1, + 7, 0 + ] + } as SkySpecification); + sky.recalculate({zoom: 6, zoomHistory: {}} as EvaluationParameters); + + expect(sky.properties.get('atmosphere-blend')).toBe(0.5); +}); + +test('Sky#getSky', () => { + const defaults = {'atmosphere-blend': 0.8}; + + expect(new Sky(defaults).getSky()).toEqual(defaults); +}); + +describe('Sky#setSky', () => { + test('sets Sky', () => { + const sky = new Sky({}); + sky.setSky({'atmosphere-blend': 1} as SkySpecification); + sky.updateTransitions({ + now: 0, + transition: { + duration: 3000, + delay: 0 + } + } as any as TransitionParameters); + sky.recalculate({zoom: 16, zoomHistory: {}, now: 1500} as EvaluationParameters); + expect(sky.properties.get('atmosphere-blend')).toBe(0.9); + }); + + test('validates by default', () => { + const sky = new Sky({}); + const skySpy = jest.spyOn(sky, '_validate'); + jest.spyOn(console, 'error').mockImplementation(() => { }); + sky.setSky({'atmosphere-blend': -1}); + sky.updateTransitions({transition: false} as any as TransitionParameters); + sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters); + expect(skySpy).toHaveBeenCalledTimes(1); + expect(console.error).toHaveBeenCalledTimes(1); + expect(skySpy.mock.calls[0][2]).toEqual({}); + }); + + test('respects validation option', () => { + const sky = new Sky({}); + + const skySpy = jest.spyOn(sky, '_validate'); + sky.setSky({'atmosphere-blend': -1} as any, {validate: false}); + sky.updateTransitions({transition: false} as any as TransitionParameters); + sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters); + + expect(skySpy).toHaveBeenCalledTimes(1); + expect(skySpy.mock.calls[0][2]).toEqual({validate: false}); + expect(sky.properties.get('atmosphere-blend')).toBe(-1); + }); +}); diff --git a/src/style/sky.ts b/src/style/sky.ts new file mode 100644 index 0000000000..afafb599ab --- /dev/null +++ b/src/style/sky.ts @@ -0,0 +1,91 @@ +import {DataConstantProperty, PossiblyEvaluated, Properties, Transitionable, Transitioning, TransitionParameters} from './properties'; +import {Evented} from '../util/evented'; +import {EvaluationParameters} from './evaluation_parameters'; +import {emitValidationErrors, validateSky, validateStyle} from './validate_style'; +import {latest as styleSpec} from '@maplibre/maplibre-gl-style-spec'; +import type {StylePropertySpecification, SkySpecification} from '@maplibre/maplibre-gl-style-spec'; +import {Mesh} from '../render/mesh'; +import {StyleSetterOptions} from './style'; + +type SkyProps = { + 'atmosphere-blend': DataConstantProperty; +}; + +type SkyPropsPossiblyEvaluated = { + 'atmosphere-blend': number; +}; + +let skyProperties: Properties; + +const TRANSITION_SUFFIX = '-transition'; + +export default class Sky extends Evented { + properties: PossiblyEvaluated; + + /** + * This is used to cache the gl mesh for the atmosphere, it should be initialized only once. + */ + atmosphereMesh: Mesh | undefined; + + _transitionable: Transitionable; + _transitioning: Transitioning; + + constructor(skyOptions?: SkySpecification) { + super(); + skyProperties = skyProperties || new Properties({ + 'atmosphere-blend': new DataConstantProperty(styleSpec.sky['atmosphere-blend'] as StylePropertySpecification), + }); + this._transitionable = new Transitionable(skyProperties); + this.setSky(skyOptions); + this._transitioning = this._transitionable.untransitioned(); + } + + setSky(sky?: SkySpecification, options: StyleSetterOptions = {}) { + if (this._validate(validateSky, sky, options)) return; + + for (const name in sky) { + const value = sky[name]; + if (name.endsWith(TRANSITION_SUFFIX)) { + this._transitionable.setTransition(name.slice(0, -TRANSITION_SUFFIX.length) as keyof SkyProps, value); + } else { + this._transitionable.setValue(name as keyof SkyProps, value); + } + } + } + + getSky(): SkySpecification { + return this._transitionable.serialize(); + } + + updateTransitions(parameters: TransitionParameters) { + this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); + } + + hasTransition() { + return this._transitioning.hasTransition(); + } + + recalculate(parameters: EvaluationParameters) { + this.properties = this._transitioning.possiblyEvaluate(parameters); + } + + _validate(validate: Function, value: unknown, options?: { + validate?: boolean; + }) { + if (options && options.validate === false) { + return false; + } + + return emitValidationErrors(this, validate.call(validateStyle, { + value, + // Workaround for https://github.com/mapbox/mapbox-gl-js/issues/2407 + style: {glyphs: true, sprite: true}, + styleSpec + })); + } + + getAtmosphereBlend(): number { + // Get the atmosphere blend coefficient + return this.properties.get('atmosphere-blend'); + } +} diff --git a/src/style/style.test.ts b/src/style/style.test.ts index 712387cc8f..b15e90c08a 100644 --- a/src/style/style.test.ts +++ b/src/style/style.test.ts @@ -663,6 +663,7 @@ describe('Style#setState', () => { spys.push(jest.spyOn(style, 'setGeoJSONSourceData').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style, 'setLayerZoomRange').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style, 'setLight').mockImplementation((() => {}) as any)); + spys.push(jest.spyOn(style, 'setSky').mockImplementation((() => {}) as any)); await style.once('style.load'); const didChange = style.setState(createStyleJSON()); expect(didChange).toBeFalsy(); @@ -691,6 +692,9 @@ describe('Style#setState', () => { }, light: { anchor: 'viewport' + }, + sky: { + 'atmosphere-blend': 0 } }); style.loadJSON(styleJson); @@ -711,6 +715,7 @@ describe('Style#setState', () => { spys.push(jest.spyOn(style, 'setSprite').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style, 'setProjection').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style.map, 'setTerrain').mockImplementation((() => {}) as any)); + spys.push(jest.spyOn(style, 'setSky').mockImplementation((() => {}) as any)); const newStyle = JSON.parse(JSON.stringify(styleJson)) as StyleSpecification; newStyle.layers[0].paint = {'text-color': '#7F7F7F',}; @@ -739,6 +744,9 @@ describe('Style#setState', () => { }; newStyle.zoom = 2; newStyle.projection = {type: 'globe'}; + newStyle.sky = { + 'atmosphere-blend': 1 + }; const didChange = style.setState(newStyle); expect(didChange).toBeTruthy(); for (const spy of spys) { @@ -766,6 +774,7 @@ describe('Style#setState', () => { spys.push(jest.spyOn(style, 'setGlyphs').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style, 'setSprite').mockImplementation((() => {}) as any)); spys.push(jest.spyOn(style.map, 'setTerrain').mockImplementation((() => {}) as any)); + spys.push(jest.spyOn(style, 'setSky').mockImplementation((() => {}) as any)); const newStyleJson = createStyleJSON(); newStyleJson.transition = {duration: 5}; @@ -2570,4 +2579,38 @@ describe('Style#serialize', () => { expect(style.serialize().projection).toBeDefined(); expect(style.serialize().projection.type).toBe('globe'); }); + + test('include sky property when map has sky', async () => { + const sky = { + 'atmosphere-blend': 0.5, + }; + const styleJson = createStyleJSON({sky}); + const style = new Style(getStubMap()); + style.loadJSON(styleJson); + + await style.once('style.load'); + expect(style.serialize().sky).toBe(sky); + }); + + test('include sky property when sky is set', async () => { + const sky = { + 'atmosphere-blend': 0.5, + }; + const style = new Style(getStubMap()); + style.loadJSON(createStyleJSON()); + + await style.once('style.load'); + style.setSky(sky); + + expect(style.serialize().sky).toBeDefined(); + expect(style.serialize().sky).toBe(sky); + }); + + test('do not include sky property when map does not have sky', async () => { + const style = new Style(getStubMap()); + style.loadJSON(createStyleJSON()); + + await style.once('style.load'); + expect(style.serialize().sky).toBeUndefined(); + }); }); diff --git a/src/style/style.ts b/src/style/style.ts index a0e324708d..d393002f50 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -49,7 +49,8 @@ import type { SourceSpecification, SpriteSpecification, DiffOperations, - ProjectionSpecification + ProjectionSpecification, + SkySpecification } from '@maplibre/maplibre-gl-style-spec'; import type {CustomLayerInterface} from './style_layer/custom_style_layer'; import type {Validator} from './validate_style'; @@ -62,6 +63,7 @@ import { } from '../util/actor_messages'; import {Projection} from '../geo/projection/projection'; import {createProjectionFromName} from '../geo/projection/projection_factory'; +import Sky from './sky'; const empty = emptyStyle() as StyleSpecification; /** @@ -188,6 +190,7 @@ export class Style extends Evented { lineAtlas: LineAtlas; light: Light; projection: Projection; + sky: Sky; _frameRequest: AbortController; _loadStyleRequest: AbortController; @@ -343,6 +346,8 @@ export class Style extends Evented { this.light = new Light(this.stylesheet.light); this.projection = createProjectionFromName(this.stylesheet.projection?.type || 'mercator'); + this.sky = new Sky(this.stylesheet.sky); + this.map.setTerrain(this.stylesheet.terrain ?? null); this.fire(new Event('data', {dataType: 'style'})); @@ -527,6 +532,10 @@ export class Style extends Evented { return true; } + if (this.sky && this.sky.hasTransition()) { + return true; + } + for (const id in this.sourceCaches) { if (this.sourceCaches[id].hasTransition()) { return true; @@ -585,6 +594,7 @@ export class Style extends Evented { } this.light.updateTransitions(parameters); + this.sky.updateTransitions(parameters); this._resetUpdates(); } @@ -629,6 +639,7 @@ export class Style extends Evented { } this.light.recalculate(parameters); + this.sky.recalculate(parameters); this.z = parameters.zoom; if (changed) { @@ -771,7 +782,8 @@ export class Style extends Evented { operations.push(() => this.map.setTerrain.apply(this, op.args)); break; case 'setSky': - throw new Error('Unimplemented: setSky'); + operations.push(() => this.setSky.apply(this, op.args)); + break; case 'setProjection': this.setProjection.apply(this, op.args); break; @@ -1287,6 +1299,7 @@ export class Style extends Evented { name: myStyleSheet.name, metadata: myStyleSheet.metadata, light: myStyleSheet.light, + sky: myStyleSheet.sky, center: myStyleSheet.center, zoom: myStyleSheet.zoom, bearing: myStyleSheet.bearing, @@ -1500,6 +1513,36 @@ export class Style extends Evented { this.projection = createProjectionFromName(projection.type); } + getSky(): SkySpecification { + return this.sky?.getSky(); + } + + setSky(skyOptions?: SkySpecification, options: StyleSetterOptions = {}) { + this._checkLoaded(); + + const sky = this.sky.getSky(); + let _update = false; + for (const key in skyOptions) { + if (!deepEqual(skyOptions[key], sky[key])) { + _update = true; + break; + } + } + if (!_update) return; + + const parameters = { + now: browser.now(), + transition: extend({ + duration: 300, + delay: 0 + }, this.stylesheet.transition) + }; + + this.stylesheet.sky = skyOptions; + this.sky.setSky(skyOptions, options); + this.sky.updateTransitions(parameters); + } + _validate(validate: Validator, key: string, value: any, props: any, options: { validate?: boolean; } = {}) { diff --git a/src/style/validate_style.ts b/src/style/validate_style.ts index 9af298694f..3f8c00566c 100644 --- a/src/style/validate_style.ts +++ b/src/style/validate_style.ts @@ -17,6 +17,7 @@ type ValidateStyle = { glyphs: Validator; layer: Validator; light: Validator; + sky: Validator; terrain: Validator; filter: Validator; paintProperty: Validator; @@ -28,6 +29,7 @@ export const validateStyle = (validateStyleMin as unknown as ValidateStyle); export const validateSource = validateStyle.source; export const validateLight = validateStyle.light; +export const validateSky = validateStyle.sky; export const validateTerrain = validateStyle.terrain; export const validateFilter = validateStyle.filter; export const validatePaintProperty = validateStyle.paintProperty; diff --git a/src/ui/map.ts b/src/ui/map.ts index ffec31b54d..2521e5803c 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -54,7 +54,8 @@ import type { LightSpecification, SourceSpecification, TerrainSpecification, - ProjectionSpecification + ProjectionSpecification, + SkySpecification } from '@maplibre/maplibre-gl-style-spec'; import type {MapGeoJSONFeature} from '../util/vectortile_to_geojson'; import type {ControlPosition, IControl} from './control/control'; @@ -2664,6 +2665,32 @@ export class Map extends Camera { return this.style.getLight(); } + /** + * Sets the value of style's sky properties. + * + * @param sky - Sky properties to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/sky). + * @param options - Options object. + * + * @example + * ```ts + * map.setSky({'atmosphere-blend': 1.0}); + * ``` + */ + setSky(sky: SkySpecification, options: StyleSetterOptions = {}) { + this._lazyInitEmptyStyle(); + this.style.setSky(sky, options); + return this._update(true); + } + + /** + * Returns the value of the style's sky. + * + * @returns sky properties of the style. + */ + getSky(): SkySpecification { + return this.style.getSky(); + } + /** * Sets the `state` of a feature. * A feature's `state` is a set of user-defined key-value pairs that are assigned to a feature at runtime. diff --git a/src/ui/map_tests/map_sky.test.ts b/src/ui/map_tests/map_sky.test.ts new file mode 100644 index 0000000000..1f9ddf5e08 --- /dev/null +++ b/src/ui/map_tests/map_sky.test.ts @@ -0,0 +1,42 @@ +import {createMap, beforeMapTest, sleep} from '../../util/test/util'; + +beforeEach(() => { + beforeMapTest(); + global.fetch = null; +}); + +describe('#setSky', () => { + test('calls style setSky when set', () => { + const map = createMap(); + const spy = jest.fn(); + map.style.setSky = spy; + map.setSky({'atmosphere-blend': 0.5}); + + expect(spy).toHaveBeenCalled(); + }); +}); + +describe('#getSky', () => { + test('returns undefined when not set', () => { + const map = createMap(); + expect(map.getSky()).toBeUndefined(); + }); + + test('calls style getSky when invoked', () => { + const map = createMap(); + const spy = jest.fn(); + map.style.getSky = spy; + map.getSky(); + + expect(spy).toHaveBeenCalled(); + }); + + test('return previous style when set', async () => { + const map = createMap(); + await map.once('style.load'); + map.setSky({'atmosphere-blend': 0.5}); + + expect(map.getSky()).toEqual({'atmosphere-blend': 0.5}); + }); + +}); diff --git a/test/build/min.test.ts b/test/build/min.test.ts index 61b6393309..8c1e536dd8 100644 --- a/test/build/min.test.ts +++ b/test/build/min.test.ts @@ -36,7 +36,7 @@ describe('test min build', () => { const decreaseQuota = 4096; // feel free to update this value after you've checked that it has changed on purpose :-) - const expectedBytes = 834444; + const expectedBytes = 843270; expect(actualBytes).toBeLessThan(expectedBytes + increaseQuota); expect(actualBytes).toBeGreaterThan(expectedBytes - decreaseQuota); diff --git a/test/examples/globe-atmosphere.html b/test/examples/globe-atmosphere.html new file mode 100644 index 0000000000..ee8ea25291 --- /dev/null +++ b/test/examples/globe-atmosphere.html @@ -0,0 +1,63 @@ + + + + Display a globe with an atmosphere + + + + + + + + + +
+ + + diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/expected.png b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/expected.png new file mode 100644 index 0000000000..c3f6837235 Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/style.json b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/style.json new file mode 100644 index 0000000000..a442154b50 --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/base/style.json @@ -0,0 +1,49 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with atmosphere-blend to 0.5." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": 0.5 + }, + "light": { + "anchor": "map", + "position": [1.5, 90, 90] + }, + "center": [ + 160.0, + 0.0 + ], + "zoom": 1, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/expected.png b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/expected.png new file mode 100644 index 0000000000..c39f55771b Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/style.json b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/style.json new file mode 100644 index 0000000000..4ef3f67aac --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0.5/style.json @@ -0,0 +1,56 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with atmosphere-blend interpolated to a mid zoom level." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": [ + "interpolate", + ["linear"], + ["zoom"], + 0, 1, + 10, 1, + 12, 0 + ] + }, + "light": { + "anchor": "map", + "position": [1.5, 0, 180] + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 11, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/expected.png b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/expected.png new file mode 100644 index 0000000000..2cfc4e76f1 Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/style.json b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/style.json new file mode 100644 index 0000000000..6a0151c0f3 --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-0/style.json @@ -0,0 +1,56 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with atmosphere-blend interpolated to a high zoom level." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": [ + "interpolate", + ["linear"], + ["zoom"], + 0, 1, + 10, 1, + 12, 0 + ] + }, + "light": { + "anchor": "map", + "position": [1.5, 0, 180] + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 12, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/expected.png b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/expected.png new file mode 100644 index 0000000000..fb63ed3de0 Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/style.json b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/style.json new file mode 100644 index 0000000000..ec84ba6d1d --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/atmosphere-blend/interpolate-to-1/style.json @@ -0,0 +1,56 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with atmosphere-blend interpolated to a mid zoom level." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": [ + "interpolate", + ["linear"], + ["zoom"], + 0, 1, + 10, 1, + 12, 0 + ] + }, + "light": { + "anchor": "map", + "position": [1.5, 0, 180] + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 10, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/base/expected.png b/test/integration/render/tests/projection/globe/atmosphere/base/expected.png new file mode 100644 index 0000000000..2eeec4b20d Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/base/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/base/style.json b/test/integration/render/tests/projection/globe/atmosphere/base/style.json new file mode 100644 index 0000000000..e99cddb72e --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/base/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests that atmosphere is well display with blend to 1.0." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": 1.0 + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 1, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/light-position-map/expected.png b/test/integration/render/tests/projection/globe/atmosphere/light-position-map/expected.png new file mode 100644 index 0000000000..216be36e9e Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/light-position-map/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/light-position-map/style.json b/test/integration/render/tests/projection/globe/atmosphere/light-position-map/style.json new file mode 100644 index 0000000000..ed13867460 --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/light-position-map/style.json @@ -0,0 +1,49 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with position on right map." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": 1.0 + }, + "light": { + "anchor": "map", + "position": [1.5, 90, 90] + }, + "center": [ + 160.0, + 0.0 + ], + "zoom": 1, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/expected.png b/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/expected.png new file mode 100644 index 0000000000..b4ba6fb797 Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/style.json b/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/style.json new file mode 100644 index 0000000000..55b42c5c37 --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/ligth-position-viewport/style.json @@ -0,0 +1,49 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with position on left viewport." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": 1.0 + }, + "light": { + "anchor": "viewport", + "position": [1.5, 90, 90] + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 1, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/atmosphere/zoom/expected.png b/test/integration/render/tests/projection/globe/atmosphere/zoom/expected.png new file mode 100644 index 0000000000..0421635cd5 Binary files /dev/null and b/test/integration/render/tests/projection/globe/atmosphere/zoom/expected.png differ diff --git a/test/integration/render/tests/projection/globe/atmosphere/zoom/style.json b/test/integration/render/tests/projection/globe/atmosphere/zoom/style.json new file mode 100644 index 0000000000..af758e2bd3 --- /dev/null +++ b/test/integration/render/tests/projection/globe/atmosphere/zoom/style.json @@ -0,0 +1,49 @@ +{ + "version": 8, + "metadata": { + "test": { + "description": "Tests atmosphere with high zoom level." + } + }, + "projection": { "type": "globe" }, + "sky": { + "atmosphere-blend": 1.0 + }, + "light": { + "anchor": "map", + "position": [1.5, 90, 90] + }, + "center": [ + 160.0, + 0.0 + ], + "zoom": 8, + "sources": { + "source": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.satellite.png" + ], + "minzoom": 1, + "maxzoom": 1, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "source", + "paint": { + "raster-fade-duration": 0 + } + } + ] + } \ No newline at end of file diff --git a/test/integration/render/tests/projection/globe/background-pattern/style.json b/test/integration/render/tests/projection/globe/background-pattern/style.json index 79309f9a69..6ac6c58a37 100644 --- a/test/integration/render/tests/projection/globe/background-pattern/style.json +++ b/test/integration/render/tests/projection/globe/background-pattern/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "sources": {}, "sprite": "local://sprites/emerald", diff --git a/test/integration/render/tests/projection/globe/background/style.json b/test/integration/render/tests/projection/globe/background/style.json index 429b1c4975..013c42dc10 100644 --- a/test/integration/render/tests/projection/globe/background/style.json +++ b/test/integration/render/tests/projection/globe/background/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-map/style.json b/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-map/style.json index 93489fa751..55e7967c7f 100644 --- a/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-map/style.json +++ b/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-map/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-viewport/style.json b/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-viewport/style.json index e53880d0cc..4bf1c7de8e 100644 --- a/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-viewport/style.json +++ b/test/integration/render/tests/projection/globe/circle-pitch-alignment/map-scale-viewport/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-map/style.json b/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-map/style.json index 6af6254a66..af50adcc01 100644 --- a/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-map/style.json +++ b/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-map/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-viewport/style.json b/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-viewport/style.json index 670bf34e02..e2d8915458 100644 --- a/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-viewport/style.json +++ b/test/integration/render/tests/projection/globe/circle-pitch-alignment/viewport-scale-viewport/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/circle-planet/style.json b/test/integration/render/tests/projection/globe/circle-planet/style.json index 7ab8b79309..27d893778c 100644 --- a/test/integration/render/tests/projection/globe/circle-planet/style.json +++ b/test/integration/render/tests/projection/globe/circle-planet/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/collision-icon-text-translate-map/style.json b/test/integration/render/tests/projection/globe/collision-icon-text-translate-map/style.json index 0826895293..316db9cfc7 100644 --- a/test/integration/render/tests/projection/globe/collision-icon-text-translate-map/style.json +++ b/test/integration/render/tests/projection/globe/collision-icon-text-translate-map/style.json @@ -7,6 +7,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "bearing": 45, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/collision-text-line-pole-to-pole/style.json b/test/integration/render/tests/projection/globe/collision-text-line-pole-to-pole/style.json index d76cfc09ae..73b29cea23 100644 --- a/test/integration/render/tests/projection/globe/collision-text-line-pole-to-pole/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-line-pole-to-pole/style.json @@ -7,6 +7,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/collision-text-pitched-rotated/style.json b/test/integration/render/tests/projection/globe/collision-text-pitched-rotated/style.json index f6d135eaef..e92eeb85f2 100644 --- a/test/integration/render/tests/projection/globe/collision-text-pitched-rotated/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-pitched-rotated/style.json @@ -7,6 +7,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "pitch": 30, "bearing": 45, diff --git a/test/integration/render/tests/projection/globe/collision-text-point-pole-to-pole/style.json b/test/integration/render/tests/projection/globe/collision-text-point-pole-to-pole/style.json index 196e3ca99b..e395a94c97 100644 --- a/test/integration/render/tests/projection/globe/collision-text-point-pole-to-pole/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-point-pole-to-pole/style.json @@ -7,6 +7,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/base/style.json b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/base/style.json index 73043f6747..fe3b2a2291 100644 --- a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/base/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/base/style.json @@ -7,6 +7,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched-and-rotated/style.json b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched-and-rotated/style.json index 0566b62f93..fc9540c423 100644 --- a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched-and-rotated/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched-and-rotated/style.json @@ -7,6 +7,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 15, 0 diff --git a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched/style.json b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched/style.json index 55398afdd0..de40b8a4a5 100644 --- a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/pitched/style.json @@ -7,6 +7,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "pitch": 60, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/rotated/style.json b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/rotated/style.json index 4d32b1d68a..8c8244c3f3 100644 --- a/test/integration/render/tests/projection/globe/collision-text-variable-anchor/rotated/style.json +++ b/test/integration/render/tests/projection/globe/collision-text-variable-anchor/rotated/style.json @@ -7,6 +7,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "bearing": 60, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/fill-extrusion-translate/style.json b/test/integration/render/tests/projection/globe/fill-extrusion-translate/style.json index 2e6cceb492..2265be140e 100644 --- a/test/integration/render/tests/projection/globe/fill-extrusion-translate/style.json +++ b/test/integration/render/tests/projection/globe/fill-extrusion-translate/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 10.0, -15.0 diff --git a/test/integration/render/tests/projection/globe/fill-extrusion/style.json b/test/integration/render/tests/projection/globe/fill-extrusion/style.json index 669fd6cb20..43d390b4e4 100644 --- a/test/integration/render/tests/projection/globe/fill-extrusion/style.json +++ b/test/integration/render/tests/projection/globe/fill-extrusion/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, -30.0 diff --git a/test/integration/render/tests/projection/globe/fill-planet-pitched/style.json b/test/integration/render/tests/projection/globe/fill-planet-pitched/style.json index dab21a0c3a..1cce46f7b9 100644 --- a/test/integration/render/tests/projection/globe/fill-planet-pitched/style.json +++ b/test/integration/render/tests/projection/globe/fill-planet-pitched/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/fill-planet-pole/style.json b/test/integration/render/tests/projection/globe/fill-planet-pole/style.json index 24e50939aa..120e14cec4 100644 --- a/test/integration/render/tests/projection/globe/fill-planet-pole/style.json +++ b/test/integration/render/tests/projection/globe/fill-planet-pole/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 80.0 diff --git a/test/integration/render/tests/projection/globe/fill-planet-solid/style.json b/test/integration/render/tests/projection/globe/fill-planet-solid/style.json index a771954aee..c93b37269b 100644 --- a/test/integration/render/tests/projection/globe/fill-planet-solid/style.json +++ b/test/integration/render/tests/projection/globe/fill-planet-solid/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/fill-planet-tiles/style.json b/test/integration/render/tests/projection/globe/fill-planet-tiles/style.json index 6dc0947da3..b1f61efbbc 100644 --- a/test/integration/render/tests/projection/globe/fill-planet-tiles/style.json +++ b/test/integration/render/tests/projection/globe/fill-planet-tiles/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/fill-seams/checkerboard/style.json b/test/integration/render/tests/projection/globe/fill-seams/checkerboard/style.json index 0aa52905cb..38f2c9b017 100644 --- a/test/integration/render/tests/projection/globe/fill-seams/checkerboard/style.json +++ b/test/integration/render/tests/projection/globe/fill-seams/checkerboard/style.json @@ -7,6 +7,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ -151.855, 61.590 diff --git a/test/integration/render/tests/projection/globe/fill-seams/ocean/style.json b/test/integration/render/tests/projection/globe/fill-seams/ocean/style.json index 05d3a015df..19d17e4237 100644 --- a/test/integration/render/tests/projection/globe/fill-seams/ocean/style.json +++ b/test/integration/render/tests/projection/globe/fill-seams/ocean/style.json @@ -7,6 +7,9 @@ "height": 1024 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ -150.52, 61.00 diff --git a/test/integration/render/tests/projection/globe/fill-translate/style.json b/test/integration/render/tests/projection/globe/fill-translate/style.json index 93a0abc0e2..0d8dff9e09 100644 --- a/test/integration/render/tests/projection/globe/fill-translate/style.json +++ b/test/integration/render/tests/projection/globe/fill-translate/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 10.0, -15.0 diff --git a/test/integration/render/tests/projection/globe/heatmap/style.json b/test/integration/render/tests/projection/globe/heatmap/style.json index b3fea53fd5..3007ca891e 100644 --- a/test/integration/render/tests/projection/globe/heatmap/style.json +++ b/test/integration/render/tests/projection/globe/heatmap/style.json @@ -6,6 +6,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0, 0 diff --git a/test/integration/render/tests/projection/globe/hillshade/style.json b/test/integration/render/tests/projection/globe/hillshade/style.json index 40dc4c434d..b9dcde29d0 100644 --- a/test/integration/render/tests/projection/globe/hillshade/style.json +++ b/test/integration/render/tests/projection/globe/hillshade/style.json @@ -7,6 +7,9 @@ "allowed": 0.05 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ -118.12, 36.60 diff --git a/test/integration/render/tests/projection/globe/icon-text-translate-map/style.json b/test/integration/render/tests/projection/globe/icon-text-translate-map/style.json index a5be236ccb..25bcd09b53 100644 --- a/test/integration/render/tests/projection/globe/icon-text-translate-map/style.json +++ b/test/integration/render/tests/projection/globe/icon-text-translate-map/style.json @@ -6,6 +6,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "bearing": 45, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/icon-text-translate-viewport/style.json b/test/integration/render/tests/projection/globe/icon-text-translate-viewport/style.json index 946f6039ec..88ce96ba56 100644 --- a/test/integration/render/tests/projection/globe/icon-text-translate-viewport/style.json +++ b/test/integration/render/tests/projection/globe/icon-text-translate-viewport/style.json @@ -6,6 +6,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "bearing": 45, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/image/style.json b/test/integration/render/tests/projection/globe/image/style.json index 6ea771b525..12cf9b42ed 100644 --- a/test/integration/render/tests/projection/globe/image/style.json +++ b/test/integration/render/tests/projection/globe/image/style.json @@ -7,6 +7,9 @@ "height": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 0.5, "center": [ 0, diff --git a/test/integration/render/tests/projection/globe/line-gradient/style.json b/test/integration/render/tests/projection/globe/line-gradient/style.json index ee7fae18d7..37b895abb1 100644 --- a/test/integration/render/tests/projection/globe/line-gradient/style.json +++ b/test/integration/render/tests/projection/globe/line-gradient/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/line-spiral/style.json b/test/integration/render/tests/projection/globe/line-spiral/style.json index 3770ee2d04..94e4d2f7d8 100644 --- a/test/integration/render/tests/projection/globe/line-spiral/style.json +++ b/test/integration/render/tests/projection/globe/line-spiral/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 0.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/line-translate/style.json b/test/integration/render/tests/projection/globe/line-translate/style.json index 6f7e526249..50bce05b76 100644 --- a/test/integration/render/tests/projection/globe/line-translate/style.json +++ b/test/integration/render/tests/projection/globe/line-translate/style.json @@ -4,6 +4,9 @@ "test": { } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 10.0, -15.0 diff --git a/test/integration/render/tests/projection/globe/raster-planet/style.json b/test/integration/render/tests/projection/globe/raster-planet/style.json index dab51c250b..cf55208032 100644 --- a/test/integration/render/tests/projection/globe/raster-planet/style.json +++ b/test/integration/render/tests/projection/globe/raster-planet/style.json @@ -5,6 +5,9 @@ "description": "Tests that globe projection works with the raster layer type." } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 15.0, 0.0 diff --git a/test/integration/render/tests/projection/globe/raster-pole/style.json b/test/integration/render/tests/projection/globe/raster-pole/style.json index f1e253d629..d252f12a21 100644 --- a/test/integration/render/tests/projection/globe/raster-pole/style.json +++ b/test/integration/render/tests/projection/globe/raster-pole/style.json @@ -5,6 +5,9 @@ "description": "Tests that globe projection of raster layer fills the poles properly." } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 15.0, 80.0 diff --git a/test/integration/render/tests/projection/globe/text-line-pole-to-pole/style.json b/test/integration/render/tests/projection/globe/text-line-pole-to-pole/style.json index d9c6de0b47..4546aded70 100644 --- a/test/integration/render/tests/projection/globe/text-line-pole-to-pole/style.json +++ b/test/integration/render/tests/projection/globe/text-line-pole-to-pole/style.json @@ -6,6 +6,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/text-pitched-rotated/style.json b/test/integration/render/tests/projection/globe/text-pitched-rotated/style.json index 7f2e27f5dd..71988f2252 100644 --- a/test/integration/render/tests/projection/globe/text-pitched-rotated/style.json +++ b/test/integration/render/tests/projection/globe/text-pitched-rotated/style.json @@ -6,6 +6,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "pitch": 30, "bearing": 45, diff --git a/test/integration/render/tests/projection/globe/text-point-pole-to-pole/style.json b/test/integration/render/tests/projection/globe/text-point-pole-to-pole/style.json index 36712bd429..90d2a2ed10 100644 --- a/test/integration/render/tests/projection/globe/text-point-pole-to-pole/style.json +++ b/test/integration/render/tests/projection/globe/text-point-pole-to-pole/style.json @@ -6,6 +6,9 @@ "width": 256 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 1, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/text-variable-anchor/base/style.json b/test/integration/render/tests/projection/globe/text-variable-anchor/base/style.json index 20323daf0a..f3a2b1013a 100644 --- a/test/integration/render/tests/projection/globe/text-variable-anchor/base/style.json +++ b/test/integration/render/tests/projection/globe/text-variable-anchor/base/style.json @@ -6,6 +6,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", "projection": { "type": "globe" }, diff --git a/test/integration/render/tests/projection/globe/text-variable-anchor/pitched-and-rotated/style.json b/test/integration/render/tests/projection/globe/text-variable-anchor/pitched-and-rotated/style.json index 8168ae2c49..7bec60bbc0 100644 --- a/test/integration/render/tests/projection/globe/text-variable-anchor/pitched-and-rotated/style.json +++ b/test/integration/render/tests/projection/globe/text-variable-anchor/pitched-and-rotated/style.json @@ -6,6 +6,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 15, 0 diff --git a/test/integration/render/tests/projection/globe/text-variable-anchor/pitched/style.json b/test/integration/render/tests/projection/globe/text-variable-anchor/pitched/style.json index 0fbdcbd1b1..df5ceb9780 100644 --- a/test/integration/render/tests/projection/globe/text-variable-anchor/pitched/style.json +++ b/test/integration/render/tests/projection/globe/text-variable-anchor/pitched/style.json @@ -6,6 +6,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "pitch": 60, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/text-variable-anchor/rotated/style.json b/test/integration/render/tests/projection/globe/text-variable-anchor/rotated/style.json index abb240f02a..a4a64cf82f 100644 --- a/test/integration/render/tests/projection/globe/text-variable-anchor/rotated/style.json +++ b/test/integration/render/tests/projection/globe/text-variable-anchor/rotated/style.json @@ -6,6 +6,9 @@ "width": 512 } }, + "sky": { + "atmosphere-blend": 0.0 + }, "zoom": 2, "bearing": 60, "glyphs": "local://glyphs/{fontstack}/{range}.pbf", diff --git a/test/integration/render/tests/projection/globe/zoom-transition/style.json b/test/integration/render/tests/projection/globe/zoom-transition/style.json index b8b30f2370..1ebf3d5456 100644 --- a/test/integration/render/tests/projection/globe/zoom-transition/style.json +++ b/test/integration/render/tests/projection/globe/zoom-transition/style.json @@ -12,6 +12,9 @@ ] } }, + "sky": { + "atmosphere-blend": 0.0 + }, "center": [ 13.418056, 52.499167