Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an Atmosphere layer for Globe (#3888) #4020

Merged
merged 37 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c087428
Port of PoC atmosphere layer.
Pheonor Apr 15, 2024
82ec89b
Fix resize for draw_atmosphere
Pheonor Apr 17, 2024
879cf5e
Add some options.
Pheonor Apr 18, 2024
1b93979
Allow to change sun date and time
Pheonor Apr 18, 2024
b884187
Fix import warning
Pheonor Apr 18, 2024
f5148c3
Render atmosphere only when a Globe projection is selected
Pheonor Apr 18, 2024
8b9c78f
Add some comments
Pheonor Apr 18, 2024
c00844d
Add some comments
Pheonor Apr 20, 2024
5d17da9
Change key
Pheonor Apr 20, 2024
46214c9
Update changelog
Pheonor Apr 20, 2024
4024626
Merge remote-tracking branch 'upstream/globe' into atmosphere
Pheonor Apr 20, 2024
4260dc2
Fix merge with globe branch
Pheonor Apr 20, 2024
1c96fe0
Fix documentation and default background color.
Pheonor Apr 20, 2024
ffc1530
Use black clear color only when atmosphere is on
Pheonor Apr 20, 2024
e933a78
Use atmosphere uniform for globe position, raidus in camera frame and…
Pheonor Apr 21, 2024
c2a4ab3
Merge branch 'globe' into atmosphere
Pheonor May 31, 2024
23a7af3
Remove unused project method
Pheonor May 31, 2024
6e26afc
Update maplibre-gl-style-spec to 20.3.0 and use sky atmosphere parameter
Pheonor Jun 2, 2024
b561d2a
Fix globe tests and use light position as Sun position.
Pheonor Jun 4, 2024
aac987e
Merge remote-tracking branch 'origin/globe' into atmosphere
Pheonor Jun 4, 2024
e105a71
Avoid type name collisions.
Pheonor Jun 4, 2024
9fc5aa5
Add atmosphere test for globe projection.
Pheonor Jun 5, 2024
5a6e304
Update expectedBytes for build test.
Pheonor Jun 5, 2024
53c2ef9
Fix PR comments.
Pheonor Jun 6, 2024
bef589a
Update Style test.
Pheonor Jun 7, 2024
31c2487
Remove unused method on projection
Pheonor Jun 7, 2024
2f9d2af
Add Sky Test.
Pheonor Jun 7, 2024
a90a068
Fix style test and add sky unit test.
Pheonor Jun 8, 2024
b1c8dc9
Move getSunPos method
Pheonor Jun 16, 2024
ef100e2
Merge remote-tracking branch 'origin/globe' into atmosphere
Pheonor Jun 17, 2024
f5fb3f0
Fix mercator updateProjection
Pheonor Jun 17, 2024
6073de1
Merge remote-tracking branch 'origin/globe' into atmosphere. Remove i…
Pheonor Jun 18, 2024
5aac672
Remove isGlobe method and fix merge.
Pheonor Jun 18, 2024
23febc3
Fix globe atmosphere tests with new projection style.
Pheonor Jun 18, 2024
d18fbe8
Clean-up some projection and light. Fix setSky and add tests.
Pheonor Jun 19, 2024
ba4cf5a
Remove sky test during update.
Pheonor Jun 20, 2024
56f2605
Clean-up
Pheonor Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions build/generate-struct-arrays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/data/atmosphere_attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {createLayout} from '../util/struct_array';

export const atmosphereAttributes = createLayout([
{name: 'a_pos', type: 'Float32', components: 4}
]);
60 changes: 47 additions & 13 deletions src/geo/projection/globe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
32 changes: 29 additions & 3 deletions src/geo/projection/mercator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions src/geo/projection/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
80 changes: 80 additions & 0 deletions src/render/draw_atmosphere.ts
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions src/render/painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 35 additions & 0 deletions src/render/program/atmosphere_program.ts
Original file line number Diff line number Diff line change
@@ -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<atmosphereUniformsType> => ({
'u_sun_pos': sunPos,
'u_atmosphere_blend': atmosphereBlend,
'u_globe_position': globePosition,
'u_globe_radius': globeRadius,
'u_inv_proj_matrix': invProjMatrix,
});

export {atmosphereUniforms, atmosphereUniformValues};
2 changes: 2 additions & 0 deletions src/render/program/program_uniforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {};

Expand Down Expand Up @@ -44,4 +45,5 @@ export const programUniforms = {
terrainDepth: terrainDepthUniforms,
terrainCoords: terrainCoordsUniforms,
projectionErrorMeasurement: projectionErrorMeasurementUniforms,
atmosphere: atmosphereUniforms,
};
6 changes: 3 additions & 3 deletions src/render/program/projection_program.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Uniform1f, Uniform4f, UniformLocations, UniformMatrix4f} from '../uniform_binding';
import {Uniform1f, Uniform3f, Uniform4f, UniformLocations, UniformMatrix4f} from '../uniform_binding';

Check warning on line 1 in src/render/program/projection_program.ts

View workflow job for this annotation

GitHub Actions / Code Hygiene

'Uniform3f' is defined but never used
import {Context} from '../../gl/context';
import {mat4} from 'gl-matrix';
import {mat4, vec3} from 'gl-matrix';

Check warning on line 3 in src/render/program/projection_program.ts

View workflow job for this annotation

GitHub Actions / Code Hygiene

'vec3' is defined but never used

export type ProjectionPreludeUniformsType = {
'u_projection_matrix': UniformMatrix4f;
Expand All @@ -15,7 +15,7 @@
'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 = {
Expand Down
Loading
Loading