diff --git a/debug/fog.html b/debug/fog.html
index 570b27b37b5..cef9e32e1ba 100644
--- a/debug/fog.html
+++ b/debug/fog.html
@@ -36,6 +36,7 @@
this.start = 2;
this.end = 12.0;
this.color = [255, 255, 255];
+ this.opacity = 1.0;
this.horizonBlend = 0.1;
this.showTileBoundaries = false;
};
@@ -99,7 +100,7 @@
enableFog.onFinishChange((value) => {
map.setFog(value ? {
"range": [guiParams.start, guiParams.end],
- "color": 'rgba(' + guiParams.color[0] + ', ' + guiParams.color[1] + ', ' + guiParams.color[2] + ', 1.0)',
+ "color": 'rgba(' + guiParams.color[0] + ', ' + guiParams.color[1] + ', ' + guiParams.color[2] + ', ' + guiParams.opacity + ')',
"horizon-blend": guiParams.horizonBlend
} : null);
});
@@ -125,10 +126,17 @@
});
});
+ var opacity = fog.add(guiParams, 'opacity', 0.0, 1.0);
+ opacity.onChange((value) => {
+ map.setFog({
+ "color": 'rgba(' + guiParams.color[0] + ', ' + guiParams.color[1] + ', ' + guiParams.color[2] + ', ' + value + ')'
+ });
+ });
+
var color = fog.addColor(guiParams, 'color');
color.onChange((value) => {
map.setFog({
- "color": 'rgba(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', 1.0)',
+ "color": 'rgba(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', ' + guiParams.opacity + ')'
});
});
};
@@ -189,7 +197,7 @@
map.setFog(guiParams.enableFog ? {
"range": [guiParams.start, guiParams.end],
- "color": 'rgba(' + guiParams.color[0] + ', ' + guiParams.color[1] + ', ' + guiParams.color[2] + ', 1.0)',
+ "color": 'rgba(' + guiParams.color[0] + ', ' + guiParams.color[1] + ', ' + guiParams.color[2] + ', ' + guiParams.opacity + ')',
"horizon-blend": guiParams.horizonBlend
} : null);
diff --git a/src/render/fog.js b/src/render/fog.js
index a5767f82b35..a51cd5edf24 100644
--- a/src/render/fog.js
+++ b/src/render/fog.js
@@ -1,25 +1,48 @@
// @flow
import Context from '../gl/context.js';
-import type {UniformLocations} from './uniform_binding.js';
-
-import {Uniform1f, Uniform2f, Uniform3f, UniformMatrix4f} from './uniform_binding.js';
+import type {UniformLocations, UniformValues} from './uniform_binding.js';
+import type {UnwrappedTileID} from '../source/tile_id.js';
+import Painter from './painter.js';
+import Fog from '../style/fog.js';
+import {Uniform1f, Uniform2f, Uniform4f, UniformMatrix4f} from './uniform_binding.js';
export type FogUniformsType = {|
'u_fog_matrix': UniformMatrix4f,
'u_fog_range': Uniform2f,
- 'u_fog_color': Uniform3f,
+ 'u_fog_color': Uniform4f,
'u_fog_horizon_blend': Uniform1f,
'u_fog_temporal_offset': Uniform1f,
- 'u_fog_opacity': Uniform1f,
|};
export const fogUniforms = (context: Context, locations: UniformLocations): FogUniformsType => ({
'u_fog_matrix': new UniformMatrix4f(context, locations.u_fog_matrix),
'u_fog_range': new Uniform2f(context, locations.u_fog_range),
- 'u_fog_color': new Uniform3f(context, locations.u_fog_color),
+ 'u_fog_color': new Uniform4f(context, locations.u_fog_color),
'u_fog_horizon_blend': new Uniform1f(context, locations.u_fog_horizon_blend),
'u_fog_temporal_offset': new Uniform1f(context, locations.u_fog_temporal_offset),
- 'u_fog_opacity': new Uniform1f(context, locations.u_fog_opacity),
});
+
+export const fogUniformValues = (
+ painter: Painter,
+ fog: Fog,
+ tileID: ?UnwrappedTileID,
+ fogOpacity: number
+): UniformValues => {
+ const fogColor = fog.properties.get('color');
+ const temporalOffset = (painter.frameCounter / 1000.0) % 1;
+ const fogColorUnpremultiplied = [
+ fogColor.r / fogColor.a,
+ fogColor.g / fogColor.a,
+ fogColor.b / fogColor.a,
+ fogOpacity
+ ];
+ return {
+ 'u_fog_matrix': tileID ? painter.transform.calculateFogTileMatrix(tileID) : painter.identityMat,
+ 'u_fog_range': fog.properties.get('range'),
+ 'u_fog_color': fogColorUnpremultiplied,
+ 'u_fog_horizon_blend': fog.properties.get('horizon-blend'),
+ 'u_fog_temporal_offset': temporalOffset
+ };
+};
diff --git a/src/render/painter.js b/src/render/painter.js
index 3b5d1e14034..fda78278d9e 100644
--- a/src/render/painter.js
+++ b/src/render/painter.js
@@ -19,7 +19,7 @@ import shaders from '../shaders/shaders.js';
import Program from './program.js';
import {programUniforms} from './program/program_uniforms.js';
import Context from '../gl/context.js';
-import {FOG_PITCH_END} from '../style/fog_helpers.js';
+import {fogUniformValues} from '../render/fog.js';
import DepthMode from '../gl/depth_mode.js';
import StencilMode from '../gl/stencil_mode.js';
import ColorMode from '../gl/color_mode.js';
@@ -179,13 +179,13 @@ class Painter {
terrain.update(style, this.transform, cameraChanging);
}
- _updateFog() {
- if (this.transform.pitch <= FOG_PITCH_END || !(this.style && this.style.fog)) {
+ _updateFog(style: Style) {
+ const fog = style.fog;
+ if (!fog || (fog && fog.getFogOpacity(this.transform.pitch) !== 1.0)) {
this.transform.fogCullDistSq = null;
return;
}
- const fog = this.style.fog;
const fogStart = fog.properties.get('range')[0];
const fogEnd = fog.properties.get('range')[1];
@@ -753,13 +753,14 @@ class Painter {
const terrain = this.terrain && !this.terrain.renderingToTexture; // Enables elevation sampling in vertex shader.
const rtt = this.terrain && this.terrain.renderingToTexture;
const fog = this.style && this.style.fog;
- const fogOpacity = fog && fog.getFogPitchFactor(this.transform.pitch);
-
const defines = [];
+
if (terrain) defines.push('TERRAIN');
// When terrain is active, fog is rendered as part of draping, not as part of tile
// rendering. Removing the fog flag during tile rendering avoids additional defines.
- if (fog && fogOpacity !== 0.0 && !rtt) defines.push('FOG');
+ if (fog && !rtt && fog.getFogOpacity(this.transform.pitch) !== 0.0) {
+ defines.push('FOG');
+ }
if (rtt) defines.push('RENDER_TO_TEXTURE');
if (this._showOverdrawInspector) defines.push('OVERDRAW_INSPECTOR');
return defines;
@@ -837,26 +838,12 @@ class Painter {
}
prepareDrawProgram(context: Context, program: Program<*>, tileID: ?UnwrappedTileID) {
- const fog = this.style && this.style.fog;
- const fogOpacity = (fog && fog.getFogPitchFactor(this.transform.pitch)) || 0.0;
- if (fog && fogOpacity !== 0.0) {
- const temporalOffset = (this.frameCounter / 1000.0) % 1;
- const fogColor = fog.properties.get('color');
- const fogColorUnpremultiplied = fogColor.a === 0 ? [0, 0, 0] : [
- fogColor.r / fogColor.a,
- fogColor.g / fogColor.a,
- fogColor.b / fogColor.a
- ];
- const uniforms = {};
-
- uniforms['u_fog_matrix'] = tileID ? this.transform.calculateFogTileMatrix(tileID) : this.identityMat;
- uniforms['u_fog_range'] = fog.properties.get('range');
- uniforms['u_fog_color'] = fogColorUnpremultiplied;
- uniforms['u_fog_horizon_blend'] = fog.properties.get('horizon-blend');
- uniforms['u_fog_temporal_offset'] = temporalOffset;
- uniforms['u_fog_opacity'] = fogOpacity;
-
- program.setFogUniformValues(context, uniforms);
+ const fog = this.style.fog;
+ if (fog) {
+ const fogOpacity = fog.getFogOpacity(this.transform.pitch);
+ if (fogOpacity !== 0.0) {
+ program.setFogUniformValues(context, fogUniformValues(this, fog, tileID, fogOpacity));
+ }
}
}
diff --git a/src/shaders/_prelude.glsl b/src/shaders/_prelude.glsl
index bb1721febbc..7d2b9c105c0 100644
--- a/src/shaders/_prelude.glsl
+++ b/src/shaders/_prelude.glsl
@@ -7,7 +7,7 @@
#ifdef FOG
-uniform mediump float u_fog_opacity;
+uniform mediump vec4 u_fog_color;
uniform mediump vec2 u_fog_range;
uniform mediump float u_fog_horizon_blend;
@@ -24,7 +24,7 @@ float fog_horizon_blending(vec3 camera_dir) {
float t = max(0.0, camera_dir.z / u_fog_horizon_blend);
// Factor of 3 chosen to roughly match smoothstep.
// See: https://www.desmos.com/calculator/pub31lvshf
- return u_fog_opacity * exp(-3.0 * t * t);
+ return u_fog_color.a * exp(-3.0 * t * t);
}
// Compute a ramp for fog opacity
@@ -38,7 +38,7 @@ float fog_opacity(float t) {
falloff *= falloff * falloff;
// Scale and clip to 1 at the far limit
- return u_fog_opacity * min(1.0, 1.00747 * falloff);
+ return u_fog_color.a * min(1.0, 1.00747 * falloff);
}
#endif
diff --git a/src/shaders/_prelude_fog.fragment.glsl b/src/shaders/_prelude_fog.fragment.glsl
index 6eddf131844..25e4325c96d 100644
--- a/src/shaders/_prelude_fog.fragment.glsl
+++ b/src/shaders/_prelude_fog.fragment.glsl
@@ -1,6 +1,5 @@
#ifdef FOG
-uniform vec3 u_fog_color;
uniform float u_fog_temporal_offset;
// This function is only used in rare places like heatmap where opacity is used
@@ -14,18 +13,18 @@ vec3 fog_apply(vec3 color, vec3 pos) {
float depth = length(pos);
float opacity = fog_opacity(fog_range(depth));
opacity *= fog_horizon_blending(pos / depth);
- return mix(color, u_fog_color, opacity);
+ return mix(color, u_fog_color.rgb, opacity);
}
// Apply fog computed in the vertex shader
vec3 fog_apply_from_vert(vec3 color, float fog_opac) {
- return mix(color, u_fog_color, fog_opac);
+ return mix(color, u_fog_color.rgb, fog_opac);
}
// Assumes z up
vec3 fog_apply_sky_gradient(vec3 camera_ray, vec3 sky_color) {
float horizon_blend = fog_horizon_blending(normalize(camera_ray));
- return mix(sky_color, u_fog_color, horizon_blend);
+ return mix(sky_color, u_fog_color.rgb, horizon_blend);
}
// Un-premultiply the alpha, then blend fog, then re-premultiply alpha.
diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json
index 978da0cb8b8..5cafea53732 100644
--- a/src/style-spec/reference/v8.json
+++ b/src/style-spec/reference/v8.json
@@ -3718,7 +3718,7 @@
]
},
"transition": true,
- "doc": "The color of the fog. If provided as an `rgba` color, the alpha component must be nonzero, but is otherwise unused.",
+ "doc": "The color of the fog. Using opacity is recommended only for smoothly transitioning fog on/off as anything less than 100% opacity results in more tiles loaded and drawn.",
"sdk-support": {
"basic functionality": {
"js": "2.3.0"
diff --git a/src/style-spec/validate/validate_fog.js b/src/style-spec/validate/validate_fog.js
index db375748560..63c8228c5f8 100644
--- a/src/style-spec/validate/validate_fog.js
+++ b/src/style-spec/validate/validate_fog.js
@@ -4,7 +4,6 @@ import validate from './validate.js';
import getType from '../util/get_type.js';
import {isExpression} from '../expression/index.js';
import {deepUnbundle} from '../util/unbundle_jsonlint.js';
-import {parseCSSColor} from 'csscolorparser';
export default function validateFog(options) {
const fog = options.value;
@@ -21,13 +20,6 @@ export default function validateFog(options) {
return errors;
}
- if (fog.color && !isExpression(deepUnbundle(fog.color))) {
- const fogColor = parseCSSColor(fog.color);
- if (fogColor && fogColor[3] === 0) {
- errors = errors.concat([new ValidationError('fog', fog, 'fog.color alpha must be nonzero.')]);
- }
- }
-
if (fog.range && !isExpression(deepUnbundle(fog.range)) && fog.range[0] >= fog.range[1]) {
errors = errors.concat([new ValidationError('fog', fog, 'fog.range[0] can\'t be greater than or equal to fog.range[1]')]);
}
diff --git a/src/style/fog.js b/src/style/fog.js
index fe740c01135..74614f398c5 100644
--- a/src/style/fog.js
+++ b/src/style/fog.js
@@ -43,7 +43,8 @@ class Fog extends Evented {
get state(): FogState {
return {
range: this.properties.get('range'),
- horizonBlend: this.properties.get('horizon-blend')
+ horizonBlend: this.properties.get('horizon-blend'),
+ alpha: this.properties.get('color').a
};
}
@@ -70,6 +71,11 @@ class Fog extends Evented {
return smoothstep(FOG_PITCH_START, FOG_PITCH_END, pitch);
}
+ getFogOpacity(pitch: number): number {
+ const fogColor = (this.properties && this.properties.get('color')) || 1.0;
+ return this.getFogPitchFactor(pitch) * fogColor.a;
+ }
+
getOpacityAtLatLng(lngLat: LngLat, transform: Transform): number {
return getFogOpacityAtLngLat(this.state, lngLat, transform);
}
diff --git a/src/style/fog_helpers.js b/src/style/fog_helpers.js
index 40ffb408d22..6c624120ed6 100644
--- a/src/style/fog_helpers.js
+++ b/src/style/fog_helpers.js
@@ -12,7 +12,8 @@ export const FOG_SYMBOL_CLIPPING_THRESHOLD = 0.9;
export type FogState = {
range: [number, number],
- horizonBlend: number
+ horizonBlend: number,
+ alpha: number
};
// As defined in _prelude_fog.fragment.glsl#fog_opacity
@@ -30,7 +31,7 @@ export function getFogOpacity(state: FogState, pos: Array, pitch: number
falloff *= falloff * falloff;
falloff = Math.min(1.0, 1.00747 * falloff);
- return falloff * fogPitchOpacity;
+ return falloff * fogPitchOpacity * state.alpha;
}
export function getFogOpacityAtTileCoord(state: FogState, x: number, y: number, z: number, tileId: UnwrappedTileID, transform: Transform): number {
diff --git a/src/ui/map.js b/src/ui/map.js
index 82ff451113f..5be892d6f0e 100755
--- a/src/ui/map.js
+++ b/src/ui/map.js
@@ -2658,7 +2658,7 @@ class Map extends Camera {
// need for the current transform
if (this.style && this._sourcesDirty) {
this._sourcesDirty = false;
- this.painter._updateFog();
+ this.painter._updateFog(this.style);
this._updateTerrain(); // Terrain DEM source updates here and skips update in style._updateSources.
this.style._updateSources(this.transform);
}
diff --git a/test/integration/render-tests/fog/color-opacity/expected.png b/test/integration/render-tests/fog/color-opacity/expected.png
new file mode 100644
index 00000000000..7530a2233b3
Binary files /dev/null and b/test/integration/render-tests/fog/color-opacity/expected.png differ
diff --git a/test/integration/render-tests/fog/color-opacity/style.json b/test/integration/render-tests/fog/color-opacity/style.json
new file mode 100644
index 00000000000..9659dc6011e
--- /dev/null
+++ b/test/integration/render-tests/fog/color-opacity/style.json
@@ -0,0 +1,29 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "height": 256,
+ "allowed": 0.0005
+ }
+ },
+ "center": [
+ 52.499167,
+ 13.418056
+ ],
+ "zoom": 16,
+ "pitch": 70,
+ "fog": {
+ "range": [1, 4],
+ "color": "rgba(255, 30, 35, 0.8)"
+ },
+ "sources": {},
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "beige"
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/fog/culling/opacity/expected.png b/test/integration/render-tests/fog/culling/opacity/expected.png
new file mode 100644
index 00000000000..ff333b3a8eb
Binary files /dev/null and b/test/integration/render-tests/fog/culling/opacity/expected.png differ
diff --git a/test/integration/render-tests/fog/culling/opacity/style.json b/test/integration/render-tests/fog/culling/opacity/style.json
new file mode 100644
index 00000000000..70637edc4f3
--- /dev/null
+++ b/test/integration/render-tests/fog/culling/opacity/style.json
@@ -0,0 +1,60 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "debug": true,
+ "height": 512,
+ "width": 64
+ }
+ },
+ "center": [
+ 0.1,
+ 0.1
+ ],
+ "pitch": 70,
+ "bearing": 90,
+ "zoom": 15,
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Feature",
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ -180,
+ 0
+ ],
+ [
+ 180,
+ 0.0
+ ]
+ ]
+ }
+ }
+ }
+ },
+ "fog": {
+ "range": [1, 1.2],
+ "color": "rgba(0, 0, 255, 0.8)"
+ },
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "beige"
+ }
+ },
+ {
+ "id": "dash",
+ "type": "line",
+ "source": "geojson",
+ "paint": {
+ "line-color": "green",
+ "line-width": 30
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/fog/disable/expected.png b/test/integration/render-tests/fog/disable/expected.png
new file mode 100644
index 00000000000..64ef56df5c6
Binary files /dev/null and b/test/integration/render-tests/fog/disable/expected.png differ
diff --git a/test/integration/render-tests/fog/disable/style.json b/test/integration/render-tests/fog/disable/style.json
new file mode 100644
index 00000000000..aac21e2e0ac
--- /dev/null
+++ b/test/integration/render-tests/fog/disable/style.json
@@ -0,0 +1,63 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "height": 256,
+ "width": 256,
+ "operations": [
+ ["wait", 500],
+ ["setFog", null],
+ ["wait", 500]
+ ]
+ }
+ },
+ "center": [-113.32296, 35.94662],
+ "zoom": 12.1,
+ "pitch": 80,
+ "bearing": 64.5,
+ "terrain": {
+ "source": "rgbterrain"
+ },
+ "fog": {
+ "range": [1, 2],
+ "color": "white",
+ "horizon-blend": 0.15
+ },
+ "sources": {
+ "rgbterrain": {
+ "type": "raster-dem",
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.terrain.png"
+ ],
+ "maxzoom": 12,
+ "tileSize": 256
+ },
+ "satellite": {
+ "type": "raster",
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.satellite.png"
+ ],
+ "maxzoom": 17,
+ "tileSize": 256
+ }
+ },
+ "layers": [
+ {
+ "id": "sky",
+ "type": "sky",
+ "paint": {
+ "sky-type": "atmosphere",
+ "sky-atmosphere-sun": [0, 0],
+ "sky-atmosphere-sun-intensity": 15
+ }
+ },
+ {
+ "id": "raster",
+ "type": "raster",
+ "source": "satellite",
+ "paint": {
+ "raster-fade-duration": 0
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/integration/render-tests/fog/reenable/expected.png b/test/integration/render-tests/fog/reenable/expected.png
new file mode 100644
index 00000000000..09066b8c9df
Binary files /dev/null and b/test/integration/render-tests/fog/reenable/expected.png differ
diff --git a/test/integration/render-tests/fog/reenable/style.json b/test/integration/render-tests/fog/reenable/style.json
new file mode 100644
index 00000000000..58efad08242
--- /dev/null
+++ b/test/integration/render-tests/fog/reenable/style.json
@@ -0,0 +1,59 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "height": 256,
+ "width": 256,
+ "operations": [
+ ["wait", 500],
+ ["setFog", null],
+ ["setFog", {
+ "range": [1, 2],
+ "color": "blue",
+ "horizon-blend": 0.15
+ }],
+ ["wait", 500]
+ ]
+ }
+ },
+ "center": [-113.32296, 35.94662],
+ "zoom": 12.1,
+ "pitch": 80,
+ "bearing": 64.5,
+ "terrain": {
+ "source": "rgbterrain"
+ },
+ "fog": {
+ "range": [1, 2],
+ "color": "white",
+ "horizon-blend": 0.15
+ },
+ "sources": {
+ "rgbterrain": {
+ "type": "raster-dem",
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.terrain.png"
+ ],
+ "maxzoom": 12,
+ "tileSize": 256
+ },
+ "satellite": {
+ "type": "raster",
+ "tiles": [
+ "local://tiles/{z}-{x}-{y}.satellite.png"
+ ],
+ "maxzoom": 17,
+ "tileSize": 256
+ }
+ },
+ "layers": [
+ {
+ "id": "raster",
+ "type": "raster",
+ "source": "satellite",
+ "paint": {
+ "raster-fade-duration": 0
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/unit/style-spec/fixture/fog-invalid-color.input.json b/test/unit/style-spec/fixture/fog-invalid-color.input.json
deleted file mode 100644
index d000388c5a1..00000000000
--- a/test/unit/style-spec/fixture/fog-invalid-color.input.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "version": 8,
- "sources": {
- "vector": {
- "type": "vector",
- "url": "mapbox://mapbox.mapbox-streets-v5"
- }
- },
- "layers": [],
- "fog": {
- "range": [1, 2],
- "color": "rgba(50,100,150,0)"
- }
-}
diff --git a/test/unit/style-spec/fixture/fog-invalid-color.output-api-supported.json b/test/unit/style-spec/fixture/fog-invalid-color.output-api-supported.json
deleted file mode 100644
index b8bb172b823..00000000000
--- a/test/unit/style-spec/fixture/fog-invalid-color.output-api-supported.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {
- "line": 10,
- "message": "fog: fog.color alpha must be nonzero."
- }
-]
diff --git a/test/unit/style-spec/fixture/fog-invalid-color.output.json b/test/unit/style-spec/fixture/fog-invalid-color.output.json
deleted file mode 100644
index b8bb172b823..00000000000
--- a/test/unit/style-spec/fixture/fog-invalid-color.output.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- {
- "line": 10,
- "message": "fog: fog.color alpha must be nonzero."
- }
-]