diff --git a/debug/hillshade.html b/debug/hillshade.html index dd5ca5b4092..bea5071f649 100644 --- a/debug/hillshade.html +++ b/debug/hillshade.html @@ -21,7 +21,7 @@ top: 10px; right: 10px; border-radius: 3px; - width: 120px; + width: 140px; border: 1px solid rgba(0,0,0,0.4); font-family: 'Open Sans', sans-serif; } @@ -76,7 +76,7 @@ map.addSource('mapbox-dem', { "type": "raster-dem", "url": "mapbox://mapbox.terrain-rgb", - "tileSize": 256 + "tileSize": 256, }); map.addLayer({ "id": "Mapbox data", @@ -106,9 +106,25 @@ // where hillshading sits in the Mapbox Outdoors style }, 'waterway-river-canal-shadow'); + // Add variant of Hillshade that overzooms beyond zoom 7. + // Note: source tiles are 512, so the following only goes up to native zoom 7. + map.addSource('mapbox-dem-z8', { + "type": "raster-dem", + "url": "mapbox://mapbox.terrain-rgb", + "tileSize": 256, + "maxzoom": 8 + }); + map.addLayer({ + "id": "Mapbox data to Z8", + "source": "mapbox-dem-z8", + "type": "hillshade", + "layout": { + "visibility": "none" + } + }, 'waterway-river-canal-shadow'); }); -var toggleableLayerIds = ['Mapbox data', 'Terrarium data']; +var toggleableLayerIds = ['Mapbox data', 'Terrarium data', 'Mapbox data to Z8']; for (var i = 0; i < toggleableLayerIds.length; i++) { var id = toggleableLayerIds[i]; diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 2e342486897..25519fce4b8 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -20,7 +20,6 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return; const context = painter.context; - const sourceMaxZoom = sourceCache.getSource().maxzoom; const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly); const colorMode = painter.colorModeForRenderPass(); @@ -31,7 +30,7 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh for (const coord of coords) { const tile = sourceCache.getTile(coord); if (tile.needsHillshadePrepare && painter.renderPass === 'offscreen') { - prepareHillshade(painter, tile, layer, sourceMaxZoom, depthMode, StencilMode.disabled, colorMode); + prepareHillshade(painter, tile, layer, depthMode, StencilMode.disabled, colorMode); } else if (painter.renderPass === 'translucent') { renderHillshade(painter, tile, layer, depthMode, stencilModes[coord.overscaledZ], colorMode); } @@ -60,7 +59,7 @@ function renderHillshade(painter, tile, layer, depthMode, stencilMode, colorMode // hillshade rendering is done in two steps. the prepare step first calculates the slope of the terrain in the x and y // directions for each pixel, and saves those values to a framebuffer texture in the r and g channels. -function prepareHillshade(painter, tile, layer, sourceMaxZoom, depthMode, stencilMode, colorMode) { +function prepareHillshade(painter, tile, layer, depthMode, stencilMode, colorMode) { const context = painter.context; const gl = context.gl; const dem = tile.dem; @@ -99,7 +98,7 @@ function prepareHillshade(painter, tile, layer, sourceMaxZoom, depthMode, stenci painter.useProgram('hillshadePrepare').draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, - hillshadeUniformPrepareValues(tile.tileID, dem, sourceMaxZoom), + hillshadeUniformPrepareValues(tile.tileID, dem), layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); diff --git a/src/render/program/hillshade_program.js b/src/render/program/hillshade_program.js index 156d88292c9..b843970ff15 100644 --- a/src/render/program/hillshade_program.js +++ b/src/render/program/hillshade_program.js @@ -36,7 +36,6 @@ export type HillshadePrepareUniformsType = {| 'u_image': Uniform1i, 'u_dimension': Uniform2f, 'u_zoom': Uniform1f, - 'u_maxzoom': Uniform1f, 'u_unpack': Uniform4f |}; @@ -55,7 +54,6 @@ const hillshadePrepareUniforms = (context: Context, locations: UniformLocations) 'u_image': new Uniform1i(context, locations.u_image), 'u_dimension': new Uniform2f(context, locations.u_dimension), 'u_zoom': new Uniform1f(context, locations.u_zoom), - 'u_maxzoom': new Uniform1f(context, locations.u_maxzoom), 'u_unpack': new Uniform4f(context, locations.u_unpack) }); @@ -86,7 +84,7 @@ const hillshadeUniformValues = ( }; const hillshadeUniformPrepareValues = ( - tileID: OverscaledTileID, dem: DEMData, maxzoom: number + tileID: OverscaledTileID, dem: DEMData ): UniformValues => { const stride = dem.stride; @@ -100,7 +98,6 @@ const hillshadeUniformPrepareValues = ( 'u_image': 1, 'u_dimension': [stride, stride], 'u_zoom': tileID.overscaledZ, - 'u_maxzoom': maxzoom, 'u_unpack': dem.getUnpackVector() }; }; diff --git a/src/shaders/hillshade_prepare.fragment.glsl b/src/shaders/hillshade_prepare.fragment.glsl index 27de56d6532..71225d28120 100644 --- a/src/shaders/hillshade_prepare.fragment.glsl +++ b/src/shaders/hillshade_prepare.fragment.glsl @@ -6,7 +6,6 @@ uniform sampler2D u_image; varying vec2 v_pos; uniform vec2 u_dimension; uniform float u_zoom; -uniform float u_maxzoom; uniform vec4 u_unpack; float getElevation(vec2 coord, float bias) { @@ -44,23 +43,25 @@ void main() { float h = getElevation(v_pos + vec2(0, epsilon.y), 0.0); float i = getElevation(v_pos + vec2(epsilon.x, epsilon.y), 0.0); - // here we divide the x and y slopes by 8 * pixel size + // Here we divide the x and y slopes by 8 * pixel size // where pixel size (aka meters/pixel) is: // circumference of the world / (pixels per tile * number of tiles) // which is equivalent to: 8 * 40075016.6855785 / (512 * pow(2, u_zoom)) - // which can be reduced to: pow(2, 19.25619978527 - u_zoom) - // we want to vertically exaggerate the hillshading though, because otherwise - // it is barely noticeable at low zooms. to do this, we multiply this by some - // scale factor pow(2, (u_zoom - u_maxzoom) * a) where a is an arbitrary value - // Here we use a=0.3 which works out to the expression below. see - // nickidlugash's awesome breakdown for more info + // which can be reduced to: pow(2, 19.25619978527 - u_zoom). + // We want to vertically exaggerate the hillshading because otherwise + // it is barely noticeable at low zooms. To do this, we multiply this by + // a scale factor that is a function of zooms below 15, which is an arbitrary + // that corresponds to the max zoom level of Mapbox terrain-RGB tiles. + // See nickidlugash's awesome breakdown for more info: // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 - float exaggeration = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; + + float exaggerationFactor = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; + float exaggeration = u_zoom < 15.0 ? (u_zoom - 15.0) * exaggerationFactor : 0.0; vec2 deriv = vec2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (u_zoom - u_maxzoom) * exaggeration + 19.2562 - u_zoom); + ) / pow(2.0, exaggeration + (19.2562 - u_zoom)); gl_FragColor = clamp(vec4( deriv.x / 2.0 + 0.5, diff --git a/test/integration/render-tests/hillshade-maxzoom/default/expected.png b/test/integration/render-tests/hillshade-maxzoom/default/expected.png new file mode 100644 index 00000000000..f47d602b0be Binary files /dev/null and b/test/integration/render-tests/hillshade-maxzoom/default/expected.png differ diff --git a/test/integration/render-tests/hillshade-maxzoom/default/style.json b/test/integration/render-tests/hillshade-maxzoom/default/style.json new file mode 100644 index 00000000000..5a1f13ac7d8 --- /dev/null +++ b/test/integration/render-tests/hillshade-maxzoom/default/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies that setting a lower maxzoom on the raster-dem source produces the same output as a higher maxzoom (hillshade-accent-color/default) when the map zoom level is lower than the maxzoom (i.e., maxzoom does not alter rendering at lower zooms)" + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 12, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +} diff --git a/test/integration/render-tests/hillshade-maxzoom/overzoom/expected.png b/test/integration/render-tests/hillshade-maxzoom/overzoom/expected.png new file mode 100644 index 00000000000..0f4deae30a9 Binary files /dev/null and b/test/integration/render-tests/hillshade-maxzoom/overzoom/expected.png differ diff --git a/test/integration/render-tests/hillshade-maxzoom/overzoom/style.json b/test/integration/render-tests/hillshade-maxzoom/overzoom/style.json new file mode 100644 index 00000000000..5991188457f --- /dev/null +++ b/test/integration/render-tests/hillshade-maxzoom/overzoom/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies that setting a maximum zoom on the raster-dem source lower than the layer results in overzooming the source." + } + }, + "center": [-113.26903, 35.9654], + "zoom": 13, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 12, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +}