From a67fb26ea9a3c851cb9bca22cd83fb88d6d0b2d0 Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Fri, 7 Oct 2022 10:31:08 +0200 Subject: [PATCH 1/7] basic sky implementation --- build/generate-style-spec.ts | 28 +++--- src/render/draw_sky.ts | 24 ++++++ src/render/painter.ts | 3 + src/render/program/program_uniforms.ts | 4 +- src/render/program/sky_program.ts | 28 ++++++ src/shaders/shaders.ts | 5 +- src/shaders/sky.fragment.glsl | 20 +++++ src/shaders/sky.vertex.glsl | 5 ++ src/style-spec/reference/v8.json | 65 ++++++++++++++ src/style-spec/validate/validate.ts | 2 + src/style-spec/validate/validate_sky.ts | 34 ++++++++ src/style-spec/validate_style.min.ts | 2 + src/style/sky.ts | 108 ++++++++++++++++++++++++ src/style/style.ts | 36 ++++++++ src/style/validate_style.ts | 2 + src/ui/map.ts | 27 +++++- 16 files changed, 378 insertions(+), 15 deletions(-) create mode 100644 src/render/draw_sky.ts create mode 100644 src/render/program/sky_program.ts create mode 100644 src/shaders/sky.fragment.glsl create mode 100644 src/shaders/sky.vertex.glsl create mode 100644 src/style-spec/validate/validate_sky.ts create mode 100644 src/style/sky.ts diff --git a/build/generate-style-spec.ts b/build/generate-style-spec.ts index 72f3f502e8..099b456266 100644 --- a/build/generate-style-spec.ts +++ b/build/generate-style-spec.ts @@ -34,6 +34,8 @@ function propertyType(property) { } case 'light': return 'LightSpecification'; + case 'sky': + return 'SkySpecification'; case 'sources': return '{[_: string]: SourceSpecification}'; case '*': @@ -130,19 +132,19 @@ export type PromoteIdSpecification = {[_: string]: string} | string; export type ExpressionInputType = string | number | boolean; -export type CollatorExpressionSpecification = +export type CollatorExpressionSpecification = ['collator', { - 'case-sensitive'?: boolean | ExpressionSpecification, - 'diacritic-sensitive'?: boolean | ExpressionSpecification, + 'case-sensitive'?: boolean | ExpressionSpecification, + 'diacritic-sensitive'?: boolean | ExpressionSpecification, locale?: string | ExpressionSpecification} ]; // collator export type InterpolationSpecification = - | ['linear'] - | ['exponential', number | ExpressionSpecification] + | ['linear'] + | ['exponential', number | ExpressionSpecification] | ['cubic-bezier', number | ExpressionSpecification, number | ExpressionSpecification, number | ExpressionSpecification, number | ExpressionSpecification] -export type ExpressionSpecification = +export type ExpressionSpecification = // types | ['array', unknown | ExpressionSpecification] // array | ['array', ExpressionInputType | ExpressionSpecification, unknown | ExpressionSpecification] // array @@ -185,20 +187,20 @@ export type ExpressionSpecification = | ['>=', ExpressionInputType | ExpressionSpecification, ExpressionInputType | ExpressionSpecification, CollatorExpressionSpecification?] // boolean | ['all', ...(boolean | ExpressionSpecification)[]] // boolean | ['any', ...(boolean | ExpressionSpecification)[]] // boolean - | ['case', boolean | ExpressionSpecification, ExpressionInputType | ExpressionSpecification, + | ['case', boolean | ExpressionSpecification, ExpressionInputType | ExpressionSpecification, ...(boolean | ExpressionInputType | ExpressionSpecification)[], ExpressionInputType | ExpressionSpecification] | ['coalesce', ...(ExpressionInputType | ExpressionSpecification)[]] // at least two inputs required - | ['match', ExpressionInputType | ExpressionSpecification, - ExpressionInputType | ExpressionInputType[], ExpressionInputType | ExpressionSpecification, + | ['match', ExpressionInputType | ExpressionSpecification, + ExpressionInputType | ExpressionInputType[], ExpressionInputType | ExpressionSpecification, ...(ExpressionInputType | ExpressionInputType[] | ExpressionSpecification)[], // repeated as above ExpressionInputType | ExpressionSpecification] | ['within', unknown | ExpressionSpecification] // Ramps, scales, curves - | ['interpolate', InterpolationSpecification, number | ExpressionSpecification, + | ['interpolate', InterpolationSpecification, number | ExpressionSpecification, ...(number | number[] | ColorSpecification)[]] // alternating number and number | number[] | ColorSpecification - | ['interpolate-hcl', InterpolationSpecification, number | ExpressionSpecification, + | ['interpolate-hcl', InterpolationSpecification, number | ExpressionSpecification, ...(number | ColorSpecification)[]] // alternating number and ColorSpecificaton - | ['interpolate-lab', InterpolationSpecification, number | ExpressionSpecification, + | ['interpolate-lab', InterpolationSpecification, number | ExpressionSpecification, ...(number | ColorSpecification)[]] // alternating number and ColorSpecification | ['step', number | ExpressionSpecification, ExpressionInputType | ExpressionSpecification, ...(number | ExpressionInputType | ExpressionSpecification)[]] // alternating number and ExpressionInputType | ExpressionSpecification @@ -308,6 +310,8 @@ ${objectDeclaration('StyleSpecification', spec.$root)} ${objectDeclaration('LightSpecification', spec.light)} +${objectDeclaration('SkySpecification', spec.sky)} + ${objectDeclaration('TerrainSpecification', spec.terrain)} ${spec.source.map(key => objectDeclaration(sourceTypeName(key), spec[key])).join('\n\n')} diff --git a/src/render/draw_sky.ts b/src/render/draw_sky.ts new file mode 100644 index 0000000000..04f7f2289e --- /dev/null +++ b/src/render/draw_sky.ts @@ -0,0 +1,24 @@ +import StencilMode from '../gl/stencil_mode'; +import DepthMode from '../gl/depth_mode'; +import CullFaceMode from '../gl/cull_face_mode'; +import {skyUniformValues} from './program/sky_program'; +import type Painter from './painter'; +import Sky from '../style/sky'; + +export default drawSky; + +function drawSky(painter: Painter, sky: Sky) { + const context = painter.context; + const gl = context.gl; + + const skyUniforms = skyUniformValues(sky, painter.style.map.transform, painter.pixelRatio); + + const depthMode = new DepthMode(gl.LEQUAL, DepthMode.ReadWrite, [0, 1]); + const stencilMode = StencilMode.disabled; + const colorMode = painter.colorModeForRenderPass(); + const program = painter.useProgram('sky'); + + program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, + CullFaceMode.disabled, skyUniforms, undefined, 'sky', sky.vertexBuffer, + sky.indexBuffer, sky.segments); +} diff --git a/src/render/painter.ts b/src/render/painter.ts index b715f2c252..d476828d31 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -62,6 +62,7 @@ import type {DepthRangeType, DepthMaskType, DepthFuncType} from '../gl/types'; import type ResolvedImage from '../style-spec/expression/types/resolved_image'; import type {RGBAImage} from '../util/image'; import RenderToTexture from './render_to_texture'; +import drawSky from './draw_sky'; export type RenderPass = 'offscreen' | 'opaque' | 'translucent'; @@ -483,6 +484,8 @@ class Painter { this.renderLayer(this, sourceCache, layer, coords); } + if (this.style.sky) drawSky(this, this.style.sky); + if (this.options.showTileBoundaries) { const selectedSource = selectDebugSource(this.style, this.transform.zoom); if (selectedSource) { diff --git a/src/render/program/program_uniforms.ts b/src/render/program/program_uniforms.ts index f1723ccac7..a080bb9578 100644 --- a/src/render/program/program_uniforms.ts +++ b/src/render/program/program_uniforms.ts @@ -11,6 +11,7 @@ import {rasterUniforms} from './raster_program'; import {symbolIconUniforms, symbolSDFUniforms, symbolTextAndIconUniforms} from './symbol_program'; import {backgroundUniforms, backgroundPatternUniforms} from './background_program'; import {terrainUniforms, terrainDepthUniforms, terrainCoordsUniforms} from './terrain_program'; +import {skyUniforms} from './sky_program'; export const programUniforms = { fillExtrusion: fillExtrusionUniforms, @@ -40,5 +41,6 @@ export const programUniforms = { backgroundPattern: backgroundPatternUniforms, terrain: terrainUniforms, terrainDepth: terrainDepthUniforms, - terrainCoords: terrainCoordsUniforms + terrainCoords: terrainCoordsUniforms, + sky: skyUniforms }; diff --git a/src/render/program/sky_program.ts b/src/render/program/sky_program.ts new file mode 100644 index 0000000000..82122db10f --- /dev/null +++ b/src/render/program/sky_program.ts @@ -0,0 +1,28 @@ +import {UniformColor, Uniform1f} from '../uniform_binding'; +import type Context from '../../gl/context'; +import type {UniformValues, UniformLocations} from '../uniform_binding'; +import Transform from '../../geo/transform'; +import Sky from '../../style/sky'; + +export type SkyUniformsType = { + 'u_sky_color': UniformColor; + 'u_fog_color': UniformColor; + 'u_horizon': Uniform1f; + 'u_horizon_blend': Uniform1f; +}; + +const skyUniforms = (context: Context, locations: UniformLocations): SkyUniformsType => ({ + 'u_sky_color': new UniformColor(context, locations.u_sky_color), + 'u_fog_color': new UniformColor(context, locations.u_fog_color), + 'u_horizon': new Uniform1f(context, locations.u_horizon), + 'u_horizon_blend': new Uniform1f(context, locations.u_horizon_blend) +}); + +const skyUniformValues = (sky: Sky, transform: Transform, pixelRatio: number): UniformValues => ({ + 'u_sky_color': sky.properties.get('sky-color'), + 'u_fog_color': sky.properties.get('fog-color'), + 'u_horizon': (transform.height / 2 + transform.getHorizon()) * pixelRatio, + 'u_horizon_blend': (sky.properties.get('horizon-blend') * transform.height / 2) * pixelRatio +}); + +export {skyUniforms, skyUniformValues}; diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index 1adfea6d19..67beddc7dc 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -57,6 +57,8 @@ import terrainDepthFrag from './terrain_depth.fragment.glsl.g'; import terrainCoordsFrag from './terrain_coords.fragment.glsl.g'; import terrainFrag from './terrain.fragment.glsl.g'; import terrainVert from './terrain.vertex.glsl.g'; +import skyFrag from './sky.fragment.glsl.g'; +import skyVert from './sky.vertex.glsl.g'; const shaders = { prelude: compile(preludeFrag, preludeVert), @@ -87,7 +89,8 @@ const shaders = { symbolTextAndIcon: compile(symbolTextAndIconFrag, symbolTextAndIconVert), terrain: compile(terrainFrag, terrainVert), terrainDepth: compile(terrainDepthFrag, terrainVert), - terrainCoords: compile(terrainCoordsFrag, terrainVert) + terrainCoords: compile(terrainCoordsFrag, terrainVert), + sky: compile(skyFrag, skyVert) }; export default shaders; diff --git a/src/shaders/sky.fragment.glsl b/src/shaders/sky.fragment.glsl new file mode 100644 index 0000000000..4a443f1eaa --- /dev/null +++ b/src/shaders/sky.fragment.glsl @@ -0,0 +1,20 @@ + + +uniform vec4 u_sky_color; +uniform vec4 u_fog_color; +uniform float u_horizon; +uniform float u_horizon_blend; + +void main() { + float y = gl_FragCoord.y; + if (y > u_horizon) { + float blend = y - u_horizon; + if (blend < u_horizon_blend) { + gl_FragColor = mix(u_fog_color, u_sky_color, blend / u_horizon_blend); + } else { + gl_FragColor = u_sky_color; + } + } else { + gl_FragColor = u_fog_color; + } +} diff --git a/src/shaders/sky.vertex.glsl b/src/shaders/sky.vertex.glsl new file mode 100644 index 0000000000..6f90a3591f --- /dev/null +++ b/src/shaders/sky.vertex.glsl @@ -0,0 +1,5 @@ +attribute vec2 a_pos; + +void main() { + gl_Position = vec4(a_pos, 0.99999, 1.0); +} diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 3dc26aa448..293ed4d7ce 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -57,6 +57,13 @@ "intensity": 0.4 } }, + "sky": { + "type": "sky", + "doc": "Tho map sky.", + "example": { + "sky-color": "#0000ff" + } + }, "terrain": { "type": "terrain", "doc": "The terrain configuration.", @@ -3822,6 +3829,64 @@ } } }, + "sky": { + "sky-color": { + "type": "color", + "property-type": "data-constant", + "default": "#ddf4ff", + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "transition": true, + "doc": "Base color for sky.", + "sdk-support": { + "basic functionality": { + "js": "3.0.0" + } + } + }, + "fog-color": { + "type": "color", + "property-type": "data-constant", + "default": "#ffffff", + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "transition": true, + "doc": "Base color for fog.", + "sdk-support": { + "basic functionality": { + "js": "3.0.0" + } + } + }, + "horizon-blend": { + "type": "number", + "property-type": "data-constant", + "default": 0.3, + "minimum": 0, + "maximum": 1, + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "transition": true, + "doc": "Blend sky and fog color on horizon", + "sdk-support": { + "basic functionality": { + "js": "3.0.0" + } + } + } + }, "terrain": { "source": { "type": "string", diff --git a/src/style-spec/validate/validate.ts b/src/style-spec/validate/validate.ts index 4dc2dc02ac..1b9e6f5862 100644 --- a/src/style-spec/validate/validate.ts +++ b/src/style-spec/validate/validate.ts @@ -17,6 +17,7 @@ import validateFilter from './validate_filter'; import validateLayer from './validate_layer'; import validateSource from './validate_source'; import validateLight from './validate_light'; +import validateSky from './validate_sky'; import validateTerrain from './validate_terrain'; import validateString from './validate_string'; import validateFormatted from './validate_formatted'; @@ -39,6 +40,7 @@ const VALIDATORS = { 'object': validateObject, 'source': validateSource, 'light': validateLight, + 'sky': validateSky, 'terrain': validateTerrain, 'string': validateString, 'formatted': validateFormatted, diff --git a/src/style-spec/validate/validate_sky.ts b/src/style-spec/validate/validate_sky.ts new file mode 100644 index 0000000000..58d2a34caf --- /dev/null +++ b/src/style-spec/validate/validate_sky.ts @@ -0,0 +1,34 @@ +import ValidationError from '../error/validation_error'; +import getType from '../util/get_type'; +import validate from './validate'; + +export default function validateSky(options) { + const sky = options.value; + const styleSpec = options.styleSpec; + const skySpec = styleSpec.sky; + const style = options.style; + + const rootType = getType(sky); + if (sky === undefined) { + return []; + } else if (rootType !== 'object') { + return [new ValidationError('sky', sky, `object expected, ${rootType} found`)]; + } + + let errors = []; + for (const key in sky) { + if (skySpec[key]) { + errors = errors.concat(validate({ + key, + value: sky[key], + valueSpec: skySpec[key], + style, + styleSpec + })); + } else { + errors = errors.concat([new ValidationError(key, sky[key], `unknown property "${key}"`)]); + } + } + + return errors; +} diff --git a/src/style-spec/validate_style.min.ts b/src/style-spec/validate_style.min.ts index 2ff408a60c..c1c3359563 100644 --- a/src/style-spec/validate_style.min.ts +++ b/src/style-spec/validate_style.min.ts @@ -6,6 +6,7 @@ import validateGlyphsURL from './validate/validate_glyphs_url'; import validateSource from './validate/validate_source'; import validateLight from './validate/validate_light'; +import validateSky from './validate/validate_sky'; import validateTerrain from './validate/validate_terrain'; import validateLayer from './validate/validate_layer'; import validateFilter from './validate/validate_filter'; @@ -60,6 +61,7 @@ function validateStyleMin(style: StyleSpecification, styleSpec = latestStyleSpec validateStyleMin.source = wrapCleanErrors(validateSource); validateStyleMin.light = wrapCleanErrors(validateLight); +validateStyleMin.sky = wrapCleanErrors(validateSky); validateStyleMin.terrain = wrapCleanErrors(validateTerrain); validateStyleMin.layer = wrapCleanErrors(validateLayer); validateStyleMin.filter = wrapCleanErrors(validateFilter); diff --git a/src/style/sky.ts b/src/style/sky.ts new file mode 100644 index 0000000000..defbc144bd --- /dev/null +++ b/src/style/sky.ts @@ -0,0 +1,108 @@ +import {PosArray, TriangleIndexArray} from '../data/array_types.g'; +import posAttributes from '../data/pos_attributes'; +import Style, {StyleSetterOptions} from './style'; +import VertexBuffer from '../gl/vertex_buffer'; +import IndexBuffer from '../gl/index_buffer'; +import SegmentVector from '../data/segment'; +import Context from '../gl/context'; +import {SkySpecification} from '../style-spec/types.g'; +import {Color, StylePropertySpecification} from '../style-spec/style-spec'; +import {DataConstantProperty, PossiblyEvaluated, Properties, Transitionable, Transitioning, TransitionParameters} from './properties'; +import styleSpec from '../style-spec/reference/latest'; +import {Evented} from '../util/evented'; +import validateSky from '../style-spec/validate/validate_sky'; +import EvaluationParameters from './evaluation_parameters'; +import {emitValidationErrors, validateStyle} from './validate_style'; +import {extend} from '../util/util'; + +type Props = { + 'sky-color': DataConstantProperty; + 'fog-color': DataConstantProperty; + 'horizon-blend': DataConstantProperty; +}; + +type PropsPossiblyEvaluated = { + 'sky-color': Color; + 'fog-color': Color; + 'horizon-blend': number; +}; + +const properties: Properties = new Properties({ + 'sky-color': new DataConstantProperty(styleSpec.sky['sky-color'] as StylePropertySpecification), + 'fog-color': new DataConstantProperty(styleSpec.sky['fog-color'] as StylePropertySpecification), + 'horizon-blend': new DataConstantProperty(styleSpec.sky['horizon-blend'] as StylePropertySpecification), +}); + +export default class Sky extends Evented { + style: Style; + properties: PossiblyEvaluated; + + _transitionable: Transitionable; + _transitioning: Transitioning; + + vertexBuffer: VertexBuffer; + indexBuffer: IndexBuffer; + segments: SegmentVector; + + constructor(context :Context, sky?: SkySpecification) { + super(); + this._transitionable = new Transitionable(properties); + this.setSky(sky); + this._transitioning = this._transitionable.untransitioned(); + + const vertexArray = new PosArray(); + vertexArray.emplaceBack(-1, -1); + vertexArray.emplaceBack(1, -1); + vertexArray.emplaceBack(1, 1); + vertexArray.emplaceBack(-1, 1); + + const indexArray = new TriangleIndexArray(); + indexArray.emplaceBack(0, 1, 2); + indexArray.emplaceBack(0, 2, 3); + + this.vertexBuffer = context.createVertexBuffer(vertexArray, posAttributes.members); + this.indexBuffer = context.createIndexBuffer(indexArray); + this.segments = SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length); + } + + setSky(sky?: SkySpecification, options: StyleSetterOptions = {}) { + if (this._validate(validateSky, sky, options)) { + return; + } + + for (const name in sky) { + this._transitionable.setValue(name as keyof Props, sky[name]); + } + } + + getSky() { + return this._transitionable.serialize(); + } + + updateTransitions(parameters: TransitionParameters) { + this._transitioning = this._transitionable.transitioned(parameters, this._transitioning); + } + + hasTransition() { + return this._transitioning.hasTransition(); + } + + recalculate(parameters: EvaluationParameters) { + this.properties = this._transitioning.possiblyEvaluate(parameters); + } + + _validate(validate: Function, value: unknown, options?: { + validate?: boolean; + }) { + if (options && options.validate === false) { + return false; + } + + return emitValidationErrors(this, validate.call(validateStyle, extend({ + value, + // Workaround for https://github.com/mapbox/mapbox-gl-js/issues/2407 + style: {glyphs: true, sprite: true}, + styleSpec + }))); + } +} diff --git a/src/style/style.ts b/src/style/style.ts index b464a2cf7b..cf103be94b 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -56,10 +56,12 @@ import type { StyleSpecification, LightSpecification, SourceSpecification, + SkySpecification, } from '../style-spec/types.g'; import type {CustomLayerInterface} from './style_layer/custom_style_layer'; import type {Validator} from './validate_style'; import type {OverscaledTileID} from '../source/tile_id'; +import Sky from './sky'; const supportedDiffOperations = pick(diffOperations, [ 'addLayer', @@ -160,6 +162,7 @@ class Style extends Evented { glyphManager: GlyphManager; lineAtlas: LineAtlas; light: Light; + sky: Sky; _request: Cancelable; _spriteRequest: Cancelable; @@ -325,6 +328,8 @@ class Style extends Evented { this.light = new Light(this.stylesheet.light); + this.sky = new Sky(this.map.painter.context, this.stylesheet.sky); + this.map.setTerrain(this.stylesheet.terrain); this.fire(new Event('data', {dataType: 'style'})); @@ -460,6 +465,7 @@ class Style extends Evented { } this.light.updateTransitions(parameters); + this.sky.updateTransitions(parameters); this._resetUpdates(); } @@ -489,6 +495,7 @@ class Style extends Evented { } this.light.recalculate(parameters); + this.sky.recalculate(parameters); this.z = parameters.zoom; if (changed) { @@ -1262,6 +1269,35 @@ class Style extends Evented { this.light.updateTransitions(parameters); } + getSky() { + return this.sky.getSky(); + } + + setSky(skyOptions: SkySpecification, options: StyleSetterOptions = {}) { + this._checkLoaded(); + + const sky = this.sky.getSky(); + let _update = false; + for (const key in skyOptions) { + if (!deepEqual(skyOptions[key], sky[key])) { + _update = true; + break; + } + } + if (!_update) return; + + const parameters = { + now: browser.now(), + transition: extend({ + duration: 300, + delay: 0 + }, this.stylesheet.transition) + }; + + this.sky.setSky(skyOptions, options); + this.sky.updateTransitions(parameters); + } + _validate(validate: Validator, key: string, value: any, props: any, options: { validate?: boolean; } = {}) { diff --git a/src/style/validate_style.ts b/src/style/validate_style.ts index 81efa18fe1..288de79355 100644 --- a/src/style/validate_style.ts +++ b/src/style/validate_style.ts @@ -15,6 +15,7 @@ type ValidateStyle = { source: Validator; layer: Validator; light: Validator; + sky: Validator; terrain: Validator; filter: Validator; paintProperty: Validator; @@ -26,6 +27,7 @@ export const validateStyle = (validateStyleMin as ValidateStyle); export const validateSource = validateStyle.source; export const validateLight = validateStyle.light; +export const validateSky = validateStyle.sky; export const validateTerrain = validateStyle.terrain; export const validateFilter = validateStyle.filter; export const validatePaintProperty = validateStyle.paintProperty; diff --git a/src/ui/map.ts b/src/ui/map.ts index 67352aaf91..9cc55f7a3c 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -53,7 +53,8 @@ import type { StyleSpecification, LightSpecification, SourceSpecification, - TerrainSpecification + TerrainSpecification, + SkySpecification } from '../style-spec/types.g'; import {Callback} from '../types/callback'; import type {ControlPosition, IControl} from './control/control'; @@ -2301,6 +2302,30 @@ class Map extends Camera { return this.style.getLight(); } + /** + * Loads a Sky onto the map. + * @param sky Sky properties to set. Must conform to the [MapLibre Style Specification](https://maplibre.org/maplibre-gl-js-docs/style-spec/#sky). + * @param {Object} [options] Options object. + * @param {boolean} [options.validate=true] Whether to check if the filter conforms to the MapLibre GL Style Specification. Disabling validation is a performance optimization that should only be used if you have previously validated the values you will be passing to this function. + * @returns {Map} `this` + * @example + * map.setSky({ 'sky-color': '#00f' }); + */ + setSky(sky: SkySpecification, options: StyleSetterOptions = {}) { + this._lazyInitEmptyStyle(); + this.style.setSky(sky, options); + return this._update(true); + } + + /** + * Returns the value of the sky object. + * + * @returns {Object} sky Sky properties of the style. + */ + getSky() { + return this.style.getSky(); + } + // eslint-disable-next-line jsdoc/require-returns /** * Sets the `state` of a feature. From e861e59cc6aea260d024d9da6205b0f759a79640 Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Sun, 9 Oct 2022 13:52:30 +0200 Subject: [PATCH 2/7] implement basic fog, pitch -fadeout logic is missing --- src/geo/transform.ts | 56 +++++++++++++++++++++---- src/render/draw_terrain.ts | 4 +- src/render/program/terrain_program.ts | 21 ++++++++-- src/shaders/_prelude.vertex.glsl | 6 +-- src/shaders/terrain.fragment.glsl | 11 ++++- src/shaders/terrain.vertex.glsl | 7 +++- src/shaders/terrain_coords.vertex.glsl | 10 +++++ src/shaders/terrain_depth.fragment.glsl | 6 +-- src/shaders/terrain_depth.vertex.glsl | 10 +++++ src/style-spec/reference/v8.json | 20 +++++++++ src/style/sky.ts | 6 ++- 11 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 src/shaders/terrain_coords.vertex.glsl create mode 100644 src/shaders/terrain_depth.vertex.glsl diff --git a/src/geo/transform.ts b/src/geo/transform.ts index c1602bea9b..466fafaf70 100644 --- a/src/geo/transform.ts +++ b/src/geo/transform.ts @@ -35,6 +35,7 @@ class Transform { cameraToSeaLevelDistance: number; mercatorMatrix: mat4; projMatrix: mat4; + fogMatrix: mat4; invProjMatrix: mat4; alignedProjMatrix: mat4; pixelMatrix: mat4; @@ -59,6 +60,7 @@ class Transform { _constraining: boolean; _posMatrixCache: {[_: string]: mat4}; _alignedPosMatrixCache: {[_: string]: mat4}; + _fogMatrixCache: {[_: string]: mat4}; constructor(minZoom?: number, maxZoom?: number, minPitch?: number, maxPitch?: number, renderWorldCopies?: boolean) { this.tileSize = 512; // constant @@ -86,6 +88,7 @@ class Transform { this._edgeInsets = new EdgeInsets(); this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; + this._fogMatrixCache = {}; } clone(): Transform { @@ -712,6 +715,17 @@ class Transform { } } + calculateTileMatrix(unwrappedTileID: UnwrappedTileID): mat4 { + const canonical = unwrappedTileID.canonical; + const scale = this.worldSize / this.zoomScale(canonical.z); + const unwrappedX = canonical.x + Math.pow(2, canonical.z) * unwrappedTileID.wrap; + + const worldMatrix = mat4.identity(new Float64Array(16) as any); + mat4.translate(worldMatrix, worldMatrix, [unwrappedX * scale, canonical.y * scale, 0]); + mat4.scale(worldMatrix, worldMatrix, [scale / EXTENT, scale / EXTENT, 1]); + return worldMatrix; + } + /** * Calculate the posMatrix that, given a tile coordinate, would be used to display the tile on a map. * @param {UnwrappedTileID} unwrappedTileID; @@ -724,19 +738,32 @@ class Transform { return cache[posMatrixKey]; } - const canonical = unwrappedTileID.canonical; - const scale = this.worldSize / this.zoomScale(canonical.z); - const unwrappedX = canonical.x + Math.pow(2, canonical.z) * unwrappedTileID.wrap; - - const posMatrix = mat4.identity(new Float64Array(16) as any); - mat4.translate(posMatrix, posMatrix, [unwrappedX * scale, canonical.y * scale, 0]); - mat4.scale(posMatrix, posMatrix, [scale / EXTENT, scale / EXTENT, 1]); + const posMatrix = this.calculateTileMatrix(unwrappedTileID); mat4.multiply(posMatrix, aligned ? this.alignedProjMatrix : this.projMatrix, posMatrix); cache[posMatrixKey] = new Float32Array(posMatrix); return cache[posMatrixKey]; } + /** + * Calculate the fogMatrix that, given a tile coordinate, would be used to calculate fog on the map. + * @param {UnwrappedTileID} unwrappedTileID; + * @private + */ + calculateFogMatrix(unwrappedTileID: UnwrappedTileID): mat4 { + const posMatrixKey = unwrappedTileID.key; + const cache = this._fogMatrixCache; + if (cache[posMatrixKey]) { + return cache[posMatrixKey]; + } + + const fogMatrix = this.calculateTileMatrix(unwrappedTileID); + mat4.multiply(fogMatrix, this.fogMatrix, fogMatrix); + + cache[posMatrixKey] = new Float32Array(fogMatrix); + return cache[posMatrixKey]; + } + customLayerMatrix(): mat4 { return this.mercatorMatrix.slice() as any; } @@ -905,6 +932,20 @@ class Transform { this.projMatrix = m; this.invProjMatrix = mat4.invert([] as any, m); + // create a fog matrix, same es proj-matrix but with near clipping-plane in mapcenter + // needed to calculate a correct z-value for fog calculation, because projMatrix z value is not + this.fogMatrix = new Float64Array(16) as any; + mat4.perspective(this.fogMatrix, this._fov, this.width / this.height, this.cameraToSeaLevelDistance, farZ); + this.fogMatrix[8] = -offset.x * 2 / this.width; + this.fogMatrix[9] = offset.y * 2 / this.height; + mat4.scale(this.fogMatrix, this.fogMatrix, [1, -1, 1]); + mat4.translate(this.fogMatrix, this.fogMatrix, [0, 0, -this.cameraToCenterDistance]); + mat4.rotateX(this.fogMatrix, this.fogMatrix, this._pitch); + mat4.rotateZ(this.fogMatrix, this.fogMatrix, this.angle); + mat4.translate(this.fogMatrix, this.fogMatrix, [-x, -y, 0]); + mat4.scale(this.fogMatrix, this.fogMatrix, [1, 1, this._pixelPerMeter]); + mat4.translate(this.fogMatrix, this.fogMatrix, [0, 0, -this.elevation]); // elevate camera over terrain + // matrix for conversion from location to screen coordinates in 2D this.pixelMatrix3D = mat4.multiply(new Float64Array(16) as any, this.labelPlaneMatrix, m); @@ -929,6 +970,7 @@ class Transform { this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; + this._fogMatrixCache = {}; } maxPitchScaleFactor() { diff --git a/src/render/draw_terrain.ts b/src/render/draw_terrain.ts index 12a836438e..26e27a4825 100644 --- a/src/render/draw_terrain.ts +++ b/src/render/draw_terrain.ts @@ -83,11 +83,11 @@ function drawTerrain(painter: Painter, terrain: Terrain, tiles: Array) { const terrainData = terrain.getTerrainData(tile.tileID); context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture.texture); + const fogMatrix = painter.transform.calculateFogMatrix(tile.tileID.toUnwrapped()); const posMatrix = painter.transform.calculatePosMatrix(tile.tileID.toUnwrapped()); - const uniformValues = terrainUniformValues(posMatrix); + const uniformValues = terrainUniformValues(posMatrix, fogMatrix, painter.style.sky); program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, 'terrain', mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); } - } export { diff --git a/src/render/program/terrain_program.ts b/src/render/program/terrain_program.ts index 6eb4c04710..707c7e25d2 100644 --- a/src/render/program/terrain_program.ts +++ b/src/render/program/terrain_program.ts @@ -2,11 +2,13 @@ import { Uniform1i, Uniform1f, Uniform4f, - UniformMatrix4f + UniformMatrix4f, + UniformColor } from '../uniform_binding'; import type Context from '../../gl/context'; import type {UniformValues, UniformLocations} from '../../render/uniform_binding'; import {mat4} from 'gl-matrix'; +import Sky from '../../style/sky'; export type TerrainPreludeUniformsType = { 'u_depth': Uniform1i; @@ -20,6 +22,9 @@ export type TerrainPreludeUniformsType = { export type TerrainUniformsType = { 'u_matrix': UniformMatrix4f; 'u_texture': Uniform1i; + 'u_fog_matrix': UniformMatrix4f; + 'u_fog_color': UniformColor; + 'u_fog_blend': Uniform1f; }; export type TerrainDepthUniformsType = { @@ -43,7 +48,10 @@ const terrainPreludeUniforms = (context: Context, locations: UniformLocations): const terrainUniforms = (context: Context, locations: UniformLocations): TerrainUniformsType => ({ 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), - 'u_texture': new Uniform1i(context, locations.u_texture) + 'u_texture': new Uniform1i(context, locations.u_texture), + 'u_fog_matrix': new UniformMatrix4f(context, locations.u_fog_matrix), + 'u_fog_color': new UniformColor(context, locations.u_fog_color), + 'u_fog_blend': new Uniform1f(context, locations.u_fog_blend), }); const terrainDepthUniforms = (context: Context, locations: UniformLocations): TerrainDepthUniformsType => ({ @@ -57,10 +65,15 @@ const terrainCoordsUniforms = (context: Context, locations: UniformLocations): T }); const terrainUniformValues = ( - matrix: mat4 + matrix: mat4, + fogMatrix: mat4, + sky: Sky ): UniformValues => ({ 'u_matrix': matrix, - 'u_texture': 0 + 'u_texture': 0, + 'u_fog_matrix': fogMatrix, + 'u_fog_color': sky.properties.get('fog-color'), + 'u_fog_blend': sky.properties.get('fog-blend') }); const terrainDepthUniformValues = ( diff --git a/src/shaders/_prelude.vertex.glsl b/src/shaders/_prelude.vertex.glsl index b190a31ff8..0b67c81418 100644 --- a/src/shaders/_prelude.vertex.glsl +++ b/src/shaders/_prelude.vertex.glsl @@ -85,11 +85,11 @@ uniform highp sampler2D u_depth; // methods for pack/unpack depth value to texture rgba // https://stackoverflow.com/questions/34963366/encode-floating-point-data-in-a-rgba-texture -const highp vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.); -const highp vec4 bitShifts = vec4(1.) / bitSh; +const highp vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); +const highp vec4 bitShifts = vec4(1.0) / bitSh; highp float unpack(highp vec4 color) { - return dot(color , bitShifts); + return dot(color, bitShifts) * 2.0 - 1.0; } // calculate the opacity behind terrain, returns a value between 0 and 1. diff --git a/src/shaders/terrain.fragment.glsl b/src/shaders/terrain.fragment.glsl index 14e2517dab..3fa2a5db30 100644 --- a/src/shaders/terrain.fragment.glsl +++ b/src/shaders/terrain.fragment.glsl @@ -1,7 +1,16 @@ uniform sampler2D u_texture; +uniform highp sampler2D u_depth; +uniform vec4 u_fog_color; +uniform float u_fog_blend; varying vec2 v_texture_pos; +varying float v_depth; void main() { - gl_FragColor = texture2D(u_texture, v_texture_pos); + vec4 color = texture2D(u_texture, v_texture_pos); + if (v_depth > u_fog_blend) { + gl_FragColor = mix(color, u_fog_color, (v_depth - u_fog_blend) / (1.0 - u_fog_blend)); + } else { + gl_FragColor = color; + } } diff --git a/src/shaders/terrain.vertex.glsl b/src/shaders/terrain.vertex.glsl index 05b4d378d2..c348a778c9 100644 --- a/src/shaders/terrain.vertex.glsl +++ b/src/shaders/terrain.vertex.glsl @@ -1,12 +1,15 @@ attribute vec2 a_pos; uniform mat4 u_matrix; +uniform mat4 u_fog_matrix; varying vec2 v_texture_pos; varying float v_depth; void main() { + float ele = get_elevation(a_pos); v_texture_pos = a_pos / 8192.0; // 8192.0 is the hardcoded vector-tiles coordinates resolution - gl_Position = u_matrix * vec4(a_pos, get_elevation(a_pos), 1.0); - v_depth = gl_Position.z / gl_Position.w; + gl_Position = u_matrix * vec4(a_pos, ele, 1.0); + vec4 pos = u_fog_matrix * vec4(a_pos, ele, 1.0); + v_depth = pos.z / pos.w * 0.5 + 0.5; } diff --git a/src/shaders/terrain_coords.vertex.glsl b/src/shaders/terrain_coords.vertex.glsl new file mode 100644 index 0000000000..4e06531b1e --- /dev/null +++ b/src/shaders/terrain_coords.vertex.glsl @@ -0,0 +1,10 @@ +attribute vec2 a_pos; + +uniform mat4 u_matrix; + +varying vec2 v_texture_pos; + +void main() { + v_texture_pos = a_pos / 8192.0; // 8192.0 is the hardcoded vector-tiles coordinates resolution + gl_Position = u_matrix * vec4(a_pos, get_elevation(a_pos), 1.0); +} diff --git a/src/shaders/terrain_depth.fragment.glsl b/src/shaders/terrain_depth.fragment.glsl index ff5cf76d8c..afefa1bd8d 100644 --- a/src/shaders/terrain_depth.fragment.glsl +++ b/src/shaders/terrain_depth.fragment.glsl @@ -2,10 +2,10 @@ varying float v_depth; // methods for pack/unpack depth value to texture rgba // https://stackoverflow.com/questions/34963366/encode-floating-point-data-in-a-rgba-texture -const highp vec4 bitSh = vec4(256. * 256. * 256., 256. * 256., 256., 1.); -const highp vec4 bitMsk = vec4(0.,vec3(1./256.0)); +const highp vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); +const highp vec4 bitMsk = vec4(0.0, vec3(1.0 / 256.0)); highp vec4 pack(highp float value) { - highp vec4 comp = fract(value * bitSh); + highp vec4 comp = fract((value * 0.5 + 0.5) * bitSh); comp -= comp.xxyz * bitMsk; return comp; } diff --git a/src/shaders/terrain_depth.vertex.glsl b/src/shaders/terrain_depth.vertex.glsl new file mode 100644 index 0000000000..86c04b9b6d --- /dev/null +++ b/src/shaders/terrain_depth.vertex.glsl @@ -0,0 +1,10 @@ +attribute vec2 a_pos; + +uniform mat4 u_matrix; + +varying float v_depth; + +void main() { + gl_Position = u_matrix * vec4(a_pos, get_elevation(a_pos), 1.0); + v_depth = gl_Position.z / gl_Position.w; +} diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 293ed4d7ce..cc50b7b117 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -3866,6 +3866,26 @@ } } }, + "fog-blend": { + "type": "number", + "property-type": "data-constant", + "default": 0.7, + "minimum": 0, + "maximum": 1, + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "transition": true, + "doc": "Blend fog over terrain3d.", + "sdk-support": { + "basic functionality": { + "js": "3.0.0" + } + } + }, "horizon-blend": { "type": "number", "property-type": "data-constant", diff --git a/src/style/sky.ts b/src/style/sky.ts index defbc144bd..21e0017159 100644 --- a/src/style/sky.ts +++ b/src/style/sky.ts @@ -1,6 +1,6 @@ import {PosArray, TriangleIndexArray} from '../data/array_types.g'; import posAttributes from '../data/pos_attributes'; -import Style, {StyleSetterOptions} from './style'; +import {StyleSetterOptions} from './style'; import VertexBuffer from '../gl/vertex_buffer'; import IndexBuffer from '../gl/index_buffer'; import SegmentVector from '../data/segment'; @@ -18,23 +18,25 @@ import {extend} from '../util/util'; type Props = { 'sky-color': DataConstantProperty; 'fog-color': DataConstantProperty; + 'fog-blend': DataConstantProperty; 'horizon-blend': DataConstantProperty; }; type PropsPossiblyEvaluated = { 'sky-color': Color; 'fog-color': Color; + 'fog-blend': number; 'horizon-blend': number; }; const properties: Properties = new Properties({ 'sky-color': new DataConstantProperty(styleSpec.sky['sky-color'] as StylePropertySpecification), 'fog-color': new DataConstantProperty(styleSpec.sky['fog-color'] as StylePropertySpecification), + 'fog-blend': new DataConstantProperty(styleSpec.sky['fog-blend'] as StylePropertySpecification), 'horizon-blend': new DataConstantProperty(styleSpec.sky['horizon-blend'] as StylePropertySpecification), }); export default class Sky extends Evented { - style: Style; properties: PossiblyEvaluated; _transitionable: Transitionable; From 9945df3c23987e083d4f8c17b766d0e916ab76df Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Sun, 9 Oct 2022 17:30:57 +0200 Subject: [PATCH 3/7] fadeout fog when pitch < 70, fog-opacity grows exp instead of linear --- src/render/draw_terrain.ts | 2 +- src/render/program/terrain_program.ts | 13 ++++++++----- src/shaders/terrain.fragment.glsl | 7 ++++--- src/style/sky.ts | 6 ++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/render/draw_terrain.ts b/src/render/draw_terrain.ts index 26e27a4825..f51fa3033e 100644 --- a/src/render/draw_terrain.ts +++ b/src/render/draw_terrain.ts @@ -85,7 +85,7 @@ function drawTerrain(painter: Painter, terrain: Terrain, tiles: Array) { gl.bindTexture(gl.TEXTURE_2D, texture.texture); const fogMatrix = painter.transform.calculateFogMatrix(tile.tileID.toUnwrapped()); const posMatrix = painter.transform.calculatePosMatrix(tile.tileID.toUnwrapped()); - const uniformValues = terrainUniformValues(posMatrix, fogMatrix, painter.style.sky); + const uniformValues = terrainUniformValues(posMatrix, fogMatrix, painter.style.sky, painter.transform.pitch); program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.backCCW, uniformValues, terrainData, 'terrain', mesh.vertexBuffer, mesh.indexBuffer, mesh.segments); } } diff --git a/src/render/program/terrain_program.ts b/src/render/program/terrain_program.ts index 707c7e25d2..c9bbed5996 100644 --- a/src/render/program/terrain_program.ts +++ b/src/render/program/terrain_program.ts @@ -3,12 +3,14 @@ import { Uniform1f, Uniform4f, UniformMatrix4f, - UniformColor + UniformColor, + Uniform2f } from '../uniform_binding'; import type Context from '../../gl/context'; import type {UniformValues, UniformLocations} from '../../render/uniform_binding'; import {mat4} from 'gl-matrix'; import Sky from '../../style/sky'; +import { transform } from 'typescript'; export type TerrainPreludeUniformsType = { 'u_depth': Uniform1i; @@ -24,7 +26,7 @@ export type TerrainUniformsType = { 'u_texture': Uniform1i; 'u_fog_matrix': UniformMatrix4f; 'u_fog_color': UniformColor; - 'u_fog_blend': Uniform1f; + 'u_fog_blend': Uniform2f; }; export type TerrainDepthUniformsType = { @@ -51,7 +53,7 @@ const terrainUniforms = (context: Context, locations: UniformLocations): Terrain 'u_texture': new Uniform1i(context, locations.u_texture), 'u_fog_matrix': new UniformMatrix4f(context, locations.u_fog_matrix), 'u_fog_color': new UniformColor(context, locations.u_fog_color), - 'u_fog_blend': new Uniform1f(context, locations.u_fog_blend), + 'u_fog_blend': new Uniform2f(context, locations.u_fog_blend), }); const terrainDepthUniforms = (context: Context, locations: UniformLocations): TerrainDepthUniformsType => ({ @@ -67,13 +69,14 @@ const terrainCoordsUniforms = (context: Context, locations: UniformLocations): T const terrainUniformValues = ( matrix: mat4, fogMatrix: mat4, - sky: Sky + sky: Sky, + pitch: number ): UniformValues => ({ 'u_matrix': matrix, 'u_texture': 0, 'u_fog_matrix': fogMatrix, 'u_fog_color': sky.properties.get('fog-color'), - 'u_fog_blend': sky.properties.get('fog-blend') + 'u_fog_blend': [sky.properties.get('fog-blend'), sky.calculateFogBlendOpacity(pitch)] }); const terrainDepthUniformValues = ( diff --git a/src/shaders/terrain.fragment.glsl b/src/shaders/terrain.fragment.glsl index 3fa2a5db30..025141d155 100644 --- a/src/shaders/terrain.fragment.glsl +++ b/src/shaders/terrain.fragment.glsl @@ -1,15 +1,16 @@ uniform sampler2D u_texture; uniform highp sampler2D u_depth; uniform vec4 u_fog_color; -uniform float u_fog_blend; +uniform vec2 u_fog_blend; varying vec2 v_texture_pos; varying float v_depth; void main() { vec4 color = texture2D(u_texture, v_texture_pos); - if (v_depth > u_fog_blend) { - gl_FragColor = mix(color, u_fog_color, (v_depth - u_fog_blend) / (1.0 - u_fog_blend)); + if (v_depth > u_fog_blend[0]) { + float a = (v_depth - u_fog_blend[0]) / (1.0 - u_fog_blend[0]); + gl_FragColor = mix(color, u_fog_color, pow(a * u_fog_blend[1], 2.0)); } else { gl_FragColor = color; } diff --git a/src/style/sky.ts b/src/style/sky.ts index 21e0017159..805693c096 100644 --- a/src/style/sky.ts +++ b/src/style/sky.ts @@ -107,4 +107,10 @@ export default class Sky extends Evented { styleSpec }))); } + + calculateFogBlendOpacity(pitch) { + if (pitch < 60) return 0; // disable + if (pitch < 70) return (pitch - 60) / 10; // fade in + return 1; + } } From 7ed74a07e22b515e4d9239ca9295c4d52c32b3ee Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Sun, 9 Oct 2022 18:30:44 +0200 Subject: [PATCH 4/7] load new terrain depth & fragment vertext shaders --- src/shaders/shaders.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index 67beddc7dc..3b6b77e57b 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -56,6 +56,8 @@ import symbolTextAndIconVert from './symbol_text_and_icon.vertex.glsl.g'; import terrainDepthFrag from './terrain_depth.fragment.glsl.g'; import terrainCoordsFrag from './terrain_coords.fragment.glsl.g'; import terrainFrag from './terrain.fragment.glsl.g'; +import terrainDepthVert from './terrain_depth.vertex.glsl.g'; +import terrainCoordsVert from './terrain_coords.vertex.glsl.g'; import terrainVert from './terrain.vertex.glsl.g'; import skyFrag from './sky.fragment.glsl.g'; import skyVert from './sky.vertex.glsl.g'; @@ -88,8 +90,8 @@ const shaders = { symbolSDF: compile(symbolSDFFrag, symbolSDFVert), symbolTextAndIcon: compile(symbolTextAndIconFrag, symbolTextAndIconVert), terrain: compile(terrainFrag, terrainVert), - terrainDepth: compile(terrainDepthFrag, terrainVert), - terrainCoords: compile(terrainCoordsFrag, terrainVert), + terrainDepth: compile(terrainDepthFrag, terrainDepthVert), + terrainCoords: compile(terrainCoordsFrag, terrainCoordsVert), sky: compile(skyFrag, skyVert) }; From 05082799261de6fedd252ba8d4137164ddf6f1f9 Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Tue, 11 Oct 2022 16:12:03 +0200 Subject: [PATCH 5/7] nicer default values, quadratic horizon-blend, fix map.setSky --- src/render/program/terrain_program.ts | 7 +++-- src/shaders/sky.fragment.glsl | 2 +- src/style-spec/reference/v8.json | 12 +++---- src/style/sky.ts | 24 +++++++------- src/style/style.ts | 45 +++++++++++++-------------- src/ui/map.ts | 4 +-- 6 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/render/program/terrain_program.ts b/src/render/program/terrain_program.ts index b336fee9a5..2d39da5922 100644 --- a/src/render/program/terrain_program.ts +++ b/src/render/program/terrain_program.ts @@ -9,6 +9,7 @@ import type Context from '../../gl/context'; import type {UniformValues, UniformLocations} from '../../render/uniform_binding'; import {mat4} from 'gl-matrix'; import Sky from '../../style/sky'; +import Color from '../../style-spec/util/color'; export type TerrainPreludeUniformsType = { 'u_depth': Uniform1i; @@ -83,9 +84,9 @@ const terrainUniformValues = ( 'u_texture': 0, 'u_ele_delta': eleDelta, 'u_fog_matrix': fogMatrix, - 'u_fog_color': sky.properties.get('fog-color'), - 'u_fog_blend': sky.properties.get('fog-blend'), - 'u_fog_blend_opacity': sky.calculateFogBlendOpacity(pitch) + 'u_fog_color': sky ? sky.properties.get('fog-color') : Color.white, + 'u_fog_blend': sky ? sky.properties.get('fog-blend') : 1, + 'u_fog_blend_opacity': sky ? sky.calculateFogBlendOpacity(pitch) : 0 }); const terrainDepthUniformValues = ( diff --git a/src/shaders/sky.fragment.glsl b/src/shaders/sky.fragment.glsl index 4a443f1eaa..e62284334c 100644 --- a/src/shaders/sky.fragment.glsl +++ b/src/shaders/sky.fragment.glsl @@ -10,7 +10,7 @@ void main() { if (y > u_horizon) { float blend = y - u_horizon; if (blend < u_horizon_blend) { - gl_FragColor = mix(u_fog_color, u_sky_color, blend / u_horizon_blend); + gl_FragColor = mix(u_sky_color, u_fog_color, pow(1.0 - blend / u_horizon_blend, 2.0)); } else { gl_FragColor = u_sky_color; } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index cc50b7b117..61b1207c4a 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -61,7 +61,7 @@ "type": "sky", "doc": "Tho map sky.", "example": { - "sky-color": "#0000ff" + "sky-color": "#199EF3" } }, "terrain": { @@ -3833,7 +3833,7 @@ "sky-color": { "type": "color", "property-type": "data-constant", - "default": "#ddf4ff", + "default": "#88C6FC", "expression": { "interpolated": true, "parameters": [ @@ -3869,7 +3869,7 @@ "fog-blend": { "type": "number", "property-type": "data-constant", - "default": 0.7, + "default": 0.5, "minimum": 0, "maximum": 1, "expression": { @@ -3879,7 +3879,7 @@ ] }, "transition": true, - "doc": "Blend fog over terrain3d.", + "doc": "Blend fog over terrain3d. A value between 0 and 1. 0 is map center and 1 is the horizon", "sdk-support": { "basic functionality": { "js": "3.0.0" @@ -3889,7 +3889,7 @@ "horizon-blend": { "type": "number", "property-type": "data-constant", - "default": 0.3, + "default": 0.8, "minimum": 0, "maximum": 1, "expression": { @@ -3899,7 +3899,7 @@ ] }, "transition": true, - "doc": "Blend sky and fog color on horizon", + "doc": "Blend fog and sky color at horizon. A value between 0 and 1. 0 is the horizon and 1 is map-height / 2", "sdk-support": { "basic functionality": { "js": "3.0.0" diff --git a/src/style/sky.ts b/src/style/sky.ts index 805693c096..d1279e0ce1 100644 --- a/src/style/sky.ts +++ b/src/style/sky.ts @@ -1,6 +1,5 @@ import {PosArray, TriangleIndexArray} from '../data/array_types.g'; import posAttributes from '../data/pos_attributes'; -import {StyleSetterOptions} from './style'; import VertexBuffer from '../gl/vertex_buffer'; import IndexBuffer from '../gl/index_buffer'; import SegmentVector from '../data/segment'; @@ -36,6 +35,8 @@ const properties: Properties = new Properties({ 'horizon-blend': new DataConstantProperty(styleSpec.sky['horizon-blend'] as StylePropertySpecification), }); +const TRANSITION_SUFFIX = '-transition'; + export default class Sky extends Evented { properties: PossiblyEvaluated; @@ -67,13 +68,16 @@ export default class Sky extends Evented { this.segments = SegmentVector.simpleSegment(0, 0, vertexArray.length, indexArray.length); } - setSky(sky?: SkySpecification, options: StyleSetterOptions = {}) { - if (this._validate(validateSky, sky, options)) { - return; - } + setSky(sky?: SkySpecification) { + if (this._validate(validateSky, sky)) return; for (const name in sky) { - this._transitionable.setValue(name as keyof Props, sky[name]); + const value = sky[name]; + if (name.endsWith(TRANSITION_SUFFIX)) { + this._transitionable.setTransition(name.slice(0, -TRANSITION_SUFFIX.length) as keyof Props, value); + } else { + this._transitionable.setValue(name as keyof Props, value); + } } } @@ -93,13 +97,7 @@ export default class Sky extends Evented { this.properties = this._transitioning.possiblyEvaluate(parameters); } - _validate(validate: Function, value: unknown, options?: { - validate?: boolean; - }) { - if (options && options.validate === false) { - return false; - } - + _validate(validate: Function, value: unknown) { return emitValidationErrors(this, validate.call(validateStyle, extend({ value, // Workaround for https://github.com/mapbox/mapbox-gl-js/issues/2407 diff --git a/src/style/style.ts b/src/style/style.ts index cf103be94b..916a2c68f8 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -328,7 +328,7 @@ class Style extends Evented { this.light = new Light(this.stylesheet.light); - this.sky = new Sky(this.map.painter.context, this.stylesheet.sky); + if (this.stylesheet.sky) this.setSky(this.stylesheet.sky); this.map.setTerrain(this.stylesheet.terrain); @@ -408,6 +408,10 @@ class Style extends Evented { return true; } + if (this.sky && this.sky.hasTransition()) { + return true; + } + for (const id in this.sourceCaches) { if (this.sourceCaches[id].hasTransition()) { return true; @@ -465,7 +469,7 @@ class Style extends Evented { } this.light.updateTransitions(parameters); - this.sky.updateTransitions(parameters); + if (this.sky) this.sky.updateTransitions(parameters); this._resetUpdates(); } @@ -495,7 +499,7 @@ class Style extends Evented { } this.light.recalculate(parameters); - this.sky.recalculate(parameters); + if (this.sky) this.sky.recalculate(parameters); this.z = parameters.zoom; if (changed) { @@ -1270,32 +1274,25 @@ class Style extends Evented { } getSky() { - return this.sky.getSky(); + return this.sky && this.sky.getSky(); } - setSky(skyOptions: SkySpecification, options: StyleSetterOptions = {}) { + setSky(skyOptions?: SkySpecification) { this._checkLoaded(); - const sky = this.sky.getSky(); - let _update = false; - for (const key in skyOptions) { - if (!deepEqual(skyOptions[key], sky[key])) { - _update = true; - break; - } + if (!skyOptions) { + this.sky = null; + } else { + if (this.sky) this.sky.setSky(skyOptions); + else this.sky = new Sky(this.map.painter.context, skyOptions); + this.sky.updateTransitions({ + now: browser.now(), + transition: extend({ + duration: 300, + delay: 0 + }, this.stylesheet.transition) + }); } - if (!_update) return; - - const parameters = { - now: browser.now(), - transition: extend({ - duration: 300, - delay: 0 - }, this.stylesheet.transition) - }; - - this.sky.setSky(skyOptions, options); - this.sky.updateTransitions(parameters); } _validate(validate: Validator, key: string, value: any, props: any, options: { diff --git a/src/ui/map.ts b/src/ui/map.ts index 9cc55f7a3c..cba3fbb824 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -2311,9 +2311,9 @@ class Map extends Camera { * @example * map.setSky({ 'sky-color': '#00f' }); */ - setSky(sky: SkySpecification, options: StyleSetterOptions = {}) { + setSky(sky: SkySpecification) { this._lazyInitEmptyStyle(); - this.style.setSky(sky, options); + this.style.setSky(sky); return this._update(true); } From d51bd933c30ed8272704ecdbaf0c0feb3f6aa47c Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Mon, 5 Dec 2022 11:35:13 +0100 Subject: [PATCH 6/7] minor fixes based on JannikGM's code-review --- src/render/painter.ts | 5 +++-- src/shaders/_prelude.vertex.glsl | 2 +- src/shaders/sky.fragment.glsl | 2 -- src/shaders/sky.vertex.glsl | 2 +- src/shaders/terrain.fragment.glsl | 1 - src/shaders/terrain_depth.fragment.glsl | 2 +- src/style-spec/reference/v8.json | 2 +- src/style/sky.ts | 8 ++++++++ 8 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/render/painter.ts b/src/render/painter.ts index d476828d31..67a35f3f7f 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -447,6 +447,9 @@ class Painter { this.context.clear({color: options.showOverdrawInspector ? Color.black : Color.transparent, depth: 1}); this.clearStencil(); + // draw sky first to not overwrite symbols + if (this.style.sky) drawSky(this, this.style.sky); + this._showOverdrawInspector = options.showOverdrawInspector; this.depthRangeFor3D = [0, 1 - ((style._order.length + 2) * this.numSublayers * this.depthEpsilon)]; @@ -484,8 +487,6 @@ class Painter { this.renderLayer(this, sourceCache, layer, coords); } - if (this.style.sky) drawSky(this, this.style.sky); - if (this.options.showTileBoundaries) { const selectedSource = selectDebugSource(this.style, this.transform.zoom); if (selectedSource) { diff --git a/src/shaders/_prelude.vertex.glsl b/src/shaders/_prelude.vertex.glsl index 0b67c81418..f6702744ac 100644 --- a/src/shaders/_prelude.vertex.glsl +++ b/src/shaders/_prelude.vertex.glsl @@ -89,7 +89,7 @@ const highp vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); const highp vec4 bitShifts = vec4(1.0) / bitSh; highp float unpack(highp vec4 color) { - return dot(color, bitShifts) * 2.0 - 1.0; + return dot(color, bitShifts); } // calculate the opacity behind terrain, returns a value between 0 and 1. diff --git a/src/shaders/sky.fragment.glsl b/src/shaders/sky.fragment.glsl index e62284334c..d0652361c0 100644 --- a/src/shaders/sky.fragment.glsl +++ b/src/shaders/sky.fragment.glsl @@ -1,5 +1,3 @@ - - uniform vec4 u_sky_color; uniform vec4 u_fog_color; uniform float u_horizon; diff --git a/src/shaders/sky.vertex.glsl b/src/shaders/sky.vertex.glsl index 6f90a3591f..594982ab06 100644 --- a/src/shaders/sky.vertex.glsl +++ b/src/shaders/sky.vertex.glsl @@ -1,5 +1,5 @@ attribute vec2 a_pos; void main() { - gl_Position = vec4(a_pos, 0.99999, 1.0); + gl_Position = vec4(a_pos, 1.0, 1.0); } diff --git a/src/shaders/terrain.fragment.glsl b/src/shaders/terrain.fragment.glsl index 6f112bb49e..f1e05e2e46 100644 --- a/src/shaders/terrain.fragment.glsl +++ b/src/shaders/terrain.fragment.glsl @@ -1,5 +1,4 @@ uniform sampler2D u_texture; -uniform highp sampler2D u_depth; uniform vec4 u_fog_color; uniform float u_fog_blend; uniform float u_fog_blend_opacity; diff --git a/src/shaders/terrain_depth.fragment.glsl b/src/shaders/terrain_depth.fragment.glsl index afefa1bd8d..16445be647 100644 --- a/src/shaders/terrain_depth.fragment.glsl +++ b/src/shaders/terrain_depth.fragment.glsl @@ -5,7 +5,7 @@ varying float v_depth; const highp vec4 bitSh = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); const highp vec4 bitMsk = vec4(0.0, vec3(1.0 / 256.0)); highp vec4 pack(highp float value) { - highp vec4 comp = fract((value * 0.5 + 0.5) * bitSh); + highp vec4 comp = fract(value * bitSh); comp -= comp.xxyz * bitMsk; return comp; } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 61b1207c4a..6bc0cba123 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -59,7 +59,7 @@ }, "sky": { "type": "sky", - "doc": "Tho map sky.", + "doc": "The map sky.", "example": { "sky-color": "#199EF3" } diff --git a/src/style/sky.ts b/src/style/sky.ts index d1279e0ce1..0837122995 100644 --- a/src/style/sky.ts +++ b/src/style/sky.ts @@ -106,6 +106,14 @@ export default class Sky extends Evented { }))); } + // Currently fog is a very simple implementation, and should only used + // to create an atmosphere near the horizon. + // But because the fog is drawn from the far-clipping-plane to + // map-center, and because the fog does nothing know about the horizon, + // this method does a fadeout in respect of pitch. So, when the horizon + // gets out of view, which is at about pitch 70, this methods calculates + // the corresponding opacity values. Below pitch 60 the fog is completely + // invisible. calculateFogBlendOpacity(pitch) { if (pitch < 60) return 0; // disable if (pitch < 70) return (pitch - 60) / 10; // fade in From 2aca406ad9b478f061326e50ce2ec87a28fb016d Mon Sep 17 00:00:00 2001 From: max demmelbauer Date: Mon, 5 Dec 2022 12:12:07 +0100 Subject: [PATCH 7/7] rename v_depth used for fog to v_fog_depth --- src/shaders/terrain.fragment.glsl | 6 +++--- src/shaders/terrain.vertex.glsl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shaders/terrain.fragment.glsl b/src/shaders/terrain.fragment.glsl index f1e05e2e46..c92080e42e 100644 --- a/src/shaders/terrain.fragment.glsl +++ b/src/shaders/terrain.fragment.glsl @@ -4,12 +4,12 @@ uniform float u_fog_blend; uniform float u_fog_blend_opacity; varying vec2 v_texture_pos; -varying float v_depth; +varying float v_fog_depth; void main() { vec4 color = texture2D(u_texture, v_texture_pos); - if (v_depth > u_fog_blend) { - float a = (v_depth - u_fog_blend) / (1.0 - u_fog_blend); + if (v_fog_depth > u_fog_blend) { + float a = (v_fog_depth - u_fog_blend) / (1.0 - u_fog_blend); gl_FragColor = mix(color, u_fog_color, pow(a * u_fog_blend_opacity, 2.0)); } else { gl_FragColor = color; diff --git a/src/shaders/terrain.vertex.glsl b/src/shaders/terrain.vertex.glsl index 094ebb29b5..dd4708030c 100644 --- a/src/shaders/terrain.vertex.glsl +++ b/src/shaders/terrain.vertex.glsl @@ -5,7 +5,7 @@ uniform mat4 u_fog_matrix; uniform float u_ele_delta; varying vec2 v_texture_pos; -varying float v_depth; +varying float v_fog_depth; void main() { float ele = get_elevation(a_pos3d.xy); @@ -13,5 +13,5 @@ void main() { v_texture_pos = a_pos3d.xy / 8192.0; gl_Position = u_matrix * vec4(a_pos3d.xy, ele - ele_delta, 1.0); vec4 pos = u_fog_matrix * vec4(a_pos3d.xy, ele, 1.0); - v_depth = pos.z / pos.w * 0.5 + 0.5; + v_fog_depth = pos.z / pos.w * 0.5 + 0.5; }