Skip to content

Commit

Permalink
[MAPS3D-425] introduce skirts for globe rendering
Browse files Browse the repository at this point in the history
Absent skirts lead to holes for hight exaggerated terrain in globe mode.
  • Loading branch information
alexey-romanov committed Jan 21, 2023
1 parent d7652ba commit 77757cf
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 50 deletions.
2 changes: 1 addition & 1 deletion debug/3d-playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
enableTerrain.onFinishChange((value) => {
map.setTerrain(value ? {"source": "mapbox-dem", "exaggeration": demo3d.terrainExaggeration} : null);
});
var terrainExaggeration = terrain.add(demo3d, 'terrainExaggeration', 0, 2);
var terrainExaggeration = terrain.add(demo3d, 'terrainExaggeration', 0, 100);
terrainExaggeration.onFinishChange((value) => {
if (demo3d.enableTerrain) {
map.setTerrain({"source": "mapbox-dem", "exaggeration": value});
Expand Down
190 changes: 153 additions & 37 deletions src/geo/projection/globe_util.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ function mercatorTileCornersInCameraSpace({x, y, z}: CanonicalTileID, numTiles:

// // Ensure that the tile viewed is the nearest when across the antimeridian
let wrap = 0;
const tileCenterXFromCamera = (w + e) / 2 - camX;
const tileCenterXFromCamera = (w + e) / 2 - camX;
if (tileCenterXFromCamera > .5) {
wrap = -1;
} else if (tileCenterXFromCamera < -.5) {
Expand All @@ -389,10 +389,10 @@ function mercatorTileCornersInCameraSpace({x, y, z}: CanonicalTileID, numTiles:
camY *= numTiles;

// Transform Mercator coordinates to points on the plane tangent to the globe at cameraCenter.
w = ((w + wrap) * numTiles - camX) * mercatorScale + camX;
e = ((e + wrap) * numTiles - camX) * mercatorScale + camX;
n = (n * numTiles - camY) * mercatorScale + camY;
s = (s * numTiles - camY) * mercatorScale + camY;
w = ((w + wrap) * numTiles - camX) * mercatorScale + camX;
e = ((e + wrap) * numTiles - camX) * mercatorScale + camX;
n = (n * numTiles - camY) * mercatorScale + camY;
s = (s * numTiles - camY) * mercatorScale + camY;

return [[w, s, 0],
[e, s, 0],
Expand Down Expand Up @@ -691,6 +691,20 @@ const POLE_RAD = degToRad(85.0);
const POLE_COS = Math.cos(POLE_RAD);
const POLE_SIN = Math.sin(POLE_RAD);

// Generate terrain grid with embedded skirts
const EMBED_SKIRTS = true;

type GridLodSegments = {
withoutSkirts: SegmentVector,
withSkirts: SegmentVector
};

type GridWithLods = {
vertices: PosArray,
indices: TriangleIndexArray,
segments: Array<GridLodSegments>
};

export class GlobeSharedBuffers {
_poleNorthVertexBuffer: VertexBuffer;
_poleSouthVertexBuffer: VertexBuffer;
Expand All @@ -699,7 +713,7 @@ export class GlobeSharedBuffers {

_gridBuffer: VertexBuffer;
_gridIndexBuffer: IndexBuffer;
_gridSegments: Array<SegmentVector>;
_gridSegments: Array<GridLodSegments>;

_wireframeIndexBuffer: IndexBuffer;
_wireframeSegments: Array<SegmentVector>;
Expand All @@ -716,45 +730,145 @@ export class GlobeSharedBuffers {
this._poleNorthVertexBuffer.destroy();
this._poleSouthVertexBuffer.destroy();
for (const segments of this._poleSegments) segments.destroy();
for (const segments of this._gridSegments) segments.destroy();
for (const segments of this._gridSegments) {
segments.withSkirts.destroy();
segments.withoutSkirts.destroy();
}

if (this._wireframeIndexBuffer) {
this._wireframeIndexBuffer.destroy();
for (const segments of this._wireframeSegments) segments.destroy();
}
}

_createGrid(context: Context) {
const gridVertices = new PosArray();
const gridIndices = new TriangleIndexArray();

const quadExt = GLOBE_VERTEX_GRID_SIZE;
const vertexExt = quadExt + 1;

for (let j = 0; j < vertexExt; j++)
for (let i = 0; i < vertexExt; i++)
gridVertices.emplaceBack(i, j);

this._gridSegments = [];
for (let k = 0, primitiveOffset = 0; k < GLOBE_LATITUDINAL_GRID_LOD_TABLE.length; k++) {
const latitudinalLod = GLOBE_LATITUDINAL_GRID_LOD_TABLE[k];
for (let j = 0; j < latitudinalLod; j++) {
for (let i = 0; i < quadExt; i++) {
const index = j * vertexExt + i;
gridIndices.emplaceBack(index + 1, index, index + vertexExt);
gridIndices.emplaceBack(index + vertexExt, index + vertexExt + 1, index + 1);
// Generate terrain grid vertices and indices for all LOD's
//
// Grid vertices memory layout:
//
// First line Skirt
// ┌───────────────┐
// │┌─────────────┐│
// Left ││┼┼┼┼┼┼┼┼┼┼┼┼┼││ Right
// Border ││┼┼┼┼┼┼┼┼┼┼┼┼┼││ Border
// Skirt │├─────────────┤│ Skirt
// ││ Main Grid ││
// │├─────────────┤│
// ││┼┼┼┼┼┼┼┼┼┼┼┼┼││
// ││┼┼┼┼┼┼┼┼┼┼┼┼┼││
// │└─────────────┘│
// ├───────────────┤
// ├───────────────┤
// └───────────────┘
// Bottom Skirt = Number of LOD's
//
_fillGridMeshWithLods(longitudinalCellsCount: number, latitudinalLods: number[]): GridWithLods {
const vertices = new PosArray();
const indices = new TriangleIndexArray();
const segments: Array<GridLodSegments> = [];

const xVertices = longitudinalCellsCount + 1 + 2 * (EMBED_SKIRTS ? 1 : 0);
const yVerticesHighLodNoStrip = latitudinalLods[0] + 1;
const yVerticesHighLodWithStrip = latitudinalLods[0] + 1 + (EMBED_SKIRTS ? 1 + latitudinalLods.length : 0);

// Index adjustment, used to make strip (x, y) vertex input attribute data
// to match same data on ordinary grid edges
const prepareVertex = (x: number, y: number, isSkirt: boolean) => {
if (!EMBED_SKIRTS) return [x, y];

let adjustedX = (() => {
if (x === xVertices - 1) {
return x - 2;
} else if (x === 0) {
return x;
} else {
return x - 1;
}
})();

// Skirt factor is introduces as an offset to the .x coordinate, similar to how it's done for mercator grids
const skirtOffset = 24575;
adjustedX += isSkirt ? skirtOffset : 0;

return [adjustedX, y];
};

// Add first horizontal strip if present
if (EMBED_SKIRTS) {
for (let x = 0; x < xVertices; ++x) {
vertices.emplaceBack(...prepareVertex(x, 0, true));
}
}

// Add main grid part with vertices strips embedded
for (let y = 0; y < yVerticesHighLodNoStrip; ++y) {
for (let x = 0; x < xVertices; ++x) {
const isSideBorder = (x === 0 || x === xVertices - 1);

vertices.emplaceBack(...prepareVertex(x, y, isSideBorder && EMBED_SKIRTS));
}
}

// Add bottom strips for each LOD
if (EMBED_SKIRTS) {
for (let lodIdx = 0; lodIdx < latitudinalLods.length; ++lodIdx) {
const lastYRowForLod = latitudinalLods[lodIdx];
for (let x = 0; x < xVertices; ++x) {
vertices.emplaceBack(...prepareVertex(x, lastYRowForLod, true));
}
}
}

// Fill triangles
for (let lodIdx = 0; lodIdx < latitudinalLods.length; ++lodIdx) {
const indexOffset = indices.length;

const yVerticesLod = latitudinalLods[lodIdx] + 1 + 2 * (EMBED_SKIRTS ? 1 : 0);

const skirtsOnlyIndices = new TriangleIndexArray();

for (let y = 0; y < yVerticesLod - 1; y++) {
const isLastLine = (y === yVerticesLod - 2);
const offsetToNextRow =
(isLastLine && EMBED_SKIRTS ?
(xVertices * (yVerticesHighLodWithStrip - latitudinalLods.length + lodIdx - y)) :
xVertices);

for (let x = 0; x < xVertices - 1; x++) {
const idx = y * xVertices + x;

const isSkirt = EMBED_SKIRTS && (y === 0 || isLastLine || x === 0 || x === xVertices - 2);

if (isSkirt) {
skirtsOnlyIndices.emplaceBack(idx + 1, idx, idx + offsetToNextRow);
skirtsOnlyIndices.emplaceBack(idx + offsetToNextRow, idx + offsetToNextRow + 1, idx + 1);
} else {
indices.emplaceBack(idx + 1, idx, idx + offsetToNextRow);
indices.emplaceBack(idx + offsetToNextRow, idx + offsetToNextRow + 1, idx + 1);
}
}
}

const numVertices = (latitudinalLod + 1) * vertexExt;
const numPrimitives = latitudinalLod * quadExt * 2;
// Segments grid only
const withoutSkirts = SegmentVector.simpleSegment(0, indexOffset, vertices.length, indices.length - indexOffset);

for (let i = 0; i < skirtsOnlyIndices.uint16.length; i += 3) {
indices.emplaceBack(skirtsOnlyIndices.uint16[i], skirtsOnlyIndices.uint16[i + 1], skirtsOnlyIndices.uint16[i + 2]);
}

this._gridSegments.push(SegmentVector.simpleSegment(0, primitiveOffset, numVertices, numPrimitives));
primitiveOffset += numPrimitives;
// Segments grid + skirts only
const withSkirts = SegmentVector.simpleSegment(0, indexOffset, vertices.length, indices.length - indexOffset);
segments.push({withoutSkirts, withSkirts});
}

this._gridBuffer = context.createVertexBuffer(gridVertices, posAttributes.members);
this._gridIndexBuffer = context.createIndexBuffer(gridIndices, true);
return {vertices, indices, segments};
}

_createGrid(context: Context) {
const gridWithLods = this._fillGridMeshWithLods(GLOBE_VERTEX_GRID_SIZE, GLOBE_LATITUDINAL_GRID_LOD_TABLE);
this._gridSegments = gridWithLods.segments;

this._gridBuffer = context.createVertexBuffer(gridWithLods.vertices, posAttributes.members);
this._gridIndexBuffer = context.createIndexBuffer(gridWithLods.indices, true);
}

_createPoles(context: Context) {
Expand Down Expand Up @@ -794,8 +908,8 @@ export class GlobeSharedBuffers {
this._poleSouthVertexBuffer = context.createVertexBuffer(southVertices, globeLayoutAttributes, false);
}

getGridBuffers(latitudinalLod: number): [VertexBuffer, IndexBuffer, SegmentVector] {
return [this._gridBuffer, this._gridIndexBuffer, this._gridSegments[latitudinalLod]];
getGridBuffers(latitudinalLod: number, withSkirts: boolean): [VertexBuffer, IndexBuffer, SegmentVector] {
return [this._gridBuffer, this._gridIndexBuffer, withSkirts ? this._gridSegments[latitudinalLod].withSkirts : this._gridSegments[latitudinalLod].withoutSkirts];
}

getPoleBuffers(z: number): [VertexBuffer, VertexBuffer, IndexBuffer, SegmentVector] {
Expand All @@ -806,13 +920,15 @@ export class GlobeSharedBuffers {
if (!this._wireframeSegments) {
const wireframeIndices = new LineIndexArray();
const quadExt = GLOBE_VERTEX_GRID_SIZE;
const vertexExt = quadExt + 1;
const vertexExt = quadExt + 1 + (EMBED_SKIRTS ? 2 : 0);

const iterOffset = EMBED_SKIRTS ? 1 : 0;

this._wireframeSegments = [];
for (let k = 0, primitiveOffset = 0; k < GLOBE_LATITUDINAL_GRID_LOD_TABLE.length; k++) {
const latitudinalLod = GLOBE_LATITUDINAL_GRID_LOD_TABLE[k];
for (let j = 0; j < latitudinalLod; j++) {
for (let i = 0; i < quadExt; i++) {
for (let j = iterOffset; j < latitudinalLod + iterOffset; j++) {
for (let i = iterOffset; i < quadExt + iterOffset; i++) {
const index = j * vertexExt + i;
wireframeIndices.emplaceBack(index, index + 1);
wireframeIndices.emplaceBack(index, index + vertexExt);
Expand Down
12 changes: 12 additions & 0 deletions src/shaders/_prelude_terrain.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ vec3 elevationVector(vec2 pos) { return vec3(0, 0, 1); }

#endif

// Handle skirt flag for terrain & globe shaders

const float skirtOffset = 24575.0;
vec3 decomposeToPosAndSkirt(vec2 posWithComposedSkirt)
{
float skirt = float(posWithComposedSkirt.x >= skirtOffset);
vec2 pos = posWithComposedSkirt - vec2(skirt * skirtOffset, 0.0);

return vec3(pos, skirt);
}


#ifdef TERRAIN

#ifdef TERRAIN_DEM_FLOAT_FORMAT
Expand Down
19 changes: 15 additions & 4 deletions src/shaders/globe_raster.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ uniform mat4 u_merc_matrix;
uniform float u_zoom_transition;
uniform vec2 u_merc_center;
uniform mat3 u_grid_matrix;
uniform float u_skirt_height;

#ifdef GLOBE_POLES
attribute vec3 a_globe_pos;
attribute vec2 a_uv;
#else
attribute vec2 a_pos;
attribute vec2 a_pos; // .xy - grid coords, .z - 1 - skirt, 0 - grid
#endif

varying vec2 v_pos0;
Expand Down Expand Up @@ -52,7 +53,9 @@ void main() {
float idx = u_grid_matrix[1][2];
float idy = u_grid_matrix[2][2];

vec3 latLng = u_grid_matrix * vec3(a_pos, 1.0);
vec3 decomposed_pos_and_skirt = decomposeToPosAndSkirt(a_pos);

vec3 latLng = u_grid_matrix * vec3(decomposed_pos_and_skirt.xy, 1.0);

float mercatorY = mercatorYfromLat(latLng[0]);
float uvY = mercatorY * tiles - idy;
Expand All @@ -68,13 +71,16 @@ void main() {
v_pos0 = uv;
vec2 tile_pos = uv * EXTENT;

// Used for poles and skirts
vec3 globe_derived_up_vector = normalize(globe_pos) * u_tile_up_scale;
#ifdef GLOBE_POLES
// Normal vector can be derived from the ecef position
// as "elevationVector" can't be queried outside of the tile
vec3 up_vector = normalize(globe_pos) * u_tile_up_scale;
vec3 up_vector = globe_derived_up_vector;
#else
vec3 up_vector = elevationVector(tile_pos);
#endif

float height = elevation(tile_pos);

#ifdef TERRAIN_WIREFRAME
Expand All @@ -83,13 +89,18 @@ void main() {

globe_pos += up_vector * height;

#ifndef GLOBE_POLES
// Apply skirts for grid and only by offsetting via globe_pos derived normal
globe_pos -= globe_derived_up_vector * u_skirt_height * decomposed_pos_and_skirt.z;
#endif

#ifdef GLOBE_POLES
vec4 interpolated_pos = u_globe_matrix * vec4(globe_pos, 1.0);
#else
vec4 globe_world_pos = u_globe_matrix * vec4(globe_pos, 1.0);
vec4 merc_world_pos = vec4(0.0);
if (u_zoom_transition > 0.0) {
merc_world_pos = vec4(merc_pos, height, 1.0);
merc_world_pos = vec4(merc_pos, height - u_skirt_height * decomposed_pos_and_skirt.z, 1.0);
merc_world_pos.xy -= u_merc_center;
merc_world_pos.x = wrap(merc_world_pos.x, -0.5, 0.5);
merc_world_pos = u_merc_matrix * merc_world_pos;
Expand Down
6 changes: 3 additions & 3 deletions src/shaders/terrain_raster.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ varying vec4 v_pos_light_view_1;
varying float v_depth;
#endif

const float skirtOffset = 24575.0;
const float wireframeOffset = 0.00015;

void main() {
float skirt = float(a_pos.x >= skirtOffset);
vec2 decodedPos = a_pos - vec2(skirt * skirtOffset, 0.0);
vec3 decomposedPosAndSkirt = decomposeToPosAndSkirt(a_pos);
float skirt = decomposedPosAndSkirt.z;
vec2 decodedPos = decomposedPosAndSkirt.xy;
float elevation = elevation(decodedPos) - skirt * u_skirt_height;
#ifdef TERRAIN_WIREFRAME
elevation += wireframeOffset;
Expand Down
Loading

0 comments on commit 77757cf

Please sign in to comment.