Skip to content

Commit

Permalink
Improve terrain rendering when using globe projection (#4825)
Browse files Browse the repository at this point in the history
* Move dangling comment

* Homogenize naming

* Add ProjectionDataParams and ignoreGlobeMatrix

* Ignore globe matrix on raster

* Add test

* Add platform-specific images

* Add changelog entry

* Just import type

* Add unit tests
  • Loading branch information
ibesora authored Oct 29, 2024
1 parent cacd433 commit 085d54d
Show file tree
Hide file tree
Showing 30 changed files with 152 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- ⚠️ Remove unminified prod build ([#4906](https://github.com/maplibre/maplibre-gl-js/pull/4906))
- Fix issue where raster tile source won't fetch updates following request error ([#4890](https://github.com/maplibre/maplibre-gl-js/pull/4890))
- Fix 3D models in custom layers not being properly occluded by the globe ([#4817](https://github.com/maplibre/maplibre-gl-js/issues/4817))
- Fix issue where raster tiles were not rendered correctly when using globe and terrain ([#4912](https://github.com/maplibre/maplibre-gl-js/pull/4912))
- _...Add new stuff here..._

## v5.0.0-pre.3
Expand Down
4 changes: 0 additions & 4 deletions src/geo/projection/globe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import {ProjectionErrorMeasurement} from './globe_projection_error_measurement';
import {createTileMeshWithBuffers, CreateTileMeshOptions} from '../../util/create_tile_mesh';

export const globeConstants = {
/**
* The size of border region for stencil masks, in internal tile coordinates.
* Used for globe rendering.
*/
globeTransitionTimeSeconds: 0.5,
maxGlobeZoom: 12.0,
errorTransitionTimeSeconds: 0.5
Expand Down
14 changes: 12 additions & 2 deletions src/geo/projection/globe_transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,26 @@ describe('GlobeTransform', () => {
describe('getProjectionData', () => {
const globeTransform = createGlobeTransform(globeProjectionMock);
test('mercator tile extents are set', () => {
const projectionData = globeTransform.getProjectionData(new OverscaledTileID(1, 0, 1, 1, 0));
const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(1, 0, 1, 1, 0)});
expectToBeCloseToArray(projectionData.tileMercatorCoords, [0.5, 0, 0.5 / EXTENT, 0.5 / EXTENT]);
});

test('Globe transition is not 0 when not ignoring the globe matrix', () => {
const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(1, 0, 1, 1, 0)});
expect(projectionData.projectionTransition).not.toBe(0);
});

test('Ignoring the globe matrix sets transition to 0', () => {
const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(1, 0, 1, 1, 0), ignoreGlobeMatrix: true});
expect(projectionData.projectionTransition).toBe(0);
});
});

describe('clipping plane', () => {
const globeTransform = createGlobeTransform(globeProjectionMock);

describe('general plane properties', () => {
const projectionData = globeTransform.getProjectionData(new OverscaledTileID(0, 0, 0, 0, 0));
const projectionData = globeTransform.getProjectionData({overscaledTileID: new OverscaledTileID(0, 0, 0, 0, 0)});

test('plane vector length', () => {
const len = Math.sqrt(
Expand Down
11 changes: 6 additions & 5 deletions src/geo/projection/globe_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {PaddingOptions} from '../edge_insets';
import {tileCoordinatesToMercatorCoordinates} from './mercator_utils';
import {angularCoordinatesToSurfaceVector, getGlobeRadiusPixels, getZoomAdjustment, mercatorCoordinatesToAngularCoordinatesRadians, projectTileCoordinatesToSphere, sphereSurfacePointToCoordinates} from './globe_utils';
import {EXTENT} from '../../data/extent';
import type {ProjectionData} from './projection_data';
import type {ProjectionData, ProjectionDataParams} from './projection_data';
import {globeCoveringTiles} from './globe_covering_tiles';
import {Frustum} from '../../util/primitives';

Expand Down Expand Up @@ -448,16 +448,17 @@ export class GlobeTransform implements ITransform {
return (this._lastUpdateTimeSeconds - this._lastGlobeChangeTimeSeconds) < globeConstants.globeTransitionTimeSeconds;
}

getProjectionData(overscaledTileID: OverscaledTileID, aligned?: boolean, ignoreTerrainMatrix?: boolean): ProjectionData {
const data = this._mercatorTransform.getProjectionData(overscaledTileID, aligned, ignoreTerrainMatrix);
getProjectionData(params: ProjectionDataParams): ProjectionData {
const {overscaledTileID, aligned, ignoreTerrainMatrix, ignoreGlobeMatrix} = params;
const data = this._mercatorTransform.getProjectionData({overscaledTileID, aligned, ignoreTerrainMatrix});

// Set 'projectionMatrix' to actual globe transform
if (this.isGlobeRendering) {
data.mainMatrix = this._globeViewProjMatrix;
}

data.clippingPlane = this._cachedClippingPlane as [number, number, number, number];
data.projectionTransition = this._globeness;
data.projectionTransition = ignoreGlobeMatrix ? 0 : this._globeness;

return data;
}
Expand Down Expand Up @@ -1181,7 +1182,7 @@ export class GlobeTransform implements ITransform {
}

getProjectionDataForCustomLayer(): ProjectionData {
const projectionData = this.getProjectionData(new OverscaledTileID(0, 0, 0, 0, 0));
const projectionData = this.getProjectionData({overscaledTileID: new OverscaledTileID(0, 0, 0, 0, 0)});
projectionData.tileMercatorCoords = [0, 0, 1, 1];

// Even though we requested projection data for the mercator base tile which covers the entire mercator range,
Expand Down
7 changes: 4 additions & 3 deletions src/geo/projection/mercator_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {CoveringTilesOptions, CoveringZoomOptions, IReadonlyTransform, ITransfor
import {PaddingOptions} from '../edge_insets';
import {mercatorCoordinateToLocation, getBasicProjectionData, getMercatorHorizon, locationToMercatorCoordinate, projectToWorldCoordinates, unprojectFromWorldCoordinates, calculateTileMatrix, maxMercatorHorizonAngle, cameraMercatorCoordinateFromCenterAndRotation} from './mercator_utils';
import {EXTENT} from '../../data/extent';
import type {ProjectionData} from './projection_data';
import type {ProjectionData, ProjectionDataParams} from './projection_data';
import {scaleZoom, TransformHelper, zoomScale} from '../transform_helper';
import {mercatorCoveringTiles} from './mercator_covering_tiles';

Expand Down Expand Up @@ -750,7 +750,8 @@ export class MercatorTransform implements ITransform {
return false;
}

getProjectionData(overscaledTileID: OverscaledTileID, aligned?: boolean, ignoreTerrainMatrix?: boolean): ProjectionData {
getProjectionData(params: ProjectionDataParams): ProjectionData {
const {overscaledTileID, aligned, ignoreTerrainMatrix} = params;
const matrix = overscaledTileID ? this.calculatePosMatrix(overscaledTileID, aligned) : null;
return getBasicProjectionData(overscaledTileID, matrix, ignoreTerrainMatrix);
}
Expand Down Expand Up @@ -826,7 +827,7 @@ export class MercatorTransform implements ITransform {

getProjectionDataForCustomLayer(): ProjectionData {
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
const projectionData = this.getProjectionData(tileID, false, true);
const projectionData = this.getProjectionData({overscaledTileID: tileID, ignoreTerrainMatrix: true});

const tileMatrix = calculateTileMatrix(tileID, this.worldSize);
mat4.multiply(tileMatrix, this._viewProjMatrix, tileMatrix);
Expand Down
20 changes: 20 additions & 0 deletions src/geo/projection/projection_data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {mat4} from 'gl-matrix';
import type {OverscaledTileID} from '../../source/tile_id';

/**
* This type contains all data necessary to project a tile to screen in MapLibre's shader system.
Expand Down Expand Up @@ -44,3 +45,22 @@ export type ProjectionData = {
*/
fallbackMatrix: mat4;
}

export type ProjectionDataParams = {
/**
* The ID of the current tile
*/
overscaledTileID: OverscaledTileID | null;
/**
* Set to true if a pixel-aligned matrix should be used, if possible (mostly used for raster tiles under mercator projection)
*/
aligned?: boolean;
/**
* Set to true if the terrain matrix should be ignored
*/
ignoreTerrainMatrix?: boolean;
/**
* Set to true if the globe matrix should be ignored (i.e. when rendering to texture for terrain)
*/
ignoreGlobeMatrix?: boolean;
}
7 changes: 3 additions & 4 deletions src/geo/transform_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {PaddingOptions} from './edge_insets';
import {Terrain} from '../render/terrain';
import {PointProjection} from '../symbol/projection';
import {MapProjectionEvent} from '../ui/events';
import type {ProjectionData} from './projection/projection_data';
import type {ProjectionData, ProjectionDataParams} from './projection/projection_data';

export type CoveringZoomOptions = {
/**
Expand Down Expand Up @@ -437,10 +437,9 @@ export interface IReadonlyTransform extends ITransformGetters {
/**
* @internal
* Generates a `ProjectionData` instance to be used while rendering the supplied tile.
* @param overscaledTileID - The ID of the current tile.
* @param aligned - Set to true if a pixel-aligned matrix should be used, if possible (mostly used for raster tiles under mercator projection).
* @param params - Parameters for the projection data generation.
*/
getProjectionData(overscaledTileID: OverscaledTileID, aligned?: boolean, ignoreTerrainMatrix?: boolean): ProjectionData;
getProjectionData(params: ProjectionDataParams): ProjectionData;

/**
* @internal
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function drawBackground(painter: Painter, sourceCache: SourceCache, layer
const crossfade = layer.getCrossfadeParameters();

for (const tileID of tileIDs) {
const projectionData = transform.getProjectionData(tileID);
const projectionData = transform.getProjectionData({overscaledTileID: tileID});

const uniformValues = image ?
backgroundPatternUniformValues(opacity, painter, image, {tileID, tileSize}, crossfade) :
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function drawCircles(painter: Painter, sourceCache: SourceCache, layer: C
const terrainData = painter.style.map.terrain && painter.style.map.terrain.getTerrainData(coord);
const uniformValues = circleUniformValues(painter, tile, layer, translateForUniforms, radiusCorrectionFactor);

const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});

const state: TileRenderState = {
programConfiguration,
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_collision_debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function drawCollisionDebug(painter: Painter, sourceCache: SourceCache, l
CullFaceMode.disabled,
collisionUniformValues(painter.transform),
painter.style.map.terrain && painter.style.map.terrain.getTerrainData(coord),
transform.getProjectionData(coord),
transform.getProjectionData({overscaledTileID: coord}),
layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer,
buffers.segments, null, painter.transform.zoom, null, null,
buffers.collisionVertexBuffer);
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function drawDebugTile(painter: Painter, sourceCache: SourceCache, coord: Oversc
const tileLabel = `${tileIdText} ${tileSizeKb}kB`;
drawTextToOverlay(painter, tileLabel);

const projectionData = painter.transform.getProjectionData(coord);
const projectionData = painter.transform.getProjectionData({overscaledTileID: coord});

program.draw(context, gl.TRIANGLES, depthMode, stencilMode, ColorMode.alphaBlended, CullFaceMode.disabled,
debugUniformValues(Color.transparent, scaleRatio), null, projectionData, id,
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function drawFillTiles(

updatePatternPositionsInProgram(programConfiguration, fillPropertyName, constantPattern, tile, layer);

const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});

const translateForUniforms = translatePosition(transform, tile, propertyFillTranslate, propertyFillTranslateAnchor);

Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_fill_extrusion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function drawExtrusionTiles(
programConfiguration.updatePaintBuffers(crossfade);
}

const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});
updatePatternPositionsInProgram(programConfiguration, fillPropertyName, constantPattern, tile, layer);

const translate = translatePosition(
Expand Down
6 changes: 3 additions & 3 deletions src/render/draw_heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function prepareHeatmapFlat(painter: Painter, sourceCache: SourceCache, layer: H
const programConfiguration = bucket.programConfigurations.get(layer.id);
const program = painter.useProgram('heatmap', programConfiguration);

const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});

const radiusCorrectionFactor = transform.getCircleRadiusCorrection();

Expand Down Expand Up @@ -145,7 +145,7 @@ function prepareHeatmapTerrain(painter: Painter, tile: Tile, layer: HeatmapStyle
const programConfiguration = bucket.programConfigurations.get(layer.id);
const program = painter.useProgram('heatmap', programConfiguration, true);

const projectionData = painter.transform.getProjectionData(tile.tileID);
const projectionData = painter.transform.getProjectionData({overscaledTileID: tile.tileID});

const terrainData = painter.style.map.terrain.getTerrainData(coord);
program.draw(context, gl.TRIANGLES, DepthMode.disabled, stencilMode, colorMode, CullFaceMode.disabled,
Expand Down Expand Up @@ -177,7 +177,7 @@ function renderHeatmapTerrain(painter: Painter, layer: HeatmapStyleLayer, coord:
context.activeTexture.set(gl.TEXTURE1);
colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);

const projectionData = transform.getProjectionData(coord, false, true);
const projectionData = transform.getProjectionData({overscaledTileID: coord, ignoreTerrainMatrix: true});

painter.useProgram('heatmapTexture').draw(context, gl.TRIANGLES,
DepthMode.disabled, StencilMode.disabled, painter.colorModeForRenderPass(), CullFaceMode.disabled,
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_hillshade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function renderHillshade(
context.activeTexture.set(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get());

const projectionData = transform.getProjectionData(coord, align);
const projectionData = transform.getProjectionData({overscaledTileID: coord, aligned: align});

program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW,
hillshadeUniformValues(painter, tile, layer), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments);
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function drawLine(painter: Painter, sourceCache: SourceCache, layer: Line
if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom);
}

const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});
const pixelRatio = transform.getPixelScale();

const uniformValues = image ? linePatternUniformValues(painter, tile, layer, pixelRatio, crossfade) :
Expand Down
15 changes: 8 additions & 7 deletions src/render/draw_raster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const cornerCoords = [
new Point(0, EXTENT),
];

export function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, tileIDs: Array<OverscaledTileID>) {
export function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, tileIDs: Array<OverscaledTileID>, isRenderingToTexture: boolean = false) {
if (painter.renderPass !== 'translucent') return;
if (layer.paint.get('raster-opacity') === 0) return;
if (!tileIDs.length) return;
Expand All @@ -43,16 +43,16 @@ export function drawRaster(painter: Painter, sourceCache: SourceCache, layer: Ra
// Stencil mask and two-pass is not used for ImageSource sources regardless of projection.
if (source instanceof ImageSource) {
// Image source - no stencil is used
drawTiles(painter, sourceCache, layer, tileIDs, null, false, false, source.tileCoords, source.flippedWindingOrder);
drawTiles(painter, sourceCache, layer, tileIDs, null, false, false, source.tileCoords, source.flippedWindingOrder, isRenderingToTexture);
} else if (useSubdivision) {
// Two-pass rendering
const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs);
drawTiles(painter, sourceCache, layer, coords, stencilBorderless, false, true, cornerCoords); // draw without borders
drawTiles(painter, sourceCache, layer, coords, stencilBorders, true, true, cornerCoords); // draw with borders
drawTiles(painter, sourceCache, layer, coords, stencilBorderless, false, true, cornerCoords, false, isRenderingToTexture); // draw without borders
drawTiles(painter, sourceCache, layer, coords, stencilBorders, true, true, cornerCoords, false, isRenderingToTexture); // draw with borders
} else {
// Simple rendering
const [stencil, coords] = painter.stencilConfigForOverlap(tileIDs);
drawTiles(painter, sourceCache, layer, coords, stencil, false, true, cornerCoords);
drawTiles(painter, sourceCache, layer, coords, stencil, false, true, cornerCoords, false, isRenderingToTexture);
}
}

Expand All @@ -65,7 +65,8 @@ function drawTiles(
useBorder: boolean,
allowPoles: boolean,
corners: Array<Point>,
flipCullfaceMode: boolean = false) {
flipCullfaceMode: boolean = false,
isRenderingToTexture: boolean = false) {
const minTileZ = coords[coords.length - 1].overscaledZ;

const context = painter.context;
Expand Down Expand Up @@ -120,7 +121,7 @@ function drawTiles(
}

const terrainData = painter.style.map.terrain && painter.style.map.terrain.getTerrainData(coord);
const projectionData = transform.getProjectionData(coord, align);
const projectionData = transform.getProjectionData({overscaledTileID: coord, aligned: align, ignoreGlobeMatrix: isRenderingToTexture});
const uniformValues = rasterUniformValues(parentTL || [0, 0], parentScaleBy || 1, fade, layer, corners);

const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, allowPoles, 'raster');
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_sky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function drawAtmosphere(painter: Painter, sky: Sky, light: Light) {

const sunPos = getSunPos(light, painter.transform);

const projectionData = transform.getProjectionData(null);
const projectionData = transform.getProjectionData({overscaledTileID: null});
const atmosphereBlend = sky.properties.get('atmosphere-blend') * projectionData.projectionTransition;

if (atmosphereBlend === 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ function drawLayerSymbols(
const glCoordMatrixForShader = getGlCoordMatrix(pitchWithMap, rotateWithMap, painter.transform, s);

const translation = translatePosition(transform, tile, translate, translateAnchor);
const projectionData = transform.getProjectionData(coord);
const projectionData = transform.getProjectionData({overscaledTileID: coord});

const hasVariableAnchors = hasVariablePlacement && bucket.hasTextData();
const updateTextFitIcon = layer.layout.get('icon-text-fit') !== 'none' &&
Expand Down
Loading

0 comments on commit 085d54d

Please sign in to comment.