-
-
Notifications
You must be signed in to change notification settings - Fork 858
Hypsometric Tint from terrain-RGB tiles #5742
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
Changes from all commits
e8a5bba
57b9b1b
b729d3e
3fe4707
bf5e974
002cc7b
df7af32
23ff672
f0c048d
700c675
98ac2b9
b83cf42
8b1bc49
06bedd4
c15de0a
57f03a3
ab7edd3
f3acf20
77d2c1e
8883d0d
5f9dd70
d9c3cfd
82d8fe9
bb11461
e22d1b6
b085c1d
2774b1f
b98e027
0a629b4
9512079
3740a55
ae5cc80
a861180
8bcd98f
7db8b75
0b96139
ca69c39
71b0e5a
60c1bf4
dd43722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,7 @@ | |
"highp", | ||
"Hira", | ||
"Hoare", | ||
"hypsometric", | ||
"ifdef", | ||
"ifdefs", | ||
"iframes", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import {Texture} from './texture'; | ||
import type {StencilMode} from '../gl/stencil_mode'; | ||
import {DepthMode} from '../gl/depth_mode'; | ||
import {CullFaceMode} from '../gl/cull_face_mode'; | ||
import {type ColorMode} from '../gl/color_mode'; | ||
import { | ||
colorReliefUniformValues | ||
} from './program/color_relief_program'; | ||
|
||
import type {Painter, RenderOptions} from './painter'; | ||
import type {SourceCache} from '../source/source_cache'; | ||
import type {ColorReliefStyleLayer} from '../style/style_layer/color_relief_style_layer'; | ||
import type {OverscaledTileID} from '../source/tile_id'; | ||
|
||
export function drawColorRelief(painter: Painter, sourceCache: SourceCache, layer: ColorReliefStyleLayer, tileIDs: Array<OverscaledTileID>, renderOptions: RenderOptions) { | ||
if (painter.renderPass !== 'translucent') return; | ||
if (!tileIDs.length) return; | ||
|
||
const {isRenderingToTexture} = renderOptions; | ||
const projection = painter.style.projection; | ||
const useSubdivision = projection.useSubdivision; | ||
|
||
const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly); | ||
const colorMode = painter.colorModeForRenderPass(); | ||
|
||
// Globe (or any projection with subdivision) needs two-pass rendering to avoid artifacts when rendering texture tiles. | ||
// See comments in draw_raster.ts for more details. | ||
if (useSubdivision) { | ||
// Two-pass rendering | ||
const [stencilBorderless, stencilBorders, coords] = painter.stencilConfigForOverlapTwoPass(tileIDs); | ||
renderColorRelief(painter, sourceCache, layer, coords, stencilBorderless, depthMode, colorMode, false, isRenderingToTexture); // draw without borders | ||
renderColorRelief(painter, sourceCache, layer, coords, stencilBorders, depthMode, colorMode, true, isRenderingToTexture); // draw with borders | ||
} else { | ||
// Simple rendering | ||
const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs); | ||
renderColorRelief(painter, sourceCache, layer, coords, stencil, depthMode, colorMode, false, isRenderingToTexture); | ||
} | ||
} | ||
|
||
function renderColorRelief( | ||
painter: Painter, | ||
sourceCache: SourceCache, | ||
layer: ColorReliefStyleLayer, | ||
coords: Array<OverscaledTileID>, | ||
stencilModes: {[_: number]: Readonly<StencilMode>}, | ||
depthMode: Readonly<DepthMode>, | ||
colorMode: Readonly<ColorMode>, | ||
useBorder: boolean, | ||
isRenderingToTexture: boolean | ||
) { | ||
const projection = painter.style.projection; | ||
const context = painter.context; | ||
const transform = painter.transform; | ||
const gl = context.gl; | ||
const maxLength = Math.floor((gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS) - 3) / 2); | ||
const colorRampLength = layer.getColorRamp(maxLength).colorStops.length; | ||
const defines = [`#define NUM_ELEVATION_STOPS ${colorRampLength}`]; | ||
const program = painter.useProgram('colorRelief', null, false, defines); | ||
const align = !painter.options.moving; | ||
|
||
for (const coord of coords) { | ||
const tile = sourceCache.getTile(coord); | ||
const dem = tile.dem; | ||
|
||
if (!dem || !dem.data) { | ||
continue; | ||
} | ||
|
||
const textureStride = dem.stride; | ||
|
||
const pixelData = dem.getPixels(); | ||
context.activeTexture.set(gl.TEXTURE0); | ||
|
||
context.pixelStoreUnpackPremultiplyAlpha.set(false); | ||
tile.demTexture = tile.demTexture || painter.getTileTexture(textureStride); | ||
if (tile.demTexture) { | ||
const demTexture = tile.demTexture; | ||
demTexture.update(pixelData, {premultiply: false}); | ||
demTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); | ||
} else { | ||
tile.demTexture = new Texture(context, pixelData, gl.RGBA, {premultiply: false}); | ||
tile.demTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); | ||
} | ||
|
||
const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, 'raster'); | ||
|
||
const terrainData = painter.style.map.terrain?.getTerrainData(coord); | ||
|
||
const projectionData = transform.getProjectionData({ | ||
overscaledTileID: coord, | ||
aligned: align, | ||
applyGlobeMatrix: !isRenderingToTexture, | ||
applyTerrainMatrix: true | ||
}); | ||
|
||
program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW, | ||
colorReliefUniformValues(layer, tile.dem, maxLength), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { | ||
Uniform1i, | ||
Uniform1f, | ||
Uniform2f, | ||
Uniform4f, | ||
UniformFloatArray, | ||
UniformColorArray | ||
} from '../uniform_binding'; | ||
|
||
import type {Context} from '../../gl/context'; | ||
import type {UniformValues, UniformLocations} from '../uniform_binding'; | ||
import type {ColorReliefStyleLayer} from '../../style/style_layer/color_relief_style_layer'; | ||
import type {DEMData} from '../../data/dem_data'; | ||
|
||
export type ColorReliefUniformsType = { | ||
'u_image': Uniform1i; | ||
'u_unpack': Uniform4f; | ||
'u_dimension': Uniform2f; | ||
'u_elevation_stops': UniformFloatArray; | ||
'u_color_stops': UniformColorArray; | ||
'u_opacity': Uniform1f; | ||
}; | ||
|
||
const colorReliefUniforms = (context: Context, locations: UniformLocations): ColorReliefUniformsType => ({ | ||
'u_image': new Uniform1i(context, locations.u_image), | ||
'u_unpack': new Uniform4f(context, locations.u_unpack), | ||
'u_dimension': new Uniform2f(context, locations.u_dimension), | ||
'u_elevation_stops': new UniformFloatArray(context, locations.u_elevation_stops), | ||
'u_color_stops': new UniformColorArray(context, locations.u_color_stops), | ||
'u_opacity': new Uniform1f(context, locations.u_opacity) | ||
}); | ||
|
||
const colorReliefUniformValues = ( | ||
layer: ColorReliefStyleLayer, | ||
dem: DEMData, | ||
maxLength: number | ||
): UniformValues<ColorReliefUniformsType> => { | ||
|
||
const colorRamp = layer.getColorRamp(maxLength); | ||
return { | ||
'u_image': 0, | ||
'u_unpack': dem.getUnpackVector(), | ||
'u_dimension': [dem.stride, dem.stride], | ||
'u_elevation_stops': colorRamp.elevationStops, | ||
'u_color_stops': colorRamp.colorStops, | ||
'u_opacity': layer.paint.get('color-relief-opacity') | ||
}; | ||
}; | ||
|
||
export { | ||
colorReliefUniforms, | ||
colorReliefUniformValues, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
uniform sampler2D u_image; | ||
uniform vec4 u_unpack; | ||
uniform float u_elevation_stops[NUM_ELEVATION_STOPS]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any thoughts on defining an upper limit on the number of permissible stops? I suspect performance would be a problem before hitting the uniform limit, but it is technically possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The minimum required value for GL_MAX_FRAGMENT_UNIFORM_VECTORS is 16, so it does seem wise to add an upper limit. 16 is too low to be a generic limit, so I'm thinking of adding a check against GL_MAX_FRAGMENT_UNIFORM_VECTORS, and if the color ramp is too long, re-sample it and print a warning "Color relief layer may not render properly". This warning would only show up on low-end systems, so might not be evident to a developer who doesn't have access to such a system. Also, the current implementation requires the colorramp length to be 1 + a power of 2, (e.g 2, 3, 5, 9, 17, 33, 65, ...). I'll see if I can rework the binary search to avoid this requirement (which would make the actual limit 9 on a low end system GL_MAX_FRAGMENT_UNIFORM_VECTORS=16). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the existing implementation didn't require the colorramp length to be 2^N + 1, so I've removed that requirement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated to remap the color ramp if the length exceeds GL_MAX_FRAGMENT_UNIFORM_VECTORS. Note this will change the appearance of non-smooth colorramps when it happens. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the limit is indeed rather low for WebGL. For WebGL 2, following the specification should defer to ES 3.0 which specifies at least 224 vectors. In practice, both WebGL and WebGL 2 report 1024 on my desktop machines (laptops) and 256 on Snapdragon 8 Gen 2 and Snapdragon 865 Android devices. It stands to reason devices which only support the original WebGL minimum are rather dated by this point but I've been (unpleasantly) surprised before by strange hardware configurations. It may be possible to win back some uniform space for the low end by packing the colors into a 1D RGBA atlas and sending the stops as pairs of (elevation, color ramp U coordinate), trading uniform space for some extra texture samples. |
||
uniform vec4 u_color_stops[NUM_ELEVATION_STOPS]; | ||
uniform float u_opacity; | ||
|
||
in vec2 v_pos; | ||
|
||
float getElevation(vec2 coord) { | ||
// Convert encoded elevation value to meters | ||
vec4 data = texture(u_image, coord) * 255.0; | ||
data.a = -1.0; | ||
return dot(data, u_unpack); | ||
} | ||
|
||
void main() { | ||
float el = getElevation(v_pos); | ||
|
||
// Binary search | ||
int r = (NUM_ELEVATION_STOPS - 1); | ||
int l = 0; | ||
while(r - l > 1) | ||
{ | ||
int m = (r + l) / 2; | ||
if(el < u_elevation_stops[m]) | ||
{ | ||
r = m; | ||
} | ||
else | ||
{ | ||
l = m; | ||
} | ||
} | ||
fragColor = u_opacity*mix(u_color_stops[l], | ||
u_color_stops[r], | ||
clamp((el - u_elevation_stops[l])/(u_elevation_stops[r]-u_elevation_stops[l]), 0.0, 1.0)); | ||
|
||
#ifdef OVERDRAW_INSPECTOR | ||
fragColor = vec4(1.0); | ||
#endif | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
uniform vec2 u_dimension; | ||
|
||
in vec2 a_pos; | ||
|
||
out vec2 v_pos; | ||
|
||
void main() { | ||
gl_Position = projectTile(a_pos, a_pos); | ||
highp vec2 epsilon = 1.0 / u_dimension; | ||
float scale = (u_dimension.x - 2.0) / u_dimension.x; | ||
v_pos = (a_pos / 8192.0) * scale + epsilon; | ||
// North pole | ||
if (a_pos.y < -32767.5) { | ||
v_pos.y = 0.0; | ||
} | ||
// South pole | ||
if (a_pos.y > 32766.5) { | ||
v_pos.y = 1.0; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.