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"
+ }
+ ]
+}