diff --git a/src/render/fog.js b/src/render/fog.js index aeceeb4e18a..daa21ef7c5b 100644 --- a/src/render/fog.js +++ b/src/render/fog.js @@ -3,18 +3,20 @@ import Context from '../gl/context.js'; import type {UniformLocations} from './uniform_binding.js'; -import {Uniform1f, Uniform2f, Uniform3f, UniformMatrix4f} from './uniform_binding.js'; +import {Uniform1f, Uniform2f, Uniform3f, Uniform4f, UniformMatrix4f} from './uniform_binding.js'; export type FogUniformsType = {| 'u_fog_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, + 'u_haze_color_linear': Uniform4f, + + // Precision may differ, so we must pass uniforms separately for use in a vertex shader + 'u_fog_opacity': Uniform1f, + |}; export const fogUniforms = (context: Context, locations: UniformLocations): FogUniformsType => ({ @@ -22,9 +24,9 @@ export const fogUniforms = (context: Context, locations: UniformLocations): FogU '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), + 'u_haze_color_linear': new Uniform4f(context, locations.u_haze_color_linear), + + 'u_fog_opacity': new Uniform1f(context, locations.u_fog_opacity), }); diff --git a/src/render/painter.js b/src/render/painter.js index f5f0e6202e0..90bfd461410 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -752,13 +752,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 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) { + if (fog && fogOpacity !== 0.0 && !rtt) { defines.push('FOG'); if (haze) defines.push('FOG_HAZE'); } @@ -840,22 +841,31 @@ class Painter { prepareDrawProgram(context: Context, program: Program<*>, tileID: ?UnwrappedTileID) { const fog = this.style && this.style.fog; - if (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 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_fog_matrix'] = tileID ? this.transform.calculateFogTileMatrix(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'); + uniforms['u_fog_opacity'] = fogOpacity; + + if (fog.properties.get('haze-energy') > 0) { + 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), + fog.properties.get('haze-energy') + ]; + + uniforms['u_haze_color_linear'] = hazeColorLinear; + } program.setFogUniformValues(context, uniforms); } diff --git a/src/shaders/_prelude_fog.fragment.glsl b/src/shaders/_prelude_fog.fragment.glsl index 1454c82d950..2a92bad1cb9 100644 --- a/src/shaders/_prelude_fog.fragment.glsl +++ b/src/shaders/_prelude_fog.fragment.glsl @@ -1,13 +1,12 @@ #ifdef FOG -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; +uniform mediump vec2 u_fog_range; +uniform mediump float u_fog_opacity; +uniform mediump vec4 u_haze_color_linear; +uniform mediump float u_fog_exponent; vec3 tonemap(vec3 color) { // Use an exponential smoothmin between y=x and y=1 for tone-mapping @@ -29,7 +28,7 @@ float fog_sky_blending(vec3 camera_dir) { // 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 +// This function much match src/style/fog.js and _prelude_fog.vertex.glsl float fog_opacity(float t) { const float decay = 6.0; float falloff = 1.0 - min(1.0, exp(-decay * t)); @@ -43,7 +42,7 @@ float fog_opacity(float t) { // 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) { +float fog_opacity(vec3 pos) { return fog_opacity((length(pos) - u_fog_range.x) / (u_fog_range.y - u_fog_range.x)); } @@ -51,16 +50,16 @@ 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); - float haze_opac = fog_opacity(pos); + float haze_opac = fog_opacity(t); 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; + vec3 haze = (haze_opac * u_haze_color_linear.a) * u_haze_color_linear.rgb; - // The smoothstep fades in tonemapping slightly before the fog layer. This causes + // The smoothstep fades in tonemapping slightly before the fog layer. This violates // 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); + // effect is hardly noticeable except on pure white glaciers. + float tonemap_strength = u_fog_opacity * min(1.0, u_haze_color_linear.a) * smoothstep(-0.5, 0.25, t); color = srgb_to_linear(color); color = mix(color, tonemap(color + haze), tonemap_strength); color = linear_to_srgb(color); @@ -69,6 +68,21 @@ vec3 fog_apply(vec3 color, vec3 pos) { return mix(color, u_fog_color, fog_opac); } +// Apply fog and haze which were computed in the vertex shader +vec3 fog_apply_from_vert(vec3 color, float fog_opac +#ifdef FOG_HAZE + , vec4 haze +#endif +) { +#ifdef FOG_HAZE + color = srgb_to_linear(color); + color = mix(color, tonemap(color + haze.rgb), haze.a); + 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))); diff --git a/src/shaders/_prelude_fog.vertex.glsl b/src/shaders/_prelude_fog.vertex.glsl index 4e562ba86bd..62d2a8ec20e 100644 --- a/src/shaders/_prelude_fog.vertex.glsl +++ b/src/shaders/_prelude_fog.vertex.glsl @@ -1,6 +1,18 @@ #ifdef FOG uniform mat4 u_fog_matrix; +uniform mediump float u_fog_opacity; +uniform mediump float u_fog_exponent; +uniform mediump vec2 u_fog_range; +uniform mediump vec4 u_haze_color_linear; + +// This function much match fog_opacity defined in _prelude_fog.fragment.glsl +float fog_opacity(float t) { + const float decay = 6.0; + float falloff = 1.0 - min(1.0, exp(-decay * t)); + falloff *= falloff * falloff; + return u_fog_opacity * min(1.0, 1.00747 * falloff); +} vec3 fog_position(vec3 pos) { // The following function requires that u_fog_matrix be affine and result in @@ -13,4 +25,26 @@ vec3 fog_position(vec2 pos) { return fog_position(vec3(pos, 0)); } +void fog_haze( + vec3 pos, out float fog_opac +#ifdef FOG_HAZE + , out vec4 haze +#endif +) { + // Map [near, far] to [0, 1] + float t = (length(pos) - u_fog_range.x) / (u_fog_range.y - u_fog_range.x); + + float haze_opac = fog_opacity(t); + fog_opac = haze_opac * pow(smoothstep(0.0, 1.0, t), u_fog_exponent); + +#ifdef FOG_HAZE + haze.rgb = (haze_opac * u_haze_color_linear.a) * u_haze_color_linear.rgb; + + // The smoothstep fades in tonemapping slightly before the fog layer. This violates + // the principle that fog should not have an effect outside the fog layer, but the + // effect is hardly noticeable except on pure white glaciers. + haze.a = u_fog_opacity * min(1.0, u_haze_color_linear.a) * smoothstep(-0.5, 0.25, t); +#endif +} + #endif diff --git a/src/shaders/terrain_raster.fragment.glsl b/src/shaders/terrain_raster.fragment.glsl index 1fd25de87d9..16529153f9a 100644 --- a/src/shaders/terrain_raster.fragment.glsl +++ b/src/shaders/terrain_raster.fragment.glsl @@ -2,13 +2,20 @@ uniform sampler2D u_image0; varying vec2 v_pos0; #ifdef FOG -varying vec3 v_fog_pos; +varying float v_fog_opacity; +#ifdef FOG_HAZE +varying vec4 v_haze_color; +#endif #endif void main() { vec4 color = texture2D(u_image0, v_pos0); #ifdef FOG - color.rgb = fog_dither(fog_apply(color.rgb, v_fog_pos)); + color.rgb = fog_dither(fog_apply_from_vert(color.rgb, v_fog_opacity +#ifdef FOG_HAZE + , v_haze_color +#endif + )); #endif gl_FragColor = color; #ifdef TERRAIN_WIREFRAME diff --git a/src/shaders/terrain_raster.vertex.glsl b/src/shaders/terrain_raster.vertex.glsl index 0a108d73be7..15945db292b 100644 --- a/src/shaders/terrain_raster.vertex.glsl +++ b/src/shaders/terrain_raster.vertex.glsl @@ -7,7 +7,10 @@ attribute vec2 a_texture_pos; varying vec2 v_pos0; #ifdef FOG -varying vec3 v_fog_pos; +varying float v_fog_opacity; +#ifdef FOG_HAZE +varying vec4 v_haze_color; +#endif #endif const float skirtOffset = 24575.0; @@ -24,6 +27,10 @@ void main() { gl_Position = u_matrix * vec4(decodedPos, elevation, 1.0); #ifdef FOG - v_fog_pos = fog_position(vec3(decodedPos, elevation)); + fog_haze(fog_position(vec3(decodedPos, elevation)), v_fog_opacity +#ifdef FOG_HAZE + , v_haze_color +#endif + ); #endif }