Skip to content

Commit

Permalink
Add haziness property to fog (#10537)
Browse files Browse the repository at this point in the history
* Add haziness to fog

* Fix flow types

* Apply PR feedback
  • Loading branch information
rreusser authored and karimnaaji committed May 4, 2021
1 parent a2854e3 commit 6c807d7
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 47 deletions.
50 changes: 40 additions & 10 deletions debug/fog.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
this.fogRangeStart = 1.5;
this.fogRangeEnd = 2.0;
this.fogColor = [255, 255, 255];
this.fogHazeColor = [109, 123, 180];
this.fogHazeEnergy = 1.0;
this.fogStrength = 1.0;
this.fogSkyBlend = 0.1;
this.showTileBoundaries = false;
};
Expand Down Expand Up @@ -100,37 +103,61 @@
map.setFog(value ? {
"range": [guiParams.fogRangeStart, guiParams.fogRangeEnd],
"color": 'rgba(' + guiParams.fogColor[0] + ', ' + guiParams.fogColor[1] + ', ' + guiParams.fogColor[2] + ', 1.0)',
"haze-color": 'rgba(' + guiParams.fogHazeColor[0] + ', ' + guiParams.fogHazeColor[1] + ', ' + guiParams.fogHazeColor[2] + ', 1.0)',
"haze-energy": guiParams.fogHazeEnergy,
"strength": guiParams.fogStrength,
"sky-blend": guiParams.fogSkyBlend
} : null);
});

var fogColor = fog.addColor(guiParams, 'fogColor');
fogColor.onFinishChange((value) => {
map.setFog({
"color": 'rgba(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', 1.0)',
});
});

