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

Globe: Tile borders fix #4868

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions src/geo/projection/globe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ export const globeConstants = {

const granularitySettingsGlobe: SubdivisionGranularitySetting = new SubdivisionGranularitySetting({
fill: new SubdivisionGranularityExpression(128, 1),
line: new SubdivisionGranularityExpression(512, 1),
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
23 changes: 1 addition & 22 deletions src/render/draw_fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ 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';

export function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLayer, coords: Array<OverscaledTileID>) {
Expand Down Expand Up @@ -124,27 +123,7 @@ 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.
const stencil = (painter.renderPass === 'translucent') ? painter.stencilModeForClipping(coord) : StencilMode.disabled;
const stencil = painter.stencilModeForClipping(coord);

program.draw(painter.context, drawMode, depthMode,
stencil, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, projectionData,
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 @@ -53,7 +53,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_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
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.