UNPKG

mapbox-gl

Version:
324 lines (277 loc) 14.9 kB
// @flow import ImageSource from '../source/image_source.js'; import StencilMode from '../gl/stencil_mode.js'; import DepthMode from '../gl/depth_mode.js'; import CullFaceMode from '../gl/cull_face_mode.js'; import Texture from './texture.js'; import {rasterPoleUniformValues, rasterUniformValues} from './program/raster_program.js'; import type Context from '../gl/context.js'; import type Painter from './painter.js'; import type SourceCache from '../source/source_cache.js'; import type RasterStyleLayer from '../style/style_layer/raster_style_layer.js'; import {OverscaledTileID, CanonicalTileID} from '../source/tile_id.js'; import rasterFade from './raster_fade.js'; import { calculateGlobeMercatorMatrix, globeNormalizeECEF, globePoleMatrixForTile, globeTileBounds, globeToMercatorTransition, GLOBE_ZOOM_THRESHOLD_MIN} from "../geo/projection/globe_util.js"; import {mat4} from "gl-matrix"; import {mercatorXfromLng, mercatorYfromLat} from "../geo/mercator_coordinate.js"; import Transform from '../geo/transform.js'; export default drawRaster; const RASTER_COLOR_TEXTURE_UNIT = 2; function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, tileIDs: Array<OverscaledTileID>, variableOffsets: any, isInitialLoad: boolean) { if (painter.renderPass !== 'translucent') return; if (layer.paint.get('raster-opacity') === 0) return; const context = painter.context; const gl = context.gl; const source = sourceCache.getSource(); const rasterConfig = configureRaster(layer, context, gl); const defines = rasterConfig.defines; const isGlobeProjection = painter.transform.projection.name === 'globe'; let drawAsGlobePole = false; if (source instanceof ImageSource && !tileIDs.length) { if (!isGlobeProjection) { return; } if (source.onNorthPole) { drawAsGlobePole = true; defines.push("GLOBE_POLES"); } else if (source.onSouthPole) { drawAsGlobePole = true; defines.push("GLOBE_POLES"); } else { // Image source without tile ID can only be rendered on the poles return; } } const emissiveStrength = layer.paint.get('raster-emissive-strength'); const colorMode = painter.colorModeForDrapableLayerRenderPass(emissiveStrength); // When rendering to texture, coordinates are already sorted: primary by // proxy id and secondary sort is by Z. const renderingToTexture = painter.terrain && painter.terrain.renderingToTexture; const renderingWithElevation = source instanceof ImageSource && layer.paint.get('raster-elevation') !== 0.0; const align = !painter.options.moving; const textureFilter = layer.paint.get('raster-resampling') === 'nearest' ? gl.NEAREST : gl.LINEAR; if (drawAsGlobePole) { const source = sourceCache.getSource(); if (!(source instanceof ImageSource)) return; const texture = source.texture; if (!texture) return; const sharedBuffers = painter.globeSharedBuffers; if (!sharedBuffers) return; const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, painter.depthRangeFor3D); const projMatrix = Float32Array.from(painter.transform.expandedFarZProjMatrix); let globeMatrix = globePoleMatrixForTile(0, 0, painter.transform); const normalizeMatrix = Float32Array.from(globeNormalizeECEF(globeTileBounds(new CanonicalTileID(0, 0, 0)))); const fade = {opacity: 1, mix: 0}; if (painter.terrain) painter.terrain.prepareDrawTile(); context.activeTexture.set(gl.TEXTURE0); texture.bind(textureFilter, gl.CLAMP_TO_EDGE); context.activeTexture.set(gl.TEXTURE1); texture.bind(textureFilter, gl.CLAMP_TO_EDGE); // Enable trilinear filtering on tiles only beyond 20 degrees pitch, // to prevent it from compromising image crispness on flat or low tilted maps. if (texture.useMipmap && context.extTextureFilterAnisotropic && painter.transform.pitch > 20) { gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } const [ northPoleBuffer, southPoleBuffer, indexBuffer, segment ] = sharedBuffers.getPoleBuffers(0, true); let vertexBuffer; if (source.onNorthPole) { vertexBuffer = northPoleBuffer; painter.renderDefaultNorthPole = false; } else { globeMatrix = mat4.scale(mat4.create(), globeMatrix, [1, -1, 1]); vertexBuffer = southPoleBuffer; painter.renderDefaultSouthPole = false; } const uniformValues = rasterPoleUniformValues(projMatrix, normalizeMatrix, globeMatrix, fade, layer, [0, 0], layer.paint.get('raster-elevation'), RASTER_COLOR_TEXTURE_UNIT, rasterConfig.mix, rasterConfig.offset, rasterConfig.range, emissiveStrength); const program = painter.getOrCreateProgram('raster', {defines: rasterConfig.defines}); painter.uploadCommonUniforms(context, program, null); program.draw( painter, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.disabled, uniformValues, layer.id, vertexBuffer, indexBuffer, segment); return; } if (!tileIDs.length) { return; } const [stencilModes, coords] = source instanceof ImageSource || renderingToTexture ? [{}, tileIDs] : painter.stencilConfigForOverlap(tileIDs); const minTileZ = coords[coords.length - 1].overscaledZ; const renderingElevatedOnGlobe = renderingWithElevation && isGlobeProjection; if (renderingElevatedOnGlobe) { rasterConfig.defines.push("PROJECTION_GLOBE_VIEW"); } if (renderingWithElevation) { rasterConfig.defines.push("RENDER_CUTOFF"); } for (const coord of coords) { const unwrappedTileID = coord.toUnwrapped(); const tile = sourceCache.getTile(coord); if (renderingToTexture && !(tile && tile.hasData())) continue; if (!tile.texture) continue; let depthMode; let projMatrix; if (renderingToTexture) { depthMode = DepthMode.disabled; projMatrix = coord.projMatrix; } else if (renderingWithElevation) { depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, painter.depthRangeFor3D); projMatrix = isGlobeProjection ? Float32Array.from(painter.transform.expandedFarZProjMatrix) : painter.transform.calculateProjMatrix(unwrappedTileID, align); } else { // Set the lower zoom level to sublayer 0, and higher zoom levels to higher sublayers // Use gl.LESS to prevent double drawing in areas where tiles overlap. depthMode = painter.depthModeForSublayer(coord.overscaledZ - minTileZ, layer.paint.get('raster-opacity') === 1 ? DepthMode.ReadWrite : DepthMode.ReadOnly, gl.LESS); projMatrix = painter.transform.calculateProjMatrix(unwrappedTileID, align); } const stencilMode = painter.terrain && renderingToTexture ? painter.terrain.stencilModeForRTTOverlap(coord) : stencilModes[coord.overscaledZ]; const rasterFadeDuration = isInitialLoad ? 0 : layer.paint.get('raster-fade-duration'); tile.registerFadeDuration(rasterFadeDuration); const parentTile = sourceCache.findLoadedParent(coord, 0); const fade = rasterFade(tile, parentTile, sourceCache, painter.transform, rasterFadeDuration); if (painter.terrain) painter.terrain.prepareDrawTile(); let parentScaleBy, parentTL; context.activeTexture.set(gl.TEXTURE0); if (tile.texture) { tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE); } context.activeTexture.set(gl.TEXTURE1); if (parentTile) { if (parentTile.texture) { parentTile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE); } parentScaleBy = Math.pow(2, parentTile.tileID.overscaledZ - tile.tileID.overscaledZ); parentTL = [tile.tileID.canonical.x * parentScaleBy % 1, tile.tileID.canonical.y * parentScaleBy % 1]; } else if (tile.texture) { tile.texture.bind(textureFilter, gl.CLAMP_TO_EDGE); } // Enable trilinear filtering on tiles only beyond 20 degrees pitch, // to prevent it from compromising image crispness on flat or low tilted maps. if (tile.texture && tile.texture.useMipmap && context.extTextureFilterAnisotropic && painter.transform.pitch > 20) { gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } const tr = painter.transform; let perspectiveTransform: [number, number]; const cutoffParams = renderingWithElevation ? cutoffParamsForElevation(tr) : [0, 0, 0, 0]; let normalizeMatrix: Float32Array; let globeMatrix: Float32Array; let globeMercatorMatrix: Float32Array; let mercatorCenter: [number, number]; let gridMatrix: Float32Array; if (renderingElevatedOnGlobe && source instanceof ImageSource && source.coordinates.length > 3) { normalizeMatrix = Float32Array.from(globeNormalizeECEF(globeTileBounds(new CanonicalTileID(0, 0, 0)))); globeMatrix = Float32Array.from(tr.globeMatrix); globeMercatorMatrix = Float32Array.from(calculateGlobeMercatorMatrix(tr)); mercatorCenter = [mercatorXfromLng(tr.center.lng), mercatorYfromLat(tr.center.lat)]; perspectiveTransform = source.elevatedGlobePerspectiveTransform; gridMatrix = source.elevatedGlobeGridMatrix || new Float32Array(9); } else { perspectiveTransform = source instanceof ImageSource ? source.perspectiveTransform : [0, 0]; normalizeMatrix = new Float32Array(16); globeMatrix = new Float32Array(9); globeMercatorMatrix = new Float32Array(16); mercatorCenter = [0, 0]; gridMatrix = new Float32Array(9); } const uniformValues = rasterUniformValues( projMatrix, normalizeMatrix, globeMatrix, globeMercatorMatrix, gridMatrix, parentTL || [0, 0], globeToMercatorTransition(painter.transform.zoom), mercatorCenter, cutoffParams, parentScaleBy || 1, fade, layer, perspectiveTransform, renderingWithElevation ? layer.paint.get('raster-elevation') : 0.0, RASTER_COLOR_TEXTURE_UNIT, rasterConfig.mix, rasterConfig.offset, rasterConfig.range, 1, 0, emissiveStrength ); const affectedByFog = painter.isTileAffectedByFog(coord); const program = painter.getOrCreateProgram('raster', {defines: rasterConfig.defines, overrideFog: affectedByFog}); painter.uploadCommonUniforms(context, program, unwrappedTileID); if (source instanceof ImageSource) { const elevatedGlobeVertexBuffer = source.elevatedGlobeVertexBuffer; const elevatedGlobeIndexBuffer = source.elevatedGlobeIndexBuffer; if (renderingToTexture || !isGlobeProjection) { if (source.boundsBuffer && source.boundsSegments) program.draw( painter, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.disabled, uniformValues, layer.id, source.boundsBuffer, painter.quadTriangleIndexBuffer, source.boundsSegments); } else if (elevatedGlobeVertexBuffer && elevatedGlobeIndexBuffer) { const segments = tr.zoom <= GLOBE_ZOOM_THRESHOLD_MIN ? source.elevatedGlobeSegments : source.getSegmentsForLongitude(tr.center.lng); if (segments) { program.draw( painter, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCW, uniformValues, layer.id, elevatedGlobeVertexBuffer, elevatedGlobeIndexBuffer, segments); program.draw( painter, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.frontCW, uniformValues, layer.id, elevatedGlobeVertexBuffer, elevatedGlobeIndexBuffer, segments); } } } else { const {tileBoundsBuffer, tileBoundsIndexBuffer, tileBoundsSegments} = painter.getTileBoundsBuffers(tile); program.draw(painter, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, uniformValues, layer.id, tileBoundsBuffer, tileBoundsIndexBuffer, tileBoundsSegments); } } painter.resetStencilClippingMasks(); } // Configure a fade out effect for elevated raster layers when they're close to the camera function cutoffParamsForElevation(tr: Transform): [number, number, number, number] { const near = tr._nearZ; const far = tr.projection.farthestPixelDistance(tr); const zRange = far - near; const fadeRangePixels = tr.height * 0.2; const cutoffDistance = near + fadeRangePixels; const relativeCutoffDistance = ((cutoffDistance - near) / zRange); const relativeCutoffFadeDistance = ((cutoffDistance - fadeRangePixels - near) / zRange); return [near, far, relativeCutoffFadeDistance, relativeCutoffDistance]; } function configureRaster(layer: RasterStyleLayer, context: Context, gl: WebGL2RenderingContext) { const isRasterColor = layer.paint.get('raster-color'); const defines = []; const inputResampling = layer.paint.get('raster-resampling'); const inputMix = layer.paint.get('raster-color-mix'); const range = layer.paint.get('raster-color-range'); // Unpack the offset for use in a separate uniform const mix = [inputMix[0], inputMix[1], inputMix[2], 0]; const offset = inputMix[3]; const resampling = inputResampling === 'nearest' ? gl.NEAREST : gl.LINEAR; if (isRasterColor) defines.push('RASTER_COLOR'); if (isRasterColor) { // Allocate a texture if not allocated context.activeTexture.set(gl.TEXTURE2); let tex = layer.colorRampTexture; if (!tex) tex = layer.colorRampTexture = new Texture(context, layer.colorRamp, gl.RGBA); tex.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } return {mix, range, offset, defines, resampling}; }