From 9b3be674755bb0044c1381cb1f1f5ca11fcc50a2 Mon Sep 17 00:00:00 2001 From: Arindam Bose Date: Tue, 1 Dec 2020 14:11:03 -0800 Subject: [PATCH 1/4] WIP heightmap cascades --- src/geo/transform.js | 43 +++++++++++++++++++++++- src/terrain/heightmap_cascade.js | 57 ++++++++++++++++++++++++++++++++ src/util/util.js | 8 +++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/terrain/heightmap_cascade.js diff --git a/src/geo/transform.js b/src/geo/transform.js index 634294aa4cc..0170f4ec207 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -4,7 +4,7 @@ import LngLat from './lng_lat'; import LngLatBounds from './lng_lat_bounds'; import MercatorCoordinate, {mercatorXfromLng, mercatorYfromLat, mercatorZfromAltitude, latFromMercatorY} from './mercator_coordinate'; import Point from '@mapbox/point-geometry'; -import {wrap, clamp, radToDeg, degToRad} from '../util/util'; +import {wrap, clamp, radToDeg, degToRad, distanceToLine} from '../util/util'; import {number as interpolate} from '../style-spec/util/interpolate'; import EXTENT from '../data/extent'; import {vec4, mat4, mat2, vec3, quat} from 'gl-matrix'; @@ -44,6 +44,7 @@ class Transform { cameraToCenterDistance: number; mercatorMatrix: Array; projMatrix: Float64Array; + orthoBoundsMatrix: Float64Array; invProjMatrix: Float64Array; alignedProjMatrix: Float64Array; pixelMatrix: Float64Array; @@ -1263,6 +1264,7 @@ class Transform { const halfFov = this._fov / 2; const offset = this.centerOffset; this.cameraToCenterDistance = 0.5 / Math.tan(halfFov) * this.height; + console.log(this.cameraToCenterDistance); const pixelsPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize; this._updateCameraState(); @@ -1356,6 +1358,8 @@ class Transform { if (!m) throw new Error("failed to invert matrix"); this.pixelMatrixInverse = m; + this.projMatrix = this._calcOrthoBoundsMatrix(); + this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; } @@ -1460,6 +1464,43 @@ class Transform { return !!this._elevation; } + /** + * Returns a matrix that represents an orthographic camera that + * looks from top down at the center, is rotated by the camera bearing and, + * encompasses the trapezoid created by the instersection of camera frustum with the map plane. + * + * Returns the correct value only hafter _calcMatrices() has been called to update other matrices first. + * + * @returns {Float64Array} + */ + _calcOrthoBoundsMatrix(): Float64Array { + const {x, y} = this.point; + const worldSize = this.worldSize; + const toPixels = (coord: MercatorCoordinate): vec3 => [coord.x * worldSize, coord.y * worldSize, 0]; + + const horizon = this.horizonLineFromTop(); + const topLeft = toPixels(this.pointCoordinate(new Point(0, horizon))); + const topRight = toPixels(this.pointCoordinate(new Point(this.width, horizon))); + const btmLeft = toPixels(this.pointCoordinate(new Point(0, this.height))); + const btmRight = toPixels(this.pointCoordinate(new Point(this.width, this.height))); + + const n = vec3.sub([], topRight, topLeft); + vec3.normalize(n, n); + + const top = Math.abs(distanceToLine(topLeft, topRight, [x, y, 0])); + const btm = Math.abs(distanceToLine(btmLeft, btmRight, [x, y, 0])); + const left = Math.abs(vec3.dot(vec3.sub([], [x, y, 0], topLeft), n)); + const right = Math.abs(vec3.dot(vec3.sub([], [x, y, 0], topRight), n)); + + const m = mat4.ortho(new Float64Array(16), -left, right, -btm, top, 0, 1); + mat4.scale(m, m, [1, -1, 1]); + mat4.rotateZ(m, m, this.angle); + mat4.translate(m, m, [-x, -y, 0]); + + console.log(vec4.transformMat4([], [x, y, 0, 1], m)); + return m; + } + isHorizonVisibleForPoints(p0: Point, p1: Point): boolean { const minX = Math.min(p0.x, p1.x); const maxX = Math.max(p0.x, p1.x); diff --git a/src/terrain/heightmap_cascade.js b/src/terrain/heightmap_cascade.js new file mode 100644 index 00000000000..a0ec8d69cf6 --- /dev/null +++ b/src/terrain/heightmap_cascade.js @@ -0,0 +1,57 @@ +// @flow +import type Transform from '../geo/transform'; +import type MercatorCoordinate from '../geo/mercator_coordinate'; +import {distanceToLine} from '../util/util'; +import Point from '@mapbox/point-geometry'; +import {mat4, vec3} from 'gl-matrix'; + +export class HeightmapCascade { + // index 0 -> near cascade + // index 1 -> far cascade + matrices: [Float64Array, Float64Array]; + + constructor() { + this.matrices = [ + new Float64Array(16), + new Float64Array(16) + ]; + } + + calculateMatrices(transform: Transform) { + const {x, y} = transform.point; + const worldSize = transform.worldSize; + const toPixels = (coord: MercatorCoordinate): vec3 => [coord.x * worldSize, coord.y * worldSize, 0]; + + const horizon = transform.horizonLineFromTop(); + const topLeft = toPixels(transform.pointCoordinate(new Point(0, horizon))); + const topRight = toPixels(transform.pointCoordinate(new Point(transform.width, horizon))); + const btmLeft = toPixels(transform.pointCoordinate(new Point(0, transform.height))); + const btmRight = toPixels(transform.pointCoordinate(new Point(transform.width, transform.height))); + + } + + _matrixFromTrapezoid(out: Float64Array, angle: Number, topLeft: vec3, topRight: vec3, btmLeft: vec3, btmRight: vec3) { + const center = vec3.add([], topLeft); + vec3.add(center, center, topRight); + vec3.add(center, center, btmLeft); + vec3.add(center, center, btmRight); + vec3.scale(center, center, 1 / 4); + + const n = vec3.sub([], topRight, topLeft); + vec3.normalize(n, n); + + const top = Math.abs(distanceToLine(topLeft, topRight, center)); + const btm = Math.abs(distanceToLine(btmLeft, btmRight, center)); + const left = Math.abs(vec3.dot(vec3.sub([], center, topLeft), n)); + const right = Math.abs(vec3.dot(vec3.sub([], center, topRight), n)); + + const m = mat4.ortho(out, -left, right, -btm, top, 0, 1); + mat4.scale(m, m, [1, -1, 1]); + mat4.rotateZ(m, m, angle); + + vec3.scale(center, center, -1); + mat4.translate(m, m, center); + } + + +} \ No newline at end of file diff --git a/src/util/util.js b/src/util/util.js index 493e05f26e6..a90d18fac67 100644 --- a/src/util/util.js +++ b/src/util/util.js @@ -5,6 +5,7 @@ import UnitBezier from '@mapbox/unitbezier'; import Point from '@mapbox/point-geometry'; import window from './window'; import assert from 'assert'; +import {vec3} from 'gl-matrix'; import type {Callback} from '../types/callback'; @@ -147,6 +148,13 @@ export function bezier(p1x: number, p1y: number, p2x: number, p2y: number): (t: }; } +export function distanceToLine(p0: vec3, p1: vec3, p: vec3): number { + const p0p1 = vec3.sub([], p1, p0); + const p0p = vec3.sub([], p, p0); + + return vec3.length(vec3.cross(p0p, p0p, p0p1)) / vec3.length(p0p1); +} + /** * A default bezier-curve powered easing function with * control points (0.25, 0.1) and (0.25, 1) From d60df950cd8227ecde4dced63163d613d533f394 Mon Sep 17 00:00:00 2001 From: Arindam Bose Date: Tue, 5 Jan 2021 17:04:48 -0800 Subject: [PATCH 2/4] Near and far cascade proj matrices are looking good --- src/geo/transform.js | 41 -------------------------------- src/terrain/heightmap_cascade.js | 36 ++++++++++++++++++++-------- src/terrain/terrain.js | 5 ++++ 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index 0170f4ec207..9cd255c96f4 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -44,7 +44,6 @@ class Transform { cameraToCenterDistance: number; mercatorMatrix: Array; projMatrix: Float64Array; - orthoBoundsMatrix: Float64Array; invProjMatrix: Float64Array; alignedProjMatrix: Float64Array; pixelMatrix: Float64Array; @@ -1264,7 +1263,6 @@ class Transform { const halfFov = this._fov / 2; const offset = this.centerOffset; this.cameraToCenterDistance = 0.5 / Math.tan(halfFov) * this.height; - console.log(this.cameraToCenterDistance); const pixelsPerMeter = mercatorZfromAltitude(1, this.center.lat) * this.worldSize; this._updateCameraState(); @@ -1358,8 +1356,6 @@ class Transform { if (!m) throw new Error("failed to invert matrix"); this.pixelMatrixInverse = m; - this.projMatrix = this._calcOrthoBoundsMatrix(); - this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; } @@ -1464,43 +1460,6 @@ class Transform { return !!this._elevation; } - /** - * Returns a matrix that represents an orthographic camera that - * looks from top down at the center, is rotated by the camera bearing and, - * encompasses the trapezoid created by the instersection of camera frustum with the map plane. - * - * Returns the correct value only hafter _calcMatrices() has been called to update other matrices first. - * - * @returns {Float64Array} - */ - _calcOrthoBoundsMatrix(): Float64Array { - const {x, y} = this.point; - const worldSize = this.worldSize; - const toPixels = (coord: MercatorCoordinate): vec3 => [coord.x * worldSize, coord.y * worldSize, 0]; - - const horizon = this.horizonLineFromTop(); - const topLeft = toPixels(this.pointCoordinate(new Point(0, horizon))); - const topRight = toPixels(this.pointCoordinate(new Point(this.width, horizon))); - const btmLeft = toPixels(this.pointCoordinate(new Point(0, this.height))); - const btmRight = toPixels(this.pointCoordinate(new Point(this.width, this.height))); - - const n = vec3.sub([], topRight, topLeft); - vec3.normalize(n, n); - - const top = Math.abs(distanceToLine(topLeft, topRight, [x, y, 0])); - const btm = Math.abs(distanceToLine(btmLeft, btmRight, [x, y, 0])); - const left = Math.abs(vec3.dot(vec3.sub([], [x, y, 0], topLeft), n)); - const right = Math.abs(vec3.dot(vec3.sub([], [x, y, 0], topRight), n)); - - const m = mat4.ortho(new Float64Array(16), -left, right, -btm, top, 0, 1); - mat4.scale(m, m, [1, -1, 1]); - mat4.rotateZ(m, m, this.angle); - mat4.translate(m, m, [-x, -y, 0]); - - console.log(vec4.transformMat4([], [x, y, 0, 1], m)); - return m; - } - isHorizonVisibleForPoints(p0: Point, p1: Point): boolean { const minX = Math.min(p0.x, p1.x); const maxX = Math.max(p0.x, p1.x); diff --git a/src/terrain/heightmap_cascade.js b/src/terrain/heightmap_cascade.js index a0ec8d69cf6..4fb40cba467 100644 --- a/src/terrain/heightmap_cascade.js +++ b/src/terrain/heightmap_cascade.js @@ -4,17 +4,21 @@ import type MercatorCoordinate from '../geo/mercator_coordinate'; import {distanceToLine} from '../util/util'; import Point from '@mapbox/point-geometry'; import {mat4, vec3} from 'gl-matrix'; +import {array as interpolate} from '../style-spec/util/interpolate'; + +const NEAR_THRESHOLD_MAX = 1 / Math.cos(Math.PI / 4); +const NEAR_THRESHOLD_MIN = 0.1; export class HeightmapCascade { - // index 0 -> near cascade - // index 1 -> far cascade - matrices: [Float64Array, Float64Array]; + nearCascadeMatrix: Float64Array; + farCascadeMatrix: Float64Array; + needsFarCascade: boolean; constructor() { - this.matrices = [ - new Float64Array(16), - new Float64Array(16) - ]; + this.nearCascadeMatrix = new Float64Array(16); + this.farCascadeMatrix = new Float64Array(16); + this.needsFarCascade = false; + } calculateMatrices(transform: Transform) { @@ -28,10 +32,24 @@ export class HeightmapCascade { const btmLeft = toPixels(transform.pointCoordinate(new Point(0, transform.height))); const btmRight = toPixels(transform.pointCoordinate(new Point(transform.width, transform.height))); + const top = distanceToLine(topLeft, topRight, [0, 0, 0]); + const btm = distanceToLine(btmLeft, btmRight, [0, 0, 0]); + + const nearCascadeHeight = transform.height * NEAR_THRESHOLD_MAX; + const nearCascadeExtent = Math.max(Math.min(nearCascadeHeight / Math.abs(btm - top), 1), NEAR_THRESHOLD_MIN); + this.needsFarCascade = nearCascadeExtent < 1; + + const midLeft = interpolate(btmLeft, topLeft, nearCascadeExtent); + const midRight = interpolate(btmRight, topRight, nearCascadeExtent); + this._matrixFromTrapezoid(this.nearCascadeMatrix, transform.angle, midLeft, midRight, btmLeft, btmRight); + + if (this.needsFarCascade) { + this._matrixFromTrapezoid(this.farCascadeMatrix, transform.angle, topLeft, topRight, midLeft, midRight); + } } _matrixFromTrapezoid(out: Float64Array, angle: Number, topLeft: vec3, topRight: vec3, btmLeft: vec3, btmRight: vec3) { - const center = vec3.add([], topLeft); + const center = vec3.add([], [0, 0, 0], topLeft); vec3.add(center, center, topRight); vec3.add(center, center, btmLeft); vec3.add(center, center, btmRight); @@ -52,6 +70,4 @@ export class HeightmapCascade { vec3.scale(center, center, -1); mat4.translate(m, m, center); } - - } \ No newline at end of file diff --git a/src/terrain/terrain.js b/src/terrain/terrain.js index a13cad85a2b..a84a07e4c9e 100644 --- a/src/terrain/terrain.js +++ b/src/terrain/terrain.js @@ -37,6 +37,7 @@ import browser from '../util/browser'; import DEMData from '../data/dem_data'; import rasterFade from '../render/raster_fade'; import {create as createSource} from '../source/source'; +import {HeightmapCascade} from './heightmap_cascade'; import type Map from '../ui/map'; import type Painter from '../render/painter'; @@ -178,6 +179,7 @@ export class Terrain extends Elevation { style: Style; orthoMatrix: mat4; enabled: boolean; + heightmapCascade: HeightmapCascade drapeFirst: boolean; drapeFirstPending: boolean; @@ -240,6 +242,7 @@ export class Terrain extends Elevation { this._tilesDirty = {}; this.style = style; this._useVertexMorphing = true; + this.heightmapCascade = new HeightmapCascade(); style.on('data', this._onStyleDataEvent.bind(this)); } @@ -1015,6 +1018,8 @@ export class Terrain extends Elevation { this._depthDone = true; } + + _isLayerDrapedOverTerrain(styleLayer: StyleLayer): boolean { if (!this.enabled) return false; return drapedLayers.hasOwnProperty(styleLayer.type); From 4147ff4e0878ea71ce5f72bcc09b1a1543f9bde0 Mon Sep 17 00:00:00 2001 From: Arindam Bose Date: Thu, 7 Jan 2021 14:07:43 -0800 Subject: [PATCH 3/4] Maybe drawing cascades correctly --- src/geo/transform.js | 11 ++- src/render/draw_circle.js | 2 +- src/render/program/program_uniforms.js | 3 +- src/shaders/shaders.js | 3 + src/shaders/terrain_height.fragment.glsl | 93 +++++++++++++++++ src/shaders/terrain_height.vertex.glsl | 10 ++ src/terrain/heightmap_cascade.js | 121 ++++++++++++++++++++++- src/terrain/terrain.js | 11 ++- src/terrain/terrain_raster_program.js | 16 ++- 9 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 src/shaders/terrain_height.fragment.glsl create mode 100644 src/shaders/terrain_height.vertex.glsl diff --git a/src/geo/transform.js b/src/geo/transform.js index 9cd255c96f4..1a00bb5d43b 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -1060,7 +1060,14 @@ class Transform { if (cache[posMatrixKey]) { return cache[posMatrixKey]; } + const posMatrix = this.calcTranslationScaleMatrix(unwrappedTileID); + mat4.multiply(posMatrix, aligned ? this.alignedProjMatrix : this.projMatrix, posMatrix); + + cache[posMatrixKey] = new Float32Array(posMatrix); + return cache[posMatrixKey]; + } + calcTranslationScaleMatrix(unwrappedTileID: UnwrappedTileID): Float64Array { const canonical = unwrappedTileID.canonical; const scale = this.worldSize / this.zoomScale(canonical.z); const unwrappedX = canonical.x + Math.pow(2, canonical.z) * unwrappedTileID.wrap; @@ -1068,10 +1075,8 @@ class Transform { const posMatrix = mat4.identity(new Float64Array(16)); mat4.translate(posMatrix, posMatrix, [unwrappedX * scale, canonical.y * scale, 0]); mat4.scale(posMatrix, posMatrix, [scale / EXTENT, scale / EXTENT, 1]); - mat4.multiply(posMatrix, aligned ? this.alignedProjMatrix : this.projMatrix, posMatrix); - cache[posMatrixKey] = new Float32Array(posMatrix); - return cache[posMatrixKey]; + return posMatrix; } customLayerMatrix(): Array { diff --git a/src/render/draw_circle.js b/src/render/draw_circle.js index 5fe136fd499..d80d0426486 100644 --- a/src/render/draw_circle.js +++ b/src/render/draw_circle.js @@ -109,7 +109,7 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt for (const segmentsState of segmentsRenderStates) { const {programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues, tile} = segmentsState.state; const segments = segmentsState.segments; - if (painter.terrain) painter.terrain.setupElevationDraw(tile, program, {useDepthForOcclusion: true}); + // if (painter.terrain) painter.terrain.setupElevationDraw(tile, program, {useDepthForOcclusion: true}); program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, uniformValues, layer.id, diff --git a/src/render/program/program_uniforms.js b/src/render/program/program_uniforms.js index c9c0da146de..39d2aa7b1d8 100644 --- a/src/render/program/program_uniforms.js +++ b/src/render/program/program_uniforms.js @@ -14,7 +14,7 @@ import {lineUniforms, lineGradientUniforms, linePatternUniforms, lineSDFUniforms import {rasterUniforms} from './raster_program'; import {symbolIconUniforms, symbolSDFUniforms, symbolTextAndIconUniforms} from './symbol_program'; import {backgroundUniforms, backgroundPatternUniforms} from './background_program'; -import {terrainRasterUniforms} from '../../terrain/terrain_raster_program'; +import {terrainRasterUniforms, terrainHeightUniforms} from '../../terrain/terrain_raster_program'; import {skyboxUniforms, skyboxGradientUniforms} from './skybox_program'; import {skyboxCaptureUniforms} from './skybox_capture_program'; @@ -48,6 +48,7 @@ export const programUniforms = { backgroundPattern: backgroundPatternUniforms, terrainRaster: terrainRasterUniforms, terrainDepth: terrainRasterUniforms, + terrainHeight: terrainHeightUniforms, skybox: skyboxUniforms, skyboxGradient: skyboxGradientUniforms, skyboxCapture: skyboxCaptureUniforms diff --git a/src/shaders/shaders.js b/src/shaders/shaders.js index d8aa42e33aa..495e7d87676 100644 --- a/src/shaders/shaders.js +++ b/src/shaders/shaders.js @@ -61,6 +61,8 @@ import terrainRasterFrag from './terrain_raster.fragment.glsl'; import terrainRasterVert from './terrain_raster.vertex.glsl'; import terrainDepthFrag from './terrain_depth.fragment.glsl'; import terrainDepthVert from './terrain_depth.vertex.glsl'; +import terrainHeightFrag from './terrain_height.fragment.glsl'; +import terrainHeightVert from './terrain_height.vertex.glsl'; import preludeTerrainVert from './_prelude_terrain.vertex.glsl'; import skyboxCaptureFrag from './skybox_capture.fragment.glsl'; import skyboxCaptureVert from './skybox_capture.vertex.glsl'; @@ -95,6 +97,7 @@ export const symbolSDF = compile(symbolSDFFrag, symbolSDFVert); export const symbolTextAndIcon = compile(symbolTextAndIconFrag, symbolTextAndIconVert); export const terrainRaster = compile(terrainRasterFrag, terrainRasterVert); export const terrainDepth = compile(terrainDepthFrag, terrainDepthVert); +export const terrainHeight = compile(terrainHeightFrag, terrainHeightVert); export const skybox = compile(skyboxFrag, skyboxVert); export const skyboxGradient = compile(skyboxGradientFrag, skyboxVert); export const skyboxCapture = compile(skyboxCaptureFrag, skyboxCaptureVert); diff --git a/src/shaders/terrain_height.fragment.glsl b/src/shaders/terrain_height.fragment.glsl new file mode 100644 index 00000000000..51f4c7c0f9a --- /dev/null +++ b/src/shaders/terrain_height.fragment.glsl @@ -0,0 +1,93 @@ +#define MIN_ELEV -10000.0 +#define MAX_ELEV 10000.0 + +#ifdef GL_ES +precision highp float; +#endif +varying vec2 v_pos; +#ifdef TERRAIN + +uniform sampler2D u_dem; +uniform sampler2D u_dem_prev; +uniform vec4 u_dem_unpack; +uniform vec2 u_dem_tl; +uniform vec2 u_dem_tl_prev; +uniform float u_dem_scale; +uniform float u_dem_scale_prev; +uniform float u_dem_size; +uniform float u_dem_lerp; +uniform float u_exaggeration; +uniform float u_meter_to_dem; +uniform mat4 u_label_plane_matrix_inv; + +uniform sampler2D u_depth; +uniform vec2 u_depth_size_inv; + +vec4 tileUvToDemSample(vec2 uv, float dem_size, float dem_scale, vec2 dem_tl) { + vec2 pos = dem_size * (uv * dem_scale + dem_tl) + 1.0; + vec2 f = fract(pos); + return vec4((pos - f + 0.5) / (dem_size + 2.0), f); +} + +float decodeElevation(vec4 v) { + return dot(vec4(v.xyz * 255.0, -1.0), u_dem_unpack); +} + +float currentElevation(vec2 apos) { + float dd = 1.0 / (u_dem_size + 2.0); + vec4 r = tileUvToDemSample(apos / 8192.0, u_dem_size, u_dem_scale, u_dem_tl); + vec2 pos = r.xy; + vec2 f = r.zw; + + float tl = decodeElevation(texture2D(u_dem, pos)); + float tr = decodeElevation(texture2D(u_dem, pos + vec2(dd, 0.0))); + float bl = decodeElevation(texture2D(u_dem, pos + vec2(0.0, dd))); + float br = decodeElevation(texture2D(u_dem, pos + vec2(dd, dd))); + + return u_exaggeration * mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y); +} + +float prevElevation(vec2 apos) { + float dd = 1.0 / (u_dem_size + 2.0); + vec4 r = tileUvToDemSample(apos / 8192.0, u_dem_size, u_dem_scale_prev, u_dem_tl_prev); + vec2 pos = r.xy; + vec2 f = r.zw; + + float tl = decodeElevation(texture2D(u_dem_prev, pos)); + float tr = decodeElevation(texture2D(u_dem_prev, pos + vec2(dd, 0.0))); + float bl = decodeElevation(texture2D(u_dem_prev, pos + vec2(0.0, dd))); + float br = decodeElevation(texture2D(u_dem_prev, pos + vec2(dd, dd))); + + return u_exaggeration * mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y); +} + +#ifdef TERRAIN_VERTEX_MORPHING +float elevation(vec2 apos) { + float nextElevation = currentElevation(apos); + float prevElevation = prevElevation(apos); + return mix(prevElevation, nextElevation, u_dem_lerp); +} +#else +float elevation(vec2 apos) { + return currentElevation(apos); +} +#endif + +#else + +float elevation(vec2 pos) { return 0.0; } +#endif + +// Pack depth to RGBA. A piece of code copied in various libraries and WebGL +// shadow mapping examples. +vec4 pack_normalized(float n) { + const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); + const vec4 bit_mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); + vec4 res = fract(n * bit_shift); + res -= res.xxyz * bit_mask; + return res; +} + +void main() { + gl_FragColor = pack_normalized((elevation(v_pos) - MIN_ELEV)/(MAX_ELEV - MIN_ELEV)); +} diff --git a/src/shaders/terrain_height.vertex.glsl b/src/shaders/terrain_height.vertex.glsl new file mode 100644 index 00000000000..bca15b389b4 --- /dev/null +++ b/src/shaders/terrain_height.vertex.glsl @@ -0,0 +1,10 @@ +attribute vec2 a_pos; +varying vec2 v_pos; + +uniform mat4 u_matrix; + +void main() { + // This vertex shader expects a EXTENT x EXTENT quad + v_pos = a_pos; + gl_Position = u_matrix * vec4(a_pos , 0, 1); +} \ No newline at end of file diff --git a/src/terrain/heightmap_cascade.js b/src/terrain/heightmap_cascade.js index 4fb40cba467..a7dfe5fc2ae 100644 --- a/src/terrain/heightmap_cascade.js +++ b/src/terrain/heightmap_cascade.js @@ -1,10 +1,21 @@ // @flow import type Transform from '../geo/transform'; import type MercatorCoordinate from '../geo/mercator_coordinate'; -import {distanceToLine} from '../util/util'; +import type Painter from '../render/painter'; +import type SourceCache from '../source/source_cache'; +import type {OverscaledTileID} from '../source/tile_id'; +import {distanceToLine, nextPowerOfTwo} from '../util/util'; import Point from '@mapbox/point-geometry'; import {mat4, vec3} from 'gl-matrix'; import {array as interpolate} from '../style-spec/util/interpolate'; +import Framebuffer from '../gl/framebuffer'; +import Texture from '../render/texture'; +import assert from 'assert'; +import DepthMode from '../gl/depth_mode'; +import ColorMode from '../gl/color_mode'; +import CullFaceMode from '../gl/cull_face_mode'; +import { terrainHeightUniformValues } from './terrain_raster_program'; +import StencilMode from '../gl/stencil_mode'; const NEAR_THRESHOLD_MAX = 1 / Math.cos(Math.PI / 4); const NEAR_THRESHOLD_MIN = 0.1; @@ -14,11 +25,21 @@ export class HeightmapCascade { farCascadeMatrix: Float64Array; needsFarCascade: boolean; + currNearSize: number; + _nearFBO: Framebuffer; + _nearTexture: Texture; + _farFBO: Framebuffer; + _farTexture: Texture; + constructor() { this.nearCascadeMatrix = new Float64Array(16); this.farCascadeMatrix = new Float64Array(16); this.needsFarCascade = false; + this.currNearSize = 512; + } + get currFarSize(): number { + return this.currNearSize / 2; } calculateMatrices(transform: Transform) { @@ -48,6 +69,104 @@ export class HeightmapCascade { } } + draw(terrain: Terrain, painter: Painter, sourceCache: SourceCache, tileIDs: Array) { + this.calculateMatrices(painter.transform); + + // Initialize FBOS + this._setupFBOs(painter); + const transform = painter.transform; + const context = painter.context; + const gl = context.gl; + + //Draw near FBO + assert(this._nearFBO); + assert(this._nearTexture); + + gl.bindTexture(gl.TEXTURE_2D, this._nearFBO.colorAttachment.get()); + context.bindFramebuffer.set(this._nearFBO.framebuffer); + context.viewport.set([0, 0, this.currNearSize, this.currNearSize]); + + const program = painter.useProgram('terrainHeight'); + for (const coord of tileIDs) { + const tile = sourceCache.getTile(coord); + + const matrix = mat4.multiply([], this.nearCascadeMatrix, transform.calcTranslationScaleMatrix(coord.toUnwrapped())); + const uniformValues = terrainHeightUniformValues(matrix); + terrain.setupElevationDraw(tile, program); + program.draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, ColorMode.unblended, CullFaceMode.disabled, + uniformValues, "terrain_height", painter.debugBuffer, painter.quadTriangleIndexBuffer, painter.debugSegments); + } + + if(this.needsFarCascade) { + //Draw far FBO + assert(this._farFBO); + assert(this._farTexture); + + gl.bindTexture(gl.TEXTURE_2D, this._farFBO.colorAttachment.get()); + context.bindFramebuffer.set(this._farFBO.framebuffer); + context.viewport.set([0, 0, this.currFarSize, this.currFarSize]); + + for (const coord of tileIDs) { + const tile = sourceCache.getTile(coord); + + const matrix = mat4.multiply([], this.farCascadeMatrix, transform.calcTranslationScaleMatrix(coord.toUnwrapped())); + const uniformValues = terrainHeightUniformValues(matrix); + terrain.setupElevationDraw(tile, program); + program.draw(context, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, ColorMode.unblended, CullFaceMode.disabled, + uniformValues, "terrain_height", painter.debugBuffer, painter.quadTriangleIndexBuffer, painter.debugSegments); + } + } + + context.bindFramebuffer.set(null); + context.viewport.set([0, 0, painter.width, painter.height]); + } + + _setupFBOs(painter: Painter) { + const context = painter.context; + const gl = context.gl; + + const nearSize = nextPowerOfTwo(painter.width); + if (this._nearFBO && nearSize !== this.currNearSize) { + this._nearTexture.destroy(); + this._nearFBO.destroy(); + delete this._nearFBO; + delete this._nearTexture; + + if (this._farFBO) { + this._farTexture.destroy(); + this._farFBO.destroy(); + + delete this._farFBO; + delete this._farTexture; + } + } + + if (!this._nearFBO) { + context.activeTexture.set(gl.TEXTURE2); + const texture = new Texture(context, {width: nearSize, height: nearSize, data: null}, gl.RGBA); + texture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); + const fbo = context.createFramebuffer(nearSize, nearSize, false); + fbo.colorAttachment.set(texture.texture); + + this._nearFBO = fbo; + this._nearTexture = texture; + this.currNearSize = nearSize; + } + + if (!this._farFBO && this.needsFarCascade) { + const farSize = this.currFarSize; + const fbo = context.createFramebuffer(farSize, farSize, false); + const texture = new Texture(context, {width: farSize, height: farSize, data: null}, gl.RGBA); + texture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); + context.activeTexture.set(gl.TEXTURE3); + fbo.colorAttachment.set(texture.texture); + + this._farFBO = fbo; + this._farTexture = texture; + } + + } + _matrixFromTrapezoid(out: Float64Array, angle: Number, topLeft: vec3, topRight: vec3, btmLeft: vec3, btmRight: vec3) { const center = vec3.add([], [0, 0, 0], topLeft); vec3.add(center, center, topRight); diff --git a/src/terrain/terrain.js b/src/terrain/terrain.js index a84a07e4c9e..1b82167689b 100644 --- a/src/terrain/terrain.js +++ b/src/terrain/terrain.js @@ -298,6 +298,7 @@ export class Terrain extends Elevation { this.proxySourceCache.update(transform); this._depthDone = false; + this._heightDone = false; this._emptyDEMTextureDirty = true; } else { this._disable(); @@ -588,7 +589,10 @@ export class Terrain extends Elevation { const painter = this.painter; if (painter.renderPass !== 'translucent') { // Depth texture is used only for POI symbols and circles, to skip render of symbols occluded by e.g. hill. - if (!this._depthDone && (layer.type === 'symbol' || layer.type === 'circle')) this.drawDepth(); + if (layer.type === 'symbol' || layer.type === 'circle') { + if(!this._depthDone) this.drawDepth(); + if(!this._heightDone) this.drawHeight(); + } return true; // Early leave: all rendering is done in translucent pass. } if (this.drapeFirst && this.drapeFirstPending) { @@ -985,6 +989,11 @@ export class Terrain extends Elevation { return p; } + drawHeight() { + this.heightmapCascade.draw(this, this.painter, this.proxySourceCache, this.proxyCoords); + this._heightDone = true; + } + drawDepth() { const painter = this.painter; const context = painter.context; diff --git a/src/terrain/terrain_raster_program.js b/src/terrain/terrain_raster_program.js index 33a1d2fee38..61001a9d969 100644 --- a/src/terrain/terrain_raster_program.js +++ b/src/terrain/terrain_raster_program.js @@ -15,12 +15,20 @@ export type TerrainRasterUniformsType = {| 'u_skirt_height': Uniform1f |}; +export type TerrainHeightUniformsType = {| + 'u_matrix': UniformMatrix4f +|}; + const terrainRasterUniforms = (context: Context, locations: UniformLocations): TerrainRasterUniformsType => ({ 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), 'u_image0': new Uniform1i(context, locations.u_image0), 'u_skirt_height': new Uniform1f(context, locations.u_skirt_height) }); +const terrainHeightUniforms = (context: Context, locations: UniformLocations): TerrainHeightUniformsType => ({ + 'u_matrix': new UniformMatrix4f(context, locations.u_matrix) +}); + const terrainRasterUniformValues = ( matrix: Float32Array, skirtHeight: number @@ -30,4 +38,10 @@ const terrainRasterUniformValues = ( 'u_skirt_height': skirtHeight }); -export {terrainRasterUniforms, terrainRasterUniformValues}; +const terrainHeightUniformValues = ( + matrix: Float32Array +): UniformValues => ({ + 'u_matrix': matrix +}); + +export {terrainRasterUniforms, terrainRasterUniformValues, terrainHeightUniforms, terrainHeightUniformValues}; From f8604a9bc51d872525436a7e96e685583a1faeff Mon Sep 17 00:00:00 2001 From: Arindam Bose Date: Thu, 7 Jan 2021 18:28:55 -0800 Subject: [PATCH 4/4] Actually drawing cascades correctly --- src/gl/framebuffer.js | 10 +++++++ src/render/draw_debug.js | 20 +++++++++++++- src/render/painter.js | 10 ++++++- src/render/program/debug_program.js | 35 ++++++++++++++++++++++-- src/render/program/program_uniforms.js | 3 +- src/shaders/debug_texture.fragment.glsl | 10 +++++++ src/shaders/debug_texture.vertex.glsl | 11 ++++++++ src/shaders/shaders.js | 3 ++ src/shaders/terrain_height.fragment.glsl | 7 +++-- src/terrain/heightmap_cascade.js | 13 ++++----- 10 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 src/shaders/debug_texture.fragment.glsl create mode 100644 src/shaders/debug_texture.vertex.glsl diff --git a/src/gl/framebuffer.js b/src/gl/framebuffer.js index 74b31e2effa..a49e4f3cc1b 100644 --- a/src/gl/framebuffer.js +++ b/src/gl/framebuffer.js @@ -2,6 +2,7 @@ import {ColorAttachment, DepthAttachment} from './value'; import type Context from './context'; +import type Painter from '../render/painter'; import assert from 'assert'; class Framebuffer { @@ -26,6 +27,15 @@ class Framebuffer { assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE); } + makeActive() { + const context = this.context; + const gl = context.gl; + + gl.bindTexture(gl.TEXTURE_2D, this.colorAttachment.get()); + context.bindFramebuffer.set(this.framebuffer); + context.viewport.set([0, 0, this.width, this.height]); + } + destroy() { const gl = this.context.gl; diff --git a/src/render/draw_debug.js b/src/render/draw_debug.js index b9c34e23366..4755aa67549 100644 --- a/src/render/draw_debug.js +++ b/src/render/draw_debug.js @@ -3,7 +3,7 @@ import DepthMode from '../gl/depth_mode'; import StencilMode from '../gl/stencil_mode'; import CullFaceMode from '../gl/cull_face_mode'; -import {debugUniformValues} from './program/debug_program'; +import {debugUniformValues, debugTextureUniformValues} from './program/debug_program'; import Color from '../style-spec/util/color'; import ColorMode from '../gl/color_mode'; import browser from '../util/browser'; @@ -11,6 +11,7 @@ import browser from '../util/browser'; import type Painter from './painter'; import type SourceCache from '../source/source_cache'; import type {OverscaledTileID} from '../source/tile_id'; +import type Framebuffer from '../gl/framebuffer'; export default drawDebug; @@ -42,6 +43,23 @@ export function drawDebugQueryGeometry(painter: Painter, sourceCache: SourceCach } } +export function drawDebugFramebuffer(painter: Painter, fbo: Framebuffer, viewport: [number, number, number, number]) { + const context = painter.context; + const gl = context.gl; + context.viewport.set(viewport); + + context.activeTexture.set(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); + + painter.useProgram('debugTexture').draw(context, gl.TRIANGLES, + DepthMode.disabled, StencilMode.disabled, ColorMode.unblended, CullFaceMode.disabled, + debugTextureUniformValues(painter, 0), + '$debug_fbo', painter.viewportBuffer, painter.quadTriangleIndexBuffer, + painter.viewportSegments); + + context.viewport.set([0, 0, painter.width, painter.height]); +} + function drawCrosshair(painter: Painter, x: number, y: number, color: Color) { const size = 20; const lineWidth = 2; diff --git a/src/render/painter.js b/src/render/painter.js index 280ee596e55..8665ed9b2bd 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -34,7 +34,7 @@ import fillExtrusion from './draw_fill_extrusion'; import hillshade from './draw_hillshade'; import raster from './draw_raster'; import background from './draw_background'; -import debug, {drawDebugPadding, drawDebugQueryGeometry} from './draw_debug'; +import debug, {drawDebugPadding, drawDebugQueryGeometry, drawDebugFramebuffer} from './draw_debug'; import custom from './draw_custom'; import sky from './draw_sky'; import {Terrain} from '../terrain/terrain'; @@ -534,6 +534,14 @@ class Painter { draw.debug(this, selectedSource, selectedSource.getVisibleCoordinates()); } + if(this.terrain) { + drawDebugFramebuffer(this, this.terrain.heightmapCascade._nearFBO, [0, 0, 512, 512]); + + if(this.terrain.heightmapCascade.needsFarCascade) { + drawDebugFramebuffer(this, this.terrain.heightmapCascade._farFBO, [0, 515, 512, 512]); + } + } + Debug.run(() => { if (this.options.showQueryGeometry && selectedSource) { drawDebugQueryGeometry(this, selectedSource, selectedSource.getVisibleCoordinates()); diff --git a/src/render/program/debug_program.js b/src/render/program/debug_program.js index a97ad08d213..5516ee51e1c 100644 --- a/src/render/program/debug_program.js +++ b/src/render/program/debug_program.js @@ -4,8 +4,10 @@ import { UniformColor, UniformMatrix4f, Uniform1i, - Uniform1f + Uniform1f, + Uniform2f } from '../uniform_binding'; +import {mat4} from 'gl-matrix'; import type Context from '../../gl/context'; import type {UniformValues, UniformLocations} from '../uniform_binding'; @@ -18,6 +20,12 @@ export type DebugUniformsType = {| 'u_overlay_scale': Uniform1f |}; +export type DebugTextureUniformsType = {| + 'u_matrix': UniformMatrix4f, + 'u_world': Uniform2f, + 'u_image': Uniform1i +|}; + const debugUniforms = (context: Context, locations: UniformLocations): DebugUniformsType => ({ 'u_color': new UniformColor(context, locations.u_color), 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), @@ -32,4 +40,27 @@ const debugUniformValues = (matrix: Float32Array, color: Color, scaleRatio: numb 'u_overlay_scale': scaleRatio }); -export {debugUniforms, debugUniformValues}; +const debugTextureUniforms = (context: Context, locations: UniformLocations): DebugTextureUniformsType => ({ + 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), + 'u_world': new Uniform2f(context, locations.u_world), + 'u_image': new Uniform1i(context, locations.u_image) +}); + +const debugTextureUniformValues = ( + painter: Painter, + textureUnit: number +): UniformValues => { + mat4 + const matrix = mat4.create(); + mat4.ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); + + const gl = painter.context.gl; + + return { + 'u_matrix': matrix, + 'u_world': [gl.drawingBufferWidth, gl.drawingBufferHeight], + 'u_image': textureUnit + }; +}; + +export {debugUniforms, debugUniformValues, debugTextureUniforms, debugTextureUniformValues}; diff --git a/src/render/program/program_uniforms.js b/src/render/program/program_uniforms.js index 39d2aa7b1d8..0b95045b632 100644 --- a/src/render/program/program_uniforms.js +++ b/src/render/program/program_uniforms.js @@ -6,7 +6,7 @@ import {fillExtrusionUniforms, fillExtrusionPatternUniforms} from './fill_extrus import {fillUniforms, fillPatternUniforms, fillOutlineUniforms, fillOutlinePatternUniforms} from './fill_program'; import {circleUniforms} from './circle_program'; import {collisionUniforms, collisionCircleUniforms} from './collision_program'; -import {debugUniforms} from './debug_program'; +import {debugUniforms, debugTextureUniforms} from './debug_program'; import {clippingMaskUniforms} from './clipping_mask_program'; import {heatmapUniforms, heatmapTextureUniforms} from './heatmap_program'; import {hillshadeUniforms, hillshadePrepareUniforms} from './hillshade_program'; @@ -31,6 +31,7 @@ export const programUniforms = { collisionBox: collisionUniforms, collisionCircle: collisionCircleUniforms, debug: debugUniforms, + debugTexture: debugTextureUniforms, clippingMask: clippingMaskUniforms, heatmap: heatmapUniforms, heatmapTexture: heatmapTextureUniforms, diff --git a/src/shaders/debug_texture.fragment.glsl b/src/shaders/debug_texture.fragment.glsl new file mode 100644 index 00000000000..bbc54385599 --- /dev/null +++ b/src/shaders/debug_texture.fragment.glsl @@ -0,0 +1,10 @@ +uniform sampler2D u_image; +varying vec2 v_pos; + +void main() { + gl_FragColor = texture2D(u_image, v_pos); + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(0.0); +#endif +} diff --git a/src/shaders/debug_texture.vertex.glsl b/src/shaders/debug_texture.vertex.glsl new file mode 100644 index 00000000000..0e57d0faaf7 --- /dev/null +++ b/src/shaders/debug_texture.vertex.glsl @@ -0,0 +1,11 @@ +uniform mat4 u_matrix; +uniform vec2 u_world; +attribute vec2 a_pos; +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos * u_world, 0, 1); + + v_pos.x = a_pos.x; + v_pos.y = 1.0 - a_pos.y; +} diff --git a/src/shaders/shaders.js b/src/shaders/shaders.js index 495e7d87676..e4ec34e62e1 100644 --- a/src/shaders/shaders.js +++ b/src/shaders/shaders.js @@ -22,6 +22,8 @@ import collisionCircleFrag from './collision_circle.fragment.glsl'; import collisionCircleVert from './collision_circle.vertex.glsl'; import debugFrag from './debug.fragment.glsl'; import debugVert from './debug.vertex.glsl'; +import debugTextureFrag from './debug_texture.fragment.glsl'; +import debugTextureVert from './debug_texture.vertex.glsl'; import fillFrag from './fill.fragment.glsl'; import fillVert from './fill.vertex.glsl'; import fillOutlineFrag from './fill_outline.fragment.glsl'; @@ -79,6 +81,7 @@ export const heatmapTexture = compile(heatmapTextureFrag, heatmapTextureVert); export const collisionBox = compile(collisionBoxFrag, collisionBoxVert); export const collisionCircle = compile(collisionCircleFrag, collisionCircleVert); export const debug = compile(debugFrag, debugVert); +export const debugTexture = compile(debugTextureFrag, debugTextureVert); export const fill = compile(fillFrag, fillVert); export const fillOutline = compile(fillOutlineFrag, fillOutlineVert); export const fillOutlinePattern = compile(fillOutlinePatternFrag, fillOutlinePatternVert); diff --git a/src/shaders/terrain_height.fragment.glsl b/src/shaders/terrain_height.fragment.glsl index 51f4c7c0f9a..6afe5680495 100644 --- a/src/shaders/terrain_height.fragment.glsl +++ b/src/shaders/terrain_height.fragment.glsl @@ -1,5 +1,5 @@ -#define MIN_ELEV -10000.0 -#define MAX_ELEV 10000.0 +#define MIN_ELEV -1000.0 +#define MAX_ELEV 2000.0 #ifdef GL_ES precision highp float; @@ -89,5 +89,6 @@ vec4 pack_normalized(float n) { } void main() { - gl_FragColor = pack_normalized((elevation(v_pos) - MIN_ELEV)/(MAX_ELEV - MIN_ELEV)); + float elevation = (elevation(v_pos) - MIN_ELEV)/(MAX_ELEV - MIN_ELEV); + gl_FragColor = vec4(elevation, elevation, elevation, 1.0); } diff --git a/src/terrain/heightmap_cascade.js b/src/terrain/heightmap_cascade.js index a7dfe5fc2ae..a6b5b67f7cc 100644 --- a/src/terrain/heightmap_cascade.js +++ b/src/terrain/heightmap_cascade.js @@ -16,9 +16,10 @@ import ColorMode from '../gl/color_mode'; import CullFaceMode from '../gl/cull_face_mode'; import { terrainHeightUniformValues } from './terrain_raster_program'; import StencilMode from '../gl/stencil_mode'; +import Color from '../style-spec/util/color'; const NEAR_THRESHOLD_MAX = 1 / Math.cos(Math.PI / 4); -const NEAR_THRESHOLD_MIN = 0.1; +const NEAR_THRESHOLD_MIN = 0.001; export class HeightmapCascade { nearCascadeMatrix: Float64Array; @@ -82,9 +83,8 @@ export class HeightmapCascade { assert(this._nearFBO); assert(this._nearTexture); - gl.bindTexture(gl.TEXTURE_2D, this._nearFBO.colorAttachment.get()); - context.bindFramebuffer.set(this._nearFBO.framebuffer); - context.viewport.set([0, 0, this.currNearSize, this.currNearSize]); + this._nearFBO.makeActive(); + context.clear({color: Color.white}); const program = painter.useProgram('terrainHeight'); for (const coord of tileIDs) { @@ -102,9 +102,8 @@ export class HeightmapCascade { assert(this._farFBO); assert(this._farTexture); - gl.bindTexture(gl.TEXTURE_2D, this._farFBO.colorAttachment.get()); - context.bindFramebuffer.set(this._farFBO.framebuffer); - context.viewport.set([0, 0, this.currFarSize, this.currFarSize]); + this._farFBO.makeActive(); + context.clear({color: Color.white}); for (const coord of tileIDs) { const tile = sourceCache.getTile(coord);