-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Custom layer immediate and draped rendering on globe and terrain (#12182
) * WIP: Custom layer draping support CustomStyleLayer API extended to support draping with new methods: export type CustomLayerInterface = { ... // misusing MercatorCoordiate's xyz for tile id (x, y, z) where wrap is baked into x. renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void, // return true only for frame when content has changed - otherwise, all the terrain // render cache would be invalidated and redrawn causing huge drop in performance. shouldRerenderTiles: ?() => boolean, } Using WebGL Wind demo code, for simplicity of use, instead of adding submodule, copied dist code. Full code is available at https://github.com/mapbox/webgl-wind/tree/astojilj-draping-support * dirty quick way to render a geometry with custom layer on globe * expose necessary API, some clean up * use sphere model * WIP combining PRs and changing immediate more rendering demo * satellite vis * satellite vis complete * clean up * continued clean up * add utility func to lnglat class and also render in mercator * format * rm threejs * rm unused funcs * minor * render test for draping * render test updated * minor update to render test * update debug page to use pure gl only * revert accidentally changed files to main * address review remarks, fix incorrect color, disable stencil * disable stencil from gl-js side before invoking renderToTile * Pass projection and resolve mercatorMatrix during transition In globe projection, mercator matrix z scale was incorrect, disabling possibility for transition. Transform.mercatorMatrix changed in order to produce the expected mercator matrix, even in globe (zoom 5->6) transition, and then the same compensated in globe to mercator matrix. updated satellites_custom_layer.js to show transition and satellites in mercator projection. * Lint and Flow fixes * break circular dependency, final minor fixes Co-authored-by: Aleksandar Stojiljković <aleksandar.stojiljkovic@mapbox.com>
- Loading branch information
Showing
17 changed files
with
10,559 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
function getColor(tileId) { | ||
const m = Math.pow(2, tileId.z); | ||
const s = tileId.z + tileId.x * m + tileId.y * m; | ||
const r = (Math.sin(s + 5) * 1924957) % 1; | ||
const g = (Math.sin(s + 7) * 3874133) % 1; | ||
const b = (Math.sin(s + 3) * 7662617) % 1; | ||
return [r, g, b]; | ||
}; | ||
|
||
var coloredEarthLayer = { | ||
id: 'coloredEarth', | ||
type: 'custom', | ||
|
||
onAdd: (map, gl) => { | ||
const vertexSource = ` | ||
attribute vec2 a_pos; | ||
void main() { | ||
gl_Position = vec4(a_pos, 1.0, 1.0); | ||
}`; | ||
|
||
const fragmentSource = ` | ||
precision highp float; | ||
uniform vec3 u_color; | ||
void main() { | ||
gl_FragColor = vec4(u_color, 0.5); | ||
}`; | ||
|
||
const vertexShader = gl.createShader(gl.VERTEX_SHADER); | ||
gl.shaderSource(vertexShader, vertexSource); | ||
gl.compileShader(vertexShader); | ||
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); | ||
gl.shaderSource(fragmentShader, fragmentSource); | ||
gl.compileShader(fragmentShader); | ||
|
||
this.program = gl.createProgram(); | ||
gl.attachShader(this.program, vertexShader); | ||
gl.attachShader(this.program, fragmentShader); | ||
gl.linkProgram(this.program); | ||
|
||
this.program.aPos = gl.getAttribLocation(this.program, "a_pos"); | ||
this.program.uColor = gl.getUniformLocation(this.program, "u_color"); | ||
|
||
const verts = new Float32Array([1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1]); | ||
this.vertexBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW); | ||
}, | ||
|
||
shouldRerenderTiles: () => { | ||
// return true only when frame content has changed otherwise, all the terrain | ||
// render cache would be invalidated and redrawn causing huge drop in performance. | ||
return true; | ||
}, | ||
|
||
renderToTile: (gl, tileId) => { | ||
gl.useProgram(this.program); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | ||
gl.enableVertexAttribArray(this.program.aPos); | ||
gl.vertexAttribPointer(this.program.aPos, 2, gl.FLOAT, false, 0, 0); | ||
const color = getColor(tileId); | ||
gl.uniform3f(this.program.uColor, color[0], color[1], color[2]); | ||
gl.drawArrays(gl.TRIANGLES, 0, 6); | ||
}, | ||
|
||
render: (gl, matrix) => { | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Mapbox GL JS custom layers on globe page</title> | ||
<meta charset='utf-8'> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||
<link rel='stylesheet' href='../dist/mapbox-gl.css' /> | ||
<style> | ||
body { margin: 0; padding: 0; } | ||
html, body, #map { height: 100%; } | ||
#checkboxes { | ||
position: absolute; | ||
top:0; | ||
left:0; | ||
padding:10px; | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<div id='map'></div> | ||
<div id='checkboxes'> | ||
<label><input id='immediate-checkbox' type='checkbox' checked> add a custom layer with immediate rendering</label><br /> | ||
<label><input id='draped-checkbox' type='checkbox'> add a custom layer with draped rendering</label><br /> | ||
<label><input id='repaint-checkbox' type='checkbox'> repaint. FPS: </label><label id='fps'>0</label><br /> | ||
</div> | ||
|
||
<script src="//unpkg.com/satellite.js/dist/satellite.min.js"></script> | ||
<script src="satellites-custom-layer.js"></script> | ||
|
||
<script src="color-earth-custom-layer.js"></script> | ||
|
||
<script src='../dist/mapbox-gl-dev.js'></script> | ||
<script src='../debug/access_token_generated.js'></script> | ||
|
||
<script> | ||
const map = new mapboxgl.Map({ | ||
container: 'map', | ||
style: 'mapbox://styles/mapbox/satellite-v9', | ||
center: [0, 0], | ||
zoom: 10, | ||
antialias: true, // create the gl context with MSAA antialiasing, so custom layers are antialiased | ||
hash:true, | ||
projection: 'globe' | ||
}); | ||
|
||
map.on('style.load', () => { | ||
map.addLayer(satellitesLayer); // eslint-disable-line no-undef | ||
map.addLayer(coloredEarthLayer); // eslint-disable-line no-undef | ||
|
||
document.getElementById('immediate-checkbox').onclick(); | ||
document.getElementById('draped-checkbox').onclick(); | ||
}); | ||
|
||
document.getElementById('immediate-checkbox').onclick = function() { | ||
map.setLayoutProperty('satellites', 'visibility', this.checked ? 'visible' : 'none'); | ||
}; | ||
|
||
document.getElementById('draped-checkbox').onclick = function() { | ||
map.setLayoutProperty('coloredEarth', 'visibility', this.checked ? 'visible' : 'none'); | ||
}; | ||
|
||
document.getElementById('repaint-checkbox').onclick = function() { | ||
map.repaint = !!this.checked; | ||
if (this.checked) { | ||
this['frameCounter'] = map.painter.frameCounter; | ||
this['fpsTimer'] = window.setInterval(() => { | ||
document.getElementById('fps').innerHTML = `${(map.painter.frameCounter - this.frameCounter) / 2}`; | ||
this.frameCounter = map.painter.frameCounter; | ||
}, 2000); | ||
} else { | ||
window.clearInterval(this.fpsTimer); | ||
document.getElementById('fps').innerHTML = `0`; | ||
} | ||
}; | ||
|
||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
const EARTH_RADIUS_METERS = 6371008.8; | ||
const EARTH_CIRCUMFERENCE_METERS = 2 * Math.PI * EARTH_RADIUS_METERS; | ||
const GLOBE_CIRCUMFERENCE_ECEF = 8192; | ||
const METERS_TO_ECEF = GLOBE_CIRCUMFERENCE_ECEF / EARTH_CIRCUMFERENCE_METERS; | ||
|
||
const KM_TO_M = 1000; | ||
const TIME_STEP = 3 * 1000; | ||
|
||
const globeVertCode = ` | ||
attribute vec3 a_pos_ecef; | ||
attribute vec3 a_pos_merc; | ||
uniform mat4 u_projection; | ||
uniform mat4 u_globeToMercMatrix; | ||
uniform float u_globeToMercatorTransition; | ||
void main() { | ||
vec4 p = u_projection * u_globeToMercMatrix * vec4(a_pos_ecef, 1.); | ||
p /= p.w; | ||
if (u_globeToMercatorTransition > 0.) { | ||
vec4 merc = u_projection * vec4(a_pos_merc, 1.); | ||
merc /= merc.w; | ||
p = mix(p, merc, u_globeToMercatorTransition); | ||
} | ||
gl_PointSize = 30.; | ||
gl_Position = p; | ||
} | ||
`; | ||
|
||
const mercVertCode = ` | ||
precision highp float; | ||
attribute vec3 a_pos_merc; | ||
uniform mat4 u_projection; | ||
void main() { | ||
gl_PointSize = 30.; | ||
gl_Position = u_projection * vec4(a_pos_merc, 1.); | ||
} | ||
`; | ||
|
||
const fragCode = ` | ||
precision highp float; | ||
uniform vec4 u_color; | ||
void main() { | ||
gl_FragColor = vec4(1., 0., 0., 1.); | ||
} | ||
`; | ||
|
||
let time = new Date(); | ||
|
||
function createShader(gl, src, type) { | ||
var shader = gl.createShader(type); | ||
gl.shaderSource(shader, src); | ||
gl.compileShader(shader); | ||
const message = gl.getShaderInfoLog(shader); | ||
if (message.length > 0) { | ||
console.error(message); | ||
} | ||
return shader; | ||
}; | ||
|
||
function createProgram(gl, vert, frag) { | ||
var vertShader = this.createShader(gl, vert, gl.VERTEX_SHADER); | ||
var fragShader = this.createShader(gl, frag, gl.FRAGMENT_SHADER); | ||
|
||
var program = gl.createProgram(); | ||
gl.attachShader(program, vertShader); | ||
gl.attachShader(program, fragShader); | ||
gl.linkProgram(program); | ||
gl.validateProgram(program); | ||
|
||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | ||
const info = gl.getProgramInfoLog(program); | ||
console.error(`Could not compile WebGL program. \n\n${info}`); | ||
} | ||
|
||
return program; | ||
}; | ||
|
||
function updateVboAndActivateAttrib(gl, prog, vbo, data, attribName) { | ||
gl.bindBuffer(gl.ARRAY_BUFFER, vbo); | ||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW); | ||
const attribLoc = gl.getAttribLocation(prog, attribName); | ||
gl.vertexAttribPointer(attribLoc, 3, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(attribLoc); | ||
} | ||
|
||
const satellitesLayer = { | ||
id: 'satellites', | ||
type: 'custom', | ||
onAdd (map, gl) { | ||
this.map = map; | ||
|
||
this.posEcef = []; | ||
this.posMerc = []; | ||
|
||
this.posEcefVbo = gl.createBuffer(); | ||
this.posMercVbo = gl.createBuffer(); | ||
|
||
this.globeProgram = createProgram(gl, globeVertCode, fragCode); | ||
this.mercProgram = createProgram(gl, mercVertCode, fragCode); | ||
|
||
fetch('space-track-leo.txt').then(r => r.text()).then(rawData => { | ||
const tleData = rawData.replace(/\r/g, '') | ||
.split(/\n(?=[^12])/) | ||
.filter(d => d) | ||
.map(tle => tle.split('\n')); | ||
this.satData = tleData.map(([name, ...tle]) => ({ | ||
satrec: satellite.twoline2satrec(...tle), | ||
name: name.trim().replace(/^0 /, '') | ||
})) | ||
// exclude those that can't be propagated | ||
.filter(d => !!satellite.propagate(d.satrec, new Date()).position) | ||
.slice(0, 10); | ||
|
||
this.updateBuffers(); | ||
}); | ||
}, | ||
|
||
updateBuffers() { | ||
time = new Date(+time + TIME_STEP); | ||
const gmst = satellite.gstime(time); | ||
this.posEcef = []; | ||
this.posMerc = []; | ||
for (let i = 0; i < this.satData.length; ++i) { | ||
const satrec = this.satData[i].satrec; | ||
const eci = satellite.propagate(satrec, time); | ||
if (eci.position) { | ||
const geodetic = satellite.eciToGeodetic(eci.position, gmst); | ||
|
||
const lngLat = [satellite.degreesLong(geodetic.longitude), satellite.degreesLat(geodetic.latitude)]; | ||
const altitude = geodetic.height * KM_TO_M; | ||
|
||
const merc = mapboxgl.MercatorCoordinate.fromLngLat(lngLat, altitude); | ||
const ecef = mapboxgl.LngLat.convert(lngLat).toEcef(altitude); | ||
|
||
this.posEcef.push(...ecef); | ||
this.posMerc.push(...[merc.x, merc.y, merc.z]); | ||
} | ||
} | ||
}, | ||
|
||
render (gl, projectionMatrix, projection, globeToMercMatrix, transition) { | ||
if (this.satData) { | ||
this.updateBuffers(); | ||
|
||
const primitiveCount = this.posEcef.length / 3; | ||
gl.enable(gl.DEPTH_TEST); | ||
if (projection && projection.name === 'globe') { // globe projection and globe to mercator transition | ||
gl.useProgram(this.globeProgram); | ||
|
||
updateVboAndActivateAttrib(gl, this.globeProgram, this.posEcefVbo, this.posEcef, "a_pos_ecef"); | ||
updateVboAndActivateAttrib(gl, this.globeProgram, this.posMercVbo, this.posMerc, "a_pos_merc"); | ||
|
||
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_projection"), false, projectionMatrix); | ||
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_globeToMercMatrix"), false, globeToMercMatrix); | ||
gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_globeToMercatorTransition"), transition); | ||
|
||
gl.drawArrays(gl.POINTS, 0, primitiveCount); | ||
} else { // mercator projection | ||
gl.useProgram(this.mercProgram); | ||
updateVboAndActivateAttrib(gl, this.mercProgram, this.posMercVbo, this.posMerc, "a_pos_merc"); | ||
gl.uniformMatrix4fv(gl.getUniformLocation(this.mercProgram, "u_projection"), false, projectionMatrix); | ||
gl.drawArrays(gl.POINTS, 0, primitiveCount); | ||
} | ||
} | ||
} | ||
}; |
Oops, something went wrong.