var fogRangeStart = fog.add(guiParams, 'fogRangeStart', 0.0, 15.0);
fogRangeStart.onFinishChange((value) => {
fogRangeStart.onChange((value) => {
map.setFog({
"range": [value, guiParams.fogRangeEnd],
});
});

var fogRangeEnd = fog.add(guiParams, 'fogRangeEnd', 0.0, 15.0);
fogRangeEnd.onFinishChange((value) => {
fogRangeEnd.onChange((value) => {
map.setFog({
"range": [guiParams.fogRangeStart, value],
});
});

var fogStrength = fog.add(guiParams, 'fogStrength', 0, 1);
fogStrength.onChange((value) => {
map.setFog({
"strength": value,
});
});

var fogHazeEnergy = fog.add(guiParams, 'fogHazeEnergy', 0, 2);
fogHazeEnergy.onChange((value) => {
map.setFog({
"haze-energy": value,
});
});

var fogSkyBlend = fog.add(guiParams, 'fogSkyBlend', 0.0, 1.0);
fogSkyBlend.onFinishChange((value) => {
fogSkyBlend.onChange((value) => {
map.setFog({
"sky-blend": value,
});
});

var fogColor = fog.addColor(guiParams, 'fogColor');
fogColor.onChange((value) => {
map.setFog({
"color": 'rgba(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', 1.0)',
});
});

var fogHazeColor = fog.addColor(guiParams, 'fogHazeColor');
fogHazeColor.onChange((value) => {
map.setFog({
"haze-color": 'rgba(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', 1.0)',
});
});
};

var popup = new mapboxgl.Popup().setHTML('Pitch Alignment: auto<br>Rotation Alignment: auto');
Expand Down Expand Up @@ -190,6 +217,9 @@
map.setFog(guiParams.enableFog ? {
"range": [guiParams.fogRangeStart, guiParams.fogRangeEnd],
"color": 'rgba(' + guiParams.fogColor[0] + ', ' + guiParams.fogColor[1] + ', ' + guiParams.fogColor[2] + ', 1.0)',
"haze-color": 'rgba(' + guiParams.fogHazeColor[0] + ', ' + guiParams.fogHazeColor[1] + ', ' + guiParams.fogHazeColor[2] + ', 1.0)',
"strength": guiParams.fogStrength,
"haze-energy": guiParams.fogHazeEnergy,
"sky-blend": guiParams.fogSkyBlend
} : null);

Expand Down
2 changes: 1 addition & 1 deletion src/css/mapbox-gl.css
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
}

.mapboxgl-marker-occluded {
opacity: 0.0;
opacity: 0;
}

.mapboxgl-marker-occluded-low {
Expand Down
6 changes: 6 additions & 0 deletions src/render/fog.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@ export type FogUniformsType = {|
'u_cam_matrix': UniformMatrix4f,
'u_fog_range': Uniform2f,
'u_fog_color': Uniform3f,
'u_fog_exponent': Uniform1f,
'u_fog_opacity': Uniform1f,
'u_fog_sky_blend': Uniform1f,
'u_fog_temporal_offset': Uniform1f,
'u_haze_color_linear': Uniform3f,
'u_haze_energy': Uniform1f,
|};

export const fogUniforms = (context: Context, locations: UniformLocations): FogUniformsType => ({
'u_cam_matrix': new UniformMatrix4f(context, locations.u_cam_matrix),
'u_fog_range': new Uniform2f(context, locations.u_fog_range),
'u_fog_color': new Uniform3f(context, locations.u_fog_color),
'u_fog_exponent': new Uniform1f(context, locations.u_fog_exponent),
'u_fog_opacity': new Uniform1f(context, locations.u_fog_opacity),
'u_fog_sky_blend': new Uniform1f(context, locations.u_fog_sky_blend),
'u_fog_temporal_offset': new Uniform1f(context, locations.u_fog_temporal_offset),
'u_haze_color_linear': new Uniform3f(context, locations.u_haze_color_linear),
'u_haze_energy': new Uniform1f(context, locations.u_haze_energy),
});
24 changes: 18 additions & 6 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,14 @@ class Painter {
if (fog) {
const fogStart = fog.properties.get('range')[0];
const fogEnd = fog.properties.get('range')[1];
// We start culling at 80% between the fog start and end,
// leaving a non-noticeable 2% fog opacity change threshold.
const fogCullDist = fogStart + (fogEnd - fogStart) * 0.8;

this.transform.fogCullDistSq = fogCullDist * fogCullDist;
const fogStrength = fog.properties.get('strength');
// We start culling where the fog opacity function hits 98%, leaving
// a non-noticeable opacity change threshold. We use an arbitrary function
// which bounds the true answer. See: https://www.desmos.com/calculator/lw03ldsuhy
const fogBoundFraction = 1 - 0.22 * Math.exp(4 * (fogStrength - 1));
const fogCullDist = fogStart + (fogEnd - fogStart) * fogBoundFraction;

this.transform.fogCullDistSq = fogCullDist * fogCullDist;
this.transform.fogCulling = this.transform.pitch > FOG_PITCH_END;
} else {
this.transform.fogCullDistSq = null;
Expand Down Expand Up @@ -749,12 +752,16 @@ 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 haze = fog && fog.properties && fog.properties.get('haze-energy') > 0;

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 && !rtt) defines.push('FOG');
if (fog && !rtt) {
defines.push('FOG');
if (haze) defines.push('FOG_HAZE');
}
if (rtt) defines.push('RENDER_TO_TEXTURE');
if (this._showOverdrawInspector) defines.push('OVERDRAW_INSPECTOR');
return defines;
Expand Down Expand Up @@ -836,14 +843,19 @@ class Painter {
if (fog) {
const temporalOffset = (this.frameCounter / 1000.0) % 1;
const fogColor = fog.properties.get('color');
const hazeColor = fog.properties.get('haze-color');
const hazeColorLinear = [Math.pow(hazeColor.r, 2.2), Math.pow(hazeColor.g, 2.2), Math.pow(hazeColor.b, 2.2)];
const uniforms = {};

uniforms['u_cam_matrix'] = tileID ? this.transform.calculateCameraMatrix(tileID) : this.identityMat;
uniforms['u_fog_range'] = fog.properties.get('range');
uniforms['u_fog_color'] = [fogColor.r, fogColor.g, fogColor.b];
uniforms['u_fog_exponent'] = Math.max(1e-3, 12 * Math.pow(1 - fog.properties.get('strength'), 2));
uniforms['u_fog_opacity'] = fog.getFogPitchFactor(this.transform.pitch);
uniforms['u_fog_sky_blend'] = fog.properties.get('sky-blend');
uniforms['u_fog_temporal_offset'] = temporalOffset;
uniforms['u_haze_color_linear'] = hazeColorLinear;
uniforms['u_haze_energy'] = fog.properties.get('haze-energy');

program.setFogUniformValues(context, uniforms);
}
Expand Down
4 changes: 3 additions & 1 deletion src/render/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ class Program<Us: UniformBindings> {
context.program.set(this.program);

for (const name in fogUniformsValues) {
uniforms[name].set(fogUniformsValues[name]);
if (uniforms[name].location) {
uniforms[name].set(fogUniformsValues[name]);
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/shaders/_prelude.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ vec3 dither(vec3 color, highp vec2 seed) {
vec3 rnd = hash(seed) + hash(seed + 0.59374) - 0.5;
return color + rnd / 255.0;
}

vec3 linear_to_srgb(vec3 color) {
return pow(color, vec3(1.0 / 2.2));
}

vec3 srgb_to_linear(vec3 color) {
return pow(color, vec3(2.2));
}

72 changes: 46 additions & 26 deletions src/shaders/_prelude_fog.fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@

uniform vec2 u_fog_range;
uniform vec3 u_fog_color;
uniform vec3 u_haze_color_linear;
uniform float u_haze_energy;
uniform float u_fog_opacity;
uniform float u_fog_sky_blend;
uniform float u_fog_temporal_offset;
uniform float u_fog_exponent;

vec3 tonemap(vec3 color) {
// Use an exponential smoothmin between y=x and y=1 for tone-mapping
// See: https://www.desmos.com/calculator/h8odggcnd0
const float k = 8.0;
return max(vec3(0), log2(exp2(-k * color) + exp2(-k)) * (-1.0 / k));
}

// Assumes z up and camera_dir *normalized* (to avoid computing its length multiple
// times for different functions).
Expand All @@ -15,45 +25,55 @@ float fog_sky_blending(vec3 camera_dir) {
return u_fog_opacity * exp(-3.0 * t * t);
}

float fog_opacity(vec3 pos) {
float depth = length(pos);
float start = u_fog_range.x;
float end = u_fog_range.y;

// The fog is not physically accurate, so we seek an expression which satisfies a
// couple basic constraints:
// - opacity should be 0 at the near limit
// - opacity should be 1 at the far limit
// - the onset should have smooth derivatives to avoid a sharp band
// To this end, we use an (1 - e^x)^n, where n is set to 3 to ensure the
// function is C2 continuous at the onset. The fog is about 99% opaque at
// the far limit, so we simply scale it and clip to achieve 100% opacity.
// https://www.desmos.com/calculator/3taufutxid
const float decay = 5.5;
float falloff = max(0.0, 1.0 - exp(-decay * (depth - start) / (end - start)));
// Computes the fog opacity when fog strength = 1. Otherwise it's multiplied
// by a smoothstep to a power to decrease the amount of fog relative to haze.
// - t: depth, rescaled to 0 at fogStart and 1 at fogEnd
// See: https://www.desmos.com/calculator/3taufutxid
// This function much match src/style/fog.js
float fog_opacity(float t) {
const float decay = 6.0;
float falloff = 1.0 - min(1.0, exp(-decay * t));

// Cube without pow()
falloff *= falloff * falloff;

// Scale and clip to 1 at the far limit
falloff = min(1.0, 1.00747 * falloff);
return u_fog_opacity * min(1.0, 1.00747 * falloff);
}

// This function is only used in rare places like heatmap where opacity is used
// directly, outside the normal fog_apply method.
float fog_opacity (vec3 pos) {
return fog_opacity((length(pos) - u_fog_range.x) / (u_fog_range.y - u_fog_range.x));
}

vec3 fog_apply(vec3 color, vec3 pos) {
// Map [near, far] to [0, 1]
float t = (length(pos) - u_fog_range.x) / (u_fog_range.y - u_fog_range.x);

return falloff * u_fog_opacity;
float haze_opac = fog_opacity(pos);
float fog_opac = haze_opac * pow(smoothstep(0.0, 1.0, t), u_fog_exponent);

#ifdef FOG_HAZE
vec3 haze = (haze_opac * u_haze_energy) * u_haze_color_linear;

// The smoothstep fades in tonemapping slightly before the fog layer. This causes
// the principle that fog should not have an effect outside the fog layer, but the
// effect is hardly noticeable except on pure white glaciers..
float tonemap_strength = u_fog_opacity * min(1.0, u_haze_energy) * smoothstep(-0.5, 0.25, t);
color = srgb_to_linear(color);
color = mix(color, tonemap(color + haze), tonemap_strength);
color = linear_to_srgb(color);
#endif

return mix(color, u_fog_color, fog_opac);
}

// Assumes z up
vec3 fog_apply_sky_gradient(vec3 camera_ray, vec3 sky_color) {
return mix(sky_color, u_fog_color, fog_sky_blending(normalize(camera_ray)));
}

vec3 fog_apply(vec3 color, vec3 pos) {
// We mix in sRGB color space. sRGB roughly corrects for perceived brightness
// so that dark fog and light fog obscure similarly for otherwise identical
// parameters. If we blend in linear RGB, then the parameters to control dark
// and light fog are fundamentally different.
return mix(color, u_fog_color, fog_opacity(pos));
}

// Un-premultiply the alpha, then blend fog, then re-premultiply alpha. For
// use with colors using premultiplied alpha
vec4 fog_apply_premultiplied(vec4 color, vec3 pos) {
Expand Down
65 changes: 65 additions & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -3731,6 +3731,71 @@
}
}
},
"haze-color": {
"type": "color",
"property-type": "data-constant",
"default": "#7287d5",
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "",
"sdk-support": {
"basic functionality": {
"js": "",
"android": "",
"ios": "",
"macos": ""
}
}
},
"strength": {
"type": "number",
"property-type": "data-constant",
"default": 1.0,
"minimum": 0.0,
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "",
"sdk-support": {
"basic functionality": {
"js": "",
"android": "",
"ios": "",
"macos": ""
}
}
},
"haze-energy": {
"type": "number",
"property-type": "data-constant",
"default": 1.0,
"minimum": 0.0,
"expression": {
"interpolated": true,
"parameters": [
"zoom"
]
},
"transition": true,
"doc": "",
"sdk-support": {
"basic functionality": {
"js": "",
"android": "",
"ios": "",
"macos": ""
}
}
},
"sky-blend": {
"type": "number",
"property-type": "data-constant",
Expand Down
3 changes: 3 additions & 0 deletions src/style-spec/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export type TerrainSpecification = {|
export type FogSpecification = {|
"range"?: PropertyValueSpecification<[number, number]>,
"color"?: PropertyValueSpecification<ColorSpecification>,
"haze-color"?: PropertyValueSpecification<ColorSpecification>,
"strength"?: PropertyValueSpecification<number>,
"haze-energy"?: PropertyValueSpecification<number>,
"sky-blend"?: PropertyValueSpecification<number>
|}

Expand Down
Loading

0 comments on commit 6c807d7

Please sign in to comment.