maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
159 lines (131 loc) • 6.61 kB
text/typescript
import {Texture} from './texture';
import {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 {
hillshadeUniformValues,
hillshadeUniformPrepareValues
} from './program/hillshade_program';
import type {Painter, RenderOptions} from './painter';
import type {SourceCache} from '../source/source_cache';
import type {HillshadeStyleLayer} from '../style/style_layer/hillshade_style_layer';
import type {OverscaledTileID} from '../source/tile_id';
export function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: HillshadeStyleLayer, tileIDs: Array<OverscaledTileID>, renderOptions: RenderOptions) {
if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return;
const {isRenderingToTexture} = renderOptions;
const context = painter.context;
const projection = painter.style.projection;
const useSubdivision = projection.useSubdivision;
const depthMode = painter.getDepthModeForSublayer(0, DepthMode.ReadOnly);
const colorMode = painter.colorModeForRenderPass();
if (painter.renderPass === 'offscreen') {
// Prepare tiles
prepareHillshade(painter, sourceCache, tileIDs, layer, depthMode, StencilMode.disabled, colorMode);
context.viewport.set([0, 0, painter.width, painter.height]);
} else if (painter.renderPass === 'translucent') {
// 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);
renderHillshade(painter, sourceCache, layer, coords, stencilBorderless, depthMode, colorMode, false, isRenderingToTexture); // draw without borders
renderHillshade(painter, sourceCache, layer, coords, stencilBorders, depthMode, colorMode, true, isRenderingToTexture); // draw with borders
} else {
// Simple rendering
const [stencil, coords] = painter.getStencilConfigForOverlapAndUpdateStencilID(tileIDs);
renderHillshade(painter, sourceCache, layer, coords, stencil, depthMode, colorMode, false, isRenderingToTexture);
}
}
}
function renderHillshade(
painter: Painter,
sourceCache: SourceCache,
layer: HillshadeStyleLayer,
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 defines = [`#define NUM_ILLUMINATION_SOURCES ${layer.paint.get('hillshade-highlight-color').values.length}`];
const program = painter.useProgram('hillshade', null, false, defines);
const align = !painter.options.moving;
for (const coord of coords) {
const tile = sourceCache.getTile(coord);
const fbo = tile.fbo;
if (!fbo) {
continue;
}
const mesh = projection.getMeshFromTileID(context, coord.canonical, useBorder, true, 'raster');
const terrainData = painter.style.map.terrain?.getTerrainData(coord);
context.activeTexture.set(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get());
const projectionData = transform.getProjectionData({
overscaledTileID: coord,
aligned: align,
applyGlobeMatrix: !isRenderingToTexture,
applyTerrainMatrix: true
});
program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.backCCW,
hillshadeUniformValues(painter, tile, layer), terrainData, projectionData, layer.id, mesh.vertexBuffer, mesh.indexBuffer, mesh.segments);
}
}
// 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: Painter,
sourceCache: SourceCache,
tileIDs: Array<OverscaledTileID>,
layer: HillshadeStyleLayer,
depthMode: Readonly<DepthMode>,
stencilMode: Readonly<StencilMode>,
colorMode: Readonly<ColorMode>) {
const context = painter.context;
const gl = context.gl;
for (const coord of tileIDs) {
const tile = sourceCache.getTile(coord);
const dem = tile.dem;
if (!dem || !dem.data) {
continue;
}
if (!tile.needsHillshadePrepare) {
continue;
}
const tileSize = dem.dim;
const textureStride = dem.stride;
const pixelData = dem.getPixels();
context.activeTexture.set(gl.TEXTURE1);
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.NEAREST, gl.CLAMP_TO_EDGE);
} else {
tile.demTexture = new Texture(context, pixelData, gl.RGBA, {premultiply: false});
tile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE);
}
context.activeTexture.set(gl.TEXTURE0);
let fbo = tile.fbo;
if (!fbo) {
const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA);
renderTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
fbo = tile.fbo = context.createFramebuffer(tileSize, tileSize, true, false);
fbo.colorAttachment.set(renderTexture.texture);
}
context.bindFramebuffer.set(fbo.framebuffer);
context.viewport.set([0, 0, tileSize, tileSize]);
painter.useProgram('hillshadePrepare').draw(context, gl.TRIANGLES,
depthMode, stencilMode, colorMode, CullFaceMode.disabled,
hillshadeUniformPrepareValues(tile.tileID, dem),
null, null, layer.id, painter.rasterBoundsBuffer,
painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments);
tile.needsHillshadePrepare = false;
}
}