Skip to content

Commit

Permalink
Globe: Tile borders fix (#4868)
Browse files Browse the repository at this point in the history
* Try reenabling stencil for fill layer + fix render tests

* Add geometry outside tile render test

* Match stencil granularity to fill granularity

* Make sure translation does not affect pole vertices

* Fix previous commit

* Reenable fill stencil & force subdivision at all zoom levels for globe

* Update build size

* Add more render test images

* Update build size

* Fix build size & add changelog entry
  • Loading branch information
kubapelc authored Nov 20, 2024
1 parent 9c636f0 commit 8e31df8
Show file tree
Hide file tree
Showing 24 changed files with 78 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Fixes cooperative gestures displaying the mobile help text when screen width is smaller than 480px on non-touch devices ([#5053](https://github.com/maplibre/maplibre-gl-js/pull/5053))
- Fixes incorrect cluster radius scaling in `GeoJSONSource.setClusterOptions()` ([#5055](https://github.com/maplibre/maplibre-gl-js/pull/5055))
- Improve innerHTML handling in code ([#5057](https://github.com/maplibre/maplibre-gl-js/pull/5057)))
- Fix geometry beyond tile borders being rendered ([#4868](https://github.com/maplibre/maplibre-gl-js/pull/4868))
- _...Add new stuff here..._

## 5.0.0-pre.6
Expand Down
10 changes: 6 additions & 4 deletions src/geo/projection/globe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ export const globeConstants = {
};

const granularitySettingsGlobe: SubdivisionGranularitySetting = new SubdivisionGranularitySetting({
fill: new SubdivisionGranularityExpression(128, 1),
line: new SubdivisionGranularityExpression(512, 1),
fill: new SubdivisionGranularityExpression(128, 2),
line: new SubdivisionGranularityExpression(512, 0),
// Always keep at least some subdivision on raster tiles, etc,
// otherwise they will be visibly warped at high zooms (before mercator transition).
// This si not needed on fill, because fill geometry tends to already be
// highly tessellated and granular at high zooms.
// Minimal granularity of 8 seems to be enough to avoid warped raster tiles, while also minimizing triangle count.
tile: new SubdivisionGranularityExpression(128, 32),
stencil: new SubdivisionGranularityExpression(128, 4),
// Stencil granularity must never be higher than fill granularity,
// otherwise we would get seams in the oceans at zoom levels where
// stencil has higher granularity than fill.
stencil: new SubdivisionGranularityExpression(128, 1),
circle: 3
});

Expand Down
34 changes: 5 additions & 29 deletions src/render/draw_fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import type {FillStyleLayer} from '../style/style_layer/fill_style_layer';
import type {FillBucket} from '../data/bucket/fill_bucket';
import type {OverscaledTileID} from '../source/tile_id';
import {updatePatternPositionsInProgram} from './update_pattern_positions_in_program';
import {StencilMode} from '../gl/stencil_mode';
import {translatePosition} from '../util/util';
import {StencilMode} from '../gl/stencil_mode';

export function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLayer, coords: Array<OverscaledTileID>, renderOptions: RenderOptions) {
const color = layer.paint.get('fill-color');
Expand Down Expand Up @@ -129,36 +129,12 @@ function drawFillTiles(
fillOutlineUniformValues(drawingBufferSize, translateForUniforms);
}

// Stencil is not really needed for anything unless we are drawing transparent things.
//
// For translucent layers, we must draw any pixel of a given layer at most once,
// otherwise we might get artifacts from the transparent geometry being drawn twice over itself,
// which can happen due to tiles having a slight overlapping border into neighboring tiles.
// Hence we use stencil tile masks for any translucent pass, including for fill.
//
// Globe rendering relies on these tile borders to hide tile seams, since under globe projection
// tiles are not squares, but slightly curved squares. At high zoom levels, the tile stencil mask
// is approximated by a square, but if the tile contains fine geometry, it might still get projected
// into a curved shape, causing a mismatch with the stencil mask, which is very visible
// if the tile border is small.
//
// The simples workaround for this is to just disable stencil masking for opaque fill layers,
// since the fine geometry will always line up perfectly with the geometry in its neighboring tiles,
// even if the border is small. Disabling stencil ensures the neighboring geometry isn't clipped.
//
// This doesn't seem to be an issue for transparent fill layers (or they don't get used enough to be noticeable),
// which is a good thing, since there is no easy solution for this problem for transparency, other than
// greatly increasing subdivision granularity for both fill layers and stencil masks, at least at tile edges.
let stencil: StencilMode;
if (painter.renderPass === 'translucent') {
if (isRenderingToTexture) {
const [stencilModes] = painter.stencilConfigForOverlap(coords);
stencil = stencilModes[coord.overscaledZ];
} else {
stencil = painter.stencilModeForClipping(coord);
}
if (painter.renderPass === 'translucent' && isRenderingToTexture) {
const [stencilModes] = painter.stencilConfigForOverlap(coords);
stencil = stencilModes[coord.overscaledZ];
} else {
stencil = StencilMode.disabled;
stencil = painter.stencilModeForClipping(coord);
}

program.draw(painter.context, drawMode, depthMode,
Expand Down
15 changes: 12 additions & 3 deletions src/shaders/_projection_globe.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ float projectLineThickness(float tileY) {
}

// get position inside the tile in range 0..8192 and project it onto the surface of a unit sphere
vec3 projectToSphere(vec2 posInTile) {
vec3 projectToSphere(vec2 posInTile, vec2 rawPos) {
// Compute position in range 0..1 of the base tile of web mercator
vec2 mercator_pos = u_projection_tile_mercator_coords.xy + u_projection_tile_mercator_coords.zw * posInTile;

Expand All @@ -61,17 +61,21 @@ vec3 projectToSphere(vec2 posInTile) {
);

// North pole
if (posInTile.y < -32767.5) {
if (rawPos.y < -32767.5) {
pos = vec3(0.0, 1.0, 0.0);
}
// South pole
if (posInTile.y > 32766.5) {
if (rawPos.y > 32766.5) {
pos = vec3(0.0, -1.0, 0.0);
}

return pos;
}

vec3 projectToSphere(vec2 posInTile) {
return projectToSphere(posInTile, vec2(0.0, 0.0));
}

float globeComputeClippingZ(vec3 spherePos) {
return (1.0 - (dot(spherePos, u_projection_clipping_plane.xyz) + u_projection_clipping_plane.w));
}
Expand Down Expand Up @@ -117,6 +121,11 @@ vec4 projectTile(vec2 posInTile) {
return interpolateProjection(posInTile, projectToSphere(posInTile), 0.0);
}

// A variant that supports special pole and planet center vertices.
vec4 projectTile(vec2 posInTile, vec2 rawPos) {
return interpolateProjection(posInTile, projectToSphere(posInTile, rawPos), 0.0);
}

// Uses elevation to compute final screenspace projection
// and **replaces Z** with a custom value that clips geometry
// on the backfacing side of the planet.
Expand Down
8 changes: 7 additions & 1 deletion src/shaders/_projection_mercator.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ float projectCircleRadius(float tileY) {

// Projects a point in tile-local coordinates (usually 0..EXTENT) to screen.
vec4 projectTile(vec2 p) {
vec4 result = u_projection_matrix * vec4(p, 0.0, 1.0);
return result;
}

// Projects a point in tile-local coordinates (usually 0..EXTENT) to screen, and handle special pole or planet center vertices.
vec4 projectTile(vec2 p, vec2 rawPos) {
// Kill pole vertices and triangles by placing the pole vertex so far in Z that
// the clipping hardware kills the entire triangle.
vec4 result = u_projection_matrix * vec4(p, 0.0, 1.0);
if (p.y < -32767.5 || p.y > 32766.5) {
if (rawPos.y < -32767.5 || rawPos.y > 32766.5) {
result.z = -10000000.0;
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/fill.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ void main() {
#pragma mapbox: initialize highp vec4 color
#pragma mapbox: initialize lowp float opacity

gl_Position = projectTile(a_pos + u_fill_translate);
gl_Position = projectTile(a_pos + u_fill_translate, a_pos);
}
2 changes: 1 addition & 1 deletion src/shaders/fill_extrusion.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void main() {
vec2 posInTile = a_pos + u_fill_translate;

#ifdef GLOBE
vec3 spherePos = projectToSphere(posInTile);
vec3 spherePos = projectToSphere(posInTile, a_pos);
gl_Position = interpolateProjectionFor3D(posInTile, spherePos, elevation);
#else
gl_Position = u_projection_matrix * vec4(posInTile, elevation, 1.0);
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/fill_extrusion_pattern.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void main() {
vec2 posInTile = a_pos + u_fill_translate;

#ifdef GLOBE
vec3 spherePos = projectToSphere(posInTile);
vec3 spherePos = projectToSphere(posInTile, a_pos);
vec3 elevatedPos = spherePos * (1.0 + elevation / GLOBE_RADIUS);
v_sphere_pos = elevatedPos;
gl_Position = interpolateProjectionFor3D(posInTile, spherePos, elevation);
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/fill_outline.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ void main() {
#pragma mapbox: initialize highp vec4 outline_color
#pragma mapbox: initialize lowp float opacity

gl_Position = projectTile(a_pos + u_fill_translate);
gl_Position = projectTile(a_pos + u_fill_translate, a_pos);

v_pos = (gl_Position.xy / gl_Position.w + 1.0) / 2.0 * u_world;
#ifdef GLOBE
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/fill_outline_pattern.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void main() {
float fromScale = u_scale.y;
float toScale = u_scale.z;

gl_Position = projectTile(a_pos + u_fill_translate);
gl_Position = projectTile(a_pos + u_fill_translate, a_pos);

vec2 display_size_a = (pattern_br_a - pattern_tl_a) / pixel_ratio_from;
vec2 display_size_b = (pattern_br_b - pattern_tl_b) / pixel_ratio_to;
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/fill_pattern.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void main() {
vec2 display_size_a = (pattern_br_a - pattern_tl_a) / pixel_ratio_from;
vec2 display_size_b = (pattern_br_b - pattern_tl_b) / pixel_ratio_to;

gl_Position = projectTile(a_pos + u_fill_translate);
gl_Position = projectTile(a_pos + u_fill_translate, a_pos);

v_pos_a = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, fromScale * display_size_a, tileZoomRatio, a_pos);
v_pos_b = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, toScale * display_size_b, tileZoomRatio, a_pos);
Expand Down
2 changes: 1 addition & 1 deletion src/shaders/hillshade.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ in vec2 a_pos;
out vec2 v_pos;

void main() {
gl_Position = projectTile(a_pos);
gl_Position = projectTile(a_pos, a_pos);
v_pos = a_pos / 8192.0;
// North pole
if (a_pos.y < -32767.5) {
Expand Down
4 changes: 2 additions & 2 deletions src/shaders/raster.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ void main() {
// Interpolate the actual desired coordinates to get the final position.
vec2 fractionalPos = a_pos / 8192.0;
vec2 position = mix(mix(u_coords_top.xy, u_coords_top.zw, fractionalPos.x), mix(u_coords_bottom.xy, u_coords_bottom.zw, fractionalPos.x), fractionalPos.y);
gl_Position = projectTile(position);
gl_Position = projectTile(position, position);

// We are using Int16 for texture position coordinates to give us enough precision for
// fractional coordinates. We use 8192 to scale the texture coordinates in the buffer
// as an arbitrarily high number to preserve adequate precision when rendering.
// This is also the same value as the EXTENT we are using for our tile buffer pos coordinates,
// so math for modifying either is consistent.
v_pos0 = ((fractionalPos - 0.5) / u_buffer_scale ) + 0.5;
v_pos0 = ((fractionalPos - 0.5) / u_buffer_scale) + 0.5;

// When globe rendering is enabled, pole vertices need special handling to get nice texture coordinates.
#ifdef GLOBE
Expand Down
2 changes: 1 addition & 1 deletion test/build/min.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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 = 897777;
const expectedBytes = 898319;

expect(actualBytes).toBeLessThan(expectedBytes + increaseQuota);
expect(actualBytes).toBeGreaterThan(expectedBytes - decreaseQuota);
Expand Down
Binary file added test/integration/assets/tiles/outside_ring.mvt
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions test/integration/render/tests/fill-outside-tile/style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"version": 8,
"metadata": {
"test": {
"description": "Tests that tiles are properly clipped by drawing a tile that has all its geometry outside its visible area. See issue #4803. The expected (correct) result is that nothing is drawn.",
"width": 64,
"height": 64
}
},
"zoom": 2.125,
"sources": {
"vector_tiles": {
"type": "vector",
"tiles": [
"local://tiles/outside_ring.mvt"
]
}
},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "white"
}
},
{
"id": "ocean",
"type": "fill",
"source": "vector_tiles",
"source-layer": "water",
"paint": {
"fill-color": "black"
}
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8e31df8

Please sign in to comment.