diff --git a/debug/satellites-custom-layer.js b/debug/satellites-custom-layer.js index 243d1604679..9c5f20cff72 100644 --- a/debug/satellites-custom-layer.js +++ b/debug/satellites-custom-layer.js @@ -1,42 +1,13 @@ const KM_TO_M = 1000; const TIME_STEP = 3 * 1000; -const globeVertCode = ` +const vertCode = ` attribute vec3 a_pos_ecef; attribute vec3 a_pos_merc; - uniform mat4 u_projection; - uniform mat4 u_globeToMercMatrix; - uniform float u_globeToMercatorTransition; - uniform vec2 u_centerInMerc; - uniform float u_pixelsPerMeterRatio; - void main() { - vec4 p = u_projection * u_globeToMercMatrix * vec4(a_pos_ecef, 1.); - p /= p.w; - if (u_globeToMercatorTransition > 0.) { - - vec4 merc = vec4(a_pos_merc, 1.); - merc.xy = (merc.xy - u_centerInMerc) * u_pixelsPerMeterRatio + u_centerInMerc; - merc.z *= u_pixelsPerMeterRatio; - - merc = u_projection * merc; - 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.); + gl_Position = project_custom_layer(a_pos_merc, a_pos_ecef); } `; @@ -100,8 +71,7 @@ const satellitesLayer = { this.posEcefVbo = gl.createBuffer(); this.posMercVbo = gl.createBuffer(); - this.globeProgram = createProgram(gl, globeVertCode, fragCode); - this.mercProgram = createProgram(gl, mercVertCode, fragCode); + this.program = createProgram(gl, map.customLayerVertexHeader.concat(vertCode), fragCode); fetch('space-track-leo.txt').then(r => r.text()).then(rawData => { const tleData = rawData.replace(/\r/g, '') @@ -143,30 +113,22 @@ const satellitesLayer = { } }, - render (gl, projectionMatrix, projection, globeToMercMatrix, transition, centerInMercator, pixelsPerMeterRatio) { + getShaderProgram () { + return this.program; + }, + + render (gl, projectionMatrix) { if (this.satData) { this.updateBuffers(); const primitiveCount = this.posEcef.length / 3; gl.disable(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.uniform2f(gl.getUniformLocation(this.globeProgram, "u_centerInMerc"), centerInMercator[0], centerInMercator[1]); - gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_pixelsPerMeterRatio"), pixelsPerMeterRatio); - - 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); - } + gl.useProgram(this.program); + + updateVboAndActivateAttrib(gl, this.program, this.posEcefVbo, this.posEcef, "a_pos_ecef"); + updateVboAndActivateAttrib(gl, this.program, this.posMercVbo, this.posMerc, "a_pos_merc"); + + gl.drawArrays(gl.POINTS, 0, primitiveCount); } } }; \ No newline at end of file diff --git a/src/geo/transform.js b/src/geo/transform.js index c7e96e6e710..5895c85ce58 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -1654,14 +1654,12 @@ class Transform { return this.mercatorMatrix.slice(); } - globeToMercatorMatrix(): ?Array { - if (this.projection.name === 'globe') { - const pixelsToMerc = 1 / this.worldSize; - const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]); - mat4.multiply(m, m, this.globeMatrix); - return m; - } - return undefined; + globeToMercatorMatrix(): Array { + assert(this.projection.name === 'globe'); + const pixelsToMerc = 1 / this.worldSize; + const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]); + mat4.multiply(m, m, this.globeMatrix); + return m; } recenterOnTerrain() { diff --git a/src/render/draw_custom.js b/src/render/draw_custom.js index 81326e91c1d..fc43c4f3c79 100644 --- a/src/render/draw_custom.js +++ b/src/render/draw_custom.js @@ -6,7 +6,7 @@ import DepthMode from '../gl/depth_mode.js'; import StencilMode from '../gl/stencil_mode.js'; import {warnOnce} from '../util/util.js'; import {globeToMercatorTransition} from './../geo/projection/globe_util.js'; - +import {mat4} from 'gl-matrix'; import type Painter from './painter.js'; import type {OverscaledTileID} from '../source/tile_id.js'; import type SourceCache from '../source/source_cache.js'; @@ -14,6 +14,19 @@ import type CustomStyleLayer from '../style/style_layer/custom_style_layer.js'; import MercatorCoordinate from '../geo/mercator_coordinate.js'; import assert from 'assert'; +function createMercatorGlobeMatrix(projection, pixelsPerMeterRatio, centerInMerc) { + const mercToPixelMatrix = mat4.create(); + mat4.identity(mercToPixelMatrix); + + mercToPixelMatrix[0] = pixelsPerMeterRatio; + mercToPixelMatrix[5] = pixelsPerMeterRatio; + mercToPixelMatrix[10] = pixelsPerMeterRatio; + mercToPixelMatrix[12] = centerInMerc.x * (1.0 - pixelsPerMeterRatio); + mercToPixelMatrix[13] = centerInMerc.y * (1.0 - pixelsPerMeterRatio); + + return mat4.multiply([], projection, mercToPixelMatrix); +} + function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomStyleLayer, coords: Array) { const context = painter.context; @@ -32,12 +45,7 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty painter.setCustomLayerDefaults(); context.setColorMode(painter.colorModeForRenderPass()); - if (painter.transform.projection.name === "globe") { - const center = painter.transform.pointMerc; - prerender.call(implementation, context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio); - } else { - prerender.call(implementation, context.gl, painter.transform.customLayerMatrix()); - } + prerender.call(implementation, context.gl, painter.transform.customLayerMatrix()); context.setDirty(); painter.setBaseState(); @@ -76,13 +84,25 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty context.setDepthMode(depthMode); - if (painter.transform.projection.name === "globe") { - const center = painter.transform.pointMerc; - implementation.render(context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio); - } else { - implementation.render(context.gl, painter.transform.customLayerMatrix()); + const program = implementation.getShaderProgram && implementation.getShaderProgram(); + if (program) { + context.gl.useProgram(program); + layer.setUniform(context.gl, program, "u_isGlobe", +(painter.transform.projection.name === "globe")); + layer.setUniform(context.gl, program, "u_transition", globeToMercatorTransition(painter.transform.zoom)); + + if (painter.transform.projection.name === "globe") { + const center = painter.transform.pointMerc; + const globeProjection = mat4.multiply([], painter.transform.customLayerMatrix(), painter.transform.globeToMercatorMatrix()); + const mercatorProjection = createMercatorGlobeMatrix(painter.transform.customLayerMatrix(), painter.transform.pixelsPerMeterRatio, center); + layer.setUniform(context.gl, program, "u_projection", globeProjection); + layer.setUniform(context.gl, program, "u_mercatorProjection", mercatorProjection); + } else { + layer.setUniform(context.gl, program, "u_projection", painter.transform.customLayerMatrix()); + } } + implementation.render(context.gl, painter.transform.customLayerMatrix()); + context.setDirty(); painter.setBaseState(); context.bindFramebuffer.set(null); diff --git a/src/style/style_layer/custom_style_layer.js b/src/style/style_layer/custom_style_layer.js index 7dc7f4d7e19..17a127060f9 100644 --- a/src/style/style_layer/custom_style_layer.js +++ b/src/style/style_layer/custom_style_layer.js @@ -5,9 +5,10 @@ import MercatorCoordinate from '../../geo/mercator_coordinate.js'; import type Map from '../../ui/map.js'; import assert from 'assert'; import type {ValidationErrors} from '../validate_style.js'; +import {warnOnce} from '../../util/util.js'; import type {ProjectionSpecification} from '../../style-spec/types.js'; -type CustomRenderMethod = (gl: WebGLRenderingContext, matrix: Array, projection: ?ProjectionSpecification, projectionToMercatorMatrix: ?Array, projectionToMercatorTransition: ?number, centerInMercator: ?Array, pixelsPerMeterRatio: ?number) => void; +type CustomRenderMethod = (gl: WebGLRenderingContext, matrix: Array) => void; /** * Interface for custom style layers. This is a specification for @@ -155,6 +156,7 @@ export type CustomLayerInterface = { type: "custom", renderingMode: "2d" | "3d", render: CustomRenderMethod, + getShaderProgram: ?(projection: ?ProjectionSpecification) => ?WebGLProgram, prerender: ?CustomRenderMethod, renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void, shouldRerenderTiles: ?() => boolean, @@ -189,13 +191,57 @@ export function validateCustomStyleLayer(layerObject: CustomLayerInterface): Val return errors; } +export function customLayerVertexHeader(): string { + return ` + uniform mat4 u_projection; + uniform mat4 u_mercatorProjection; + uniform float u_isGlobe; + uniform float u_transition; + + vec4 project_custom_layer(vec3 pos_merc, vec3 pos_ecef) { + if (u_isGlobe == 1.0) { + vec4 projected_pos = u_projection * vec4(pos_ecef, 1.0); + projected_pos /= projected_pos.w; + + if (u_transition > 0.0) { + vec4 mercator = u_mercatorProjection * vec4(pos_merc, 1.0); + mercator /= mercator.w; + projected_pos = mix(projected_pos, mercator, u_transition); + } + + return projected_pos; + } else { + return u_projection * vec4(pos_merc, 1.0); + } + } + `; +} + class CustomStyleLayer extends StyleLayer { implementation: CustomLayerInterface; + uniformLocation: {string: WebGLUniformLocation}; + constructor(implementation: CustomLayerInterface) { super(implementation, {}); this.implementation = implementation; + this.uniformLocation = {}; + } + + setUniform(gl: WebGLRenderingContext, program: WebGLProgram, name: string, value: number | Array) { + this.uniformLocation[name] = this.uniformLocation[name] || gl.getUniformLocation(program, name); + if (this.uniformLocation[name]) { + if (Array.isArray(value)) { + gl.uniformMatrix4fv(this.uniformLocation[name], false, value); + } else if (typeof value === 'number') { + gl.uniform1f(this.uniformLocation[name], value); + } else { + assert(false, "Unimplemented custom uniform type"); + } + } else { + warnOnce(`Layer "${this.id}" is missing uniform location "${name}", make sure to include shader prelude with map.customLayerVertexHeader as part of your custom layer shader source`); + } } is3D(): boolean { diff --git a/src/ui/map.js b/src/ui/map.js index 8d642373f7b..cd2760f57bf 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -34,6 +34,7 @@ import SourceCache from '../source/source_cache.js'; import {GLOBE_ZOOM_THRESHOLD_MAX} from '../geo/projection/globe_util.js'; import {setCacheLimits} from '../util/tile_request_cache.js'; import {Debug} from '../util/debug.js'; +import {customLayerVertexHeader} from '../style/style_layer/custom_style_layer.js'; import type {PointLike} from '@mapbox/point-geometry'; import type {RequestTransformFunction} from '../util/mapbox.js'; @@ -3764,6 +3765,10 @@ class Map extends Camera { */ get version(): string { return version; } + + get customLayerVertexHeader(): string { + return customLayerVertexHeader(); + } } export default Map;