Skip to main content

bevy_pbr/
fog.rs

1use bevy_camera::Camera;
2use bevy_color::{Color, ColorToComponents, LinearRgba};
3use bevy_ecs::prelude::*;
4use bevy_math::{ops, Vec3};
5use bevy_reflect::{std_traits::ReflectDefault, Reflect};
6use bevy_render::{extract_component::ExtractComponent, RenderApp};
7
8use crate::ViewFogUniformOffset;
9
10/// Configures the “classic” computer graphics [distance fog](https://en.wikipedia.org/wiki/Distance_fog) effect,
11/// in which objects appear progressively more covered in atmospheric haze the further away they are from the camera.
12/// Affects meshes rendered via the PBR [`StandardMaterial`](crate::StandardMaterial).
13///
14/// ## Falloff
15///
16/// The rate at which fog intensity increases with distance is controlled by the falloff mode.
17/// Currently, the following fog falloff modes are supported:
18///
19/// - [`FogFalloff::Linear`]
20/// - [`FogFalloff::Exponential`]
21/// - [`FogFalloff::ExponentialSquared`]
22/// - [`FogFalloff::Atmospheric`]
23///
24/// ## Example
25///
26/// ```
27/// # use bevy_ecs::prelude::*;
28/// # use bevy_render::prelude::*;
29/// # use bevy_camera::prelude::*;
30/// # use bevy_pbr::prelude::*;
31/// # use bevy_color::Color;
32/// # fn system(mut commands: Commands) {
33/// commands.spawn((
34///     // Setup your camera as usual
35///     Camera3d::default(),
36///     // Add fog to the same entity
37///     DistanceFog {
38///         color: Color::WHITE,
39///         falloff: FogFalloff::Exponential { density: 1e-3 },
40///         ..Default::default()
41///     },
42/// ));
43/// # }
44/// # bevy_ecs::system::assert_is_system(system);
45/// ```
46///
47/// ## Material Override
48///
49/// Once enabled for a specific camera, the fog effect can also be disabled for individual
50/// [`StandardMaterial`](crate::StandardMaterial) instances via the `fog_enabled` flag.
51#[derive(Debug, Clone, Component, Reflect, ExtractComponent)]
52#[extract_component_filter(With<Camera>)]
53#[extract_component_sync_target((Self, ViewFogUniformOffset))]
54#[reflect(Component, Default, Debug, Clone)]
55#[extract_app(RenderApp)]
56pub struct DistanceFog {
57    /// The color of the fog effect.
58    ///
59    /// **Tip:** The alpha channel of the color can be used to “modulate” the fog effect without
60    /// changing the fog falloff mode or parameters.
61    pub color: Color,
62
63    /// Color used to modulate the influence of directional light colors on the
64    /// fog, where the view direction aligns with each directional light direction,
65    /// producing a “glow” or light dispersion effect. (e.g. around the sun)
66    ///
67    /// Use [`Color::NONE`] to disable the effect.
68    pub directional_light_color: Color,
69
70    /// The exponent applied to the directional light alignment calculation.
71    /// A higher value means a more concentrated “glow”.
72    pub directional_light_exponent: f32,
73
74    /// Determines which falloff mode to use, and its parameters.
75    pub falloff: FogFalloff,
76}
77
78/// Allows switching between different fog falloff modes, and configuring their parameters.
79///
80/// ## Convenience Methods
81///
82/// When using non-linear fog modes it can be hard to determine the right parameter values
83/// for a given scene.
84///
85/// For easier artistic control, instead of creating the enum variants directly, you can use the
86/// visibility-based convenience methods:
87///
88/// - For `FogFalloff::Exponential`:
89///     - [`FogFalloff::from_visibility()`]
90///     - [`FogFalloff::from_visibility_contrast()`]
91///
92/// - For `FogFalloff::ExponentialSquared`:
93///     - [`FogFalloff::from_visibility_squared()`]
94///     - [`FogFalloff::from_visibility_contrast_squared()`]
95///
96/// - For `FogFalloff::Atmospheric`:
97///     - [`FogFalloff::from_visibility_color()`]
98///     - [`FogFalloff::from_visibility_colors()`]
99///     - [`FogFalloff::from_visibility_contrast_color()`]
100///     - [`FogFalloff::from_visibility_contrast_colors()`]
101#[derive(Debug, Clone, Reflect)]
102#[reflect(Clone)]
103pub enum FogFalloff {
104    /// A linear fog falloff that grows in intensity between `start` and `end` distances.
105    ///
106    /// This falloff mode is simpler to control than other modes, however it can produce results that look “artificial”, depending on the scene.
107    ///
108    /// ## Formula
109    ///
110    /// The fog intensity for a given point in the scene is determined by the following formula:
111    ///
112    /// ```text
113    /// let fog_intensity = 1.0 - ((end - distance) / (end - start)).clamp(0.0, 1.0);
114    /// ```
115    ///
116    /// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
117    /// <title>Plot showing how linear fog falloff behaves for start and end values of 0.8 and 2.2, respectively.</title>
118    /// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
119    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
120    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
121    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
122    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
123    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
124    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
125    /// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
126    /// <path d="M43 150H117.227L263 48H331" stroke="#FF00E5"/>
127    /// <path d="M118 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
128    /// <path d="M263 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
129    /// <text font-family="sans-serif" fill="#FF00E5" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0em"><tspan x="121" y="58.6364">start</tspan></text>
130    /// <text font-family="sans-serif" fill="#FF00E5" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0em"><tspan x="267" y="58.6364">end</tspan></text>
131    /// </svg>
132    Linear {
133        /// Distance from the camera where fog is completely transparent, in world units.
134        start: f32,
135
136        /// Distance from the camera where fog is completely opaque, in world units.
137        end: f32,
138    },
139
140    /// An exponential fog falloff with a given `density`.
141    ///
142    /// Initially gains intensity quickly with distance, then more slowly. Typically produces more natural results than [`FogFalloff::Linear`],
143    /// but is a bit harder to control.
144    ///
145    /// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
146    ///
147    /// ## Tips
148    ///
149    /// - Use the [`FogFalloff::from_visibility()`] convenience method to create an exponential falloff with the proper
150    ///   density for a desired visibility distance in world units;
151    /// - It's not _unusual_ to have very large or very small values for the density, depending on the scene
152    ///   scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values
153    ///   in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of
154    ///   density;
155    /// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
156    ///
157    /// ## Formula
158    ///
159    /// The fog intensity for a given point in the scene is determined by the following formula:
160    ///
161    /// ```text
162    /// let fog_intensity = 1.0 - 1.0 / (distance * density).exp();
163    /// ```
164    ///
165    /// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
166    /// <title>Plot showing how exponential fog falloff behaves for different density values</title>
167    /// <mask id="mask0_3_31" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
168    /// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
169    /// </mask>
170    /// <g mask="url(#mask0_3_31)">
171    /// <path d="M42 150C42 150 98.3894 53 254.825 53L662 53" stroke="#FF003D" stroke-width="1"/>
172    /// <path d="M42 150C42 150 139.499 53 409.981 53L1114 53" stroke="#001AFF" stroke-width="1"/>
173    /// <path d="M42 150C42 150 206.348 53 662.281 53L1849 53" stroke="#14FF00" stroke-width="1"/>
174    /// </g>
175    /// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
176    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
177    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
178    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
179    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
180    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
181    /// <text font-family="sans-serif" fill="#FF003D" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="77" y="64.6364">density = 2</tspan></text>
182    /// <text font-family="sans-serif" fill="#001AFF" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="236" y="76.6364">density = 1</tspan></text>
183    /// <text font-family="sans-serif" fill="#14FF00" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="205" y="115.636">density = 0.5</tspan></text>
184    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
185    /// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
186    /// </svg>
187    Exponential {
188        /// Multiplier applied to the world distance (within the exponential fog falloff calculation).
189        density: f32,
190    },
191
192    /// A squared exponential fog falloff with a given `density`.
193    ///
194    /// Similar to [`FogFalloff::Exponential`], but grows more slowly in intensity for closer distances
195    /// before “catching up”.
196    ///
197    /// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
198    ///
199    /// ## Tips
200    ///
201    /// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff
202    ///   with the proper density for a desired visibility distance in world units;
203    /// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
204    ///
205    /// ## Formula
206    ///
207    /// The fog intensity for a given point in the scene is determined by the following formula:
208    ///
209    /// ```text
210    /// let fog_intensity = 1.0 - 1.0 / (distance * density).squared().exp();
211    /// ```
212    ///
213    /// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
214    /// <title>Plot showing how exponential squared fog falloff behaves for different density values</title>
215    /// <mask id="mask0_1_3" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
216    /// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
217    /// </mask>
218    /// <g mask="url(#mask0_1_3)">
219    /// <path d="M42 150C75.4552 150 74.9241 53.1724 166.262 53.1724L404 53.1724" stroke="#FF003D" stroke-width="1"/>
220    /// <path d="M42 150C107.986 150 106.939 53.1724 287.091 53.1724L756 53.1724" stroke="#001AFF" stroke-width="1"/>
221    /// <path d="M42 150C166.394 150 164.42 53.1724 504.035 53.1724L1388 53.1724" stroke="#14FF00" stroke-width="1"/>
222    /// </g>
223    /// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
224    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
225    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
226    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
227    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
228    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
229    /// <text font-family="sans-serif" fill="#FF003D" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="61" y="54.6364">density = 2</tspan></text>
230    /// <text font-family="sans-serif" fill="#001AFF" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="168" y="84.6364">density = 1</tspan></text>
231    /// <text font-family="sans-serif" fill="#14FF00" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="174" y="121.636">density = 0.5</tspan></text>
232    /// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
233    /// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
234    /// </svg>
235    ExponentialSquared {
236        /// Multiplier applied to the world distance (within the exponential squared fog falloff calculation).
237        density: f32,
238    },
239
240    /// A more general form of the [`FogFalloff::Exponential`] mode. The falloff formula is separated into
241    /// two terms, `extinction` and `inscattering`, for a somewhat simplified atmospheric scattering model.
242    /// Additionally, individual color channels can have their own density values, resulting in a total of
243    /// six different configuration parameters.
244    ///
245    /// ## Tips
246    ///
247    /// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods
248    ///   to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and
249    ///   extinction and inscattering colors;
250    /// - Combine the atmospheric fog parameters with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
251    ///
252    /// ## Formula
253    ///
254    /// Unlike other modes, atmospheric falloff doesn't use a simple intensity-based blend of fog color with
255    /// object color. Instead, it calculates per-channel extinction and inscattering factors, which are
256    /// then used to calculate the final color.
257    ///
258    /// ```text
259    /// let extinction_factor = 1.0 - 1.0 / (distance * extinction).exp();
260    /// let inscattering_factor = 1.0 - 1.0 / (distance * inscattering).exp();
261    /// let result = input_color * (1.0 - extinction_factor) + fog_color * inscattering_factor;
262    /// ```
263    ///
264    /// ## Equivalence to [`FogFalloff::Exponential`]
265    ///
266    /// For a density value of `D`, the following two falloff modes will produce identical visual results:
267    ///
268    /// ```
269    /// # use bevy_pbr::prelude::*;
270    /// # use bevy_math::prelude::*;
271    /// # const D: f32 = 0.5;
272    /// #
273    /// let exponential = FogFalloff::Exponential {
274    ///     density: D,
275    /// };
276    ///
277    /// let atmospheric = FogFalloff::Atmospheric {
278    ///     extinction: Vec3::new(D, D, D),
279    ///     inscattering: Vec3::new(D, D, D),
280    /// };
281    /// ```
282    ///
283    /// **Note:** While the results are identical, [`FogFalloff::Atmospheric`] is computationally more expensive.
284    Atmospheric {
285        /// Controls how much light is removed due to atmospheric “extinction”, i.e. loss of light due to
286        /// photons being absorbed by atmospheric particles.
287        ///
288        /// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
289        /// [`FogFalloff::Exponential`]: Multiplier applied to the world distance (within the fog
290        /// falloff calculation) for that specific channel.
291        ///
292        /// **Note:**
293        /// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
294        /// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
295        extinction: Vec3,
296
297        /// Controls how much light is added due to light scattering from the sun through the atmosphere.
298        ///
299        /// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
300        /// [`FogFalloff::Exponential`]: A multiplier applied to the world distance (within the fog
301        /// falloff calculation) for that specific channel.
302        ///
303        /// **Note:**
304        /// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
305        /// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
306        inscattering: Vec3,
307    },
308}
309
310impl FogFalloff {
311    /// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
312    /// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
313    pub fn from_visibility(visibility: f32) -> FogFalloff {
314        FogFalloff::from_visibility_contrast(
315            visibility,
316            FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
317        )
318    }
319
320    /// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
321    /// and a given contrast threshold in the range of `0.0` to `1.0`.
322    pub fn from_visibility_contrast(visibility: f32, contrast_threshold: f32) -> FogFalloff {
323        FogFalloff::Exponential {
324            density: FogFalloff::koschmieder(visibility, contrast_threshold),
325        }
326    }
327
328    /// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
329    /// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
330    pub fn from_visibility_squared(visibility: f32) -> FogFalloff {
331        FogFalloff::from_visibility_contrast_squared(
332            visibility,
333            FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
334        )
335    }
336
337    /// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
338    /// and a given contrast threshold in the range of `0.0` to `1.0`.
339    pub fn from_visibility_contrast_squared(
340        visibility: f32,
341        contrast_threshold: f32,
342    ) -> FogFalloff {
343        FogFalloff::ExponentialSquared {
344            density: (FogFalloff::koschmieder(visibility, contrast_threshold) / visibility).sqrt(),
345        }
346    }
347
348    /// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
349    /// and a shared color for both extinction and inscattering, using the revised Koschmieder contrast threshold,
350    /// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
351    pub fn from_visibility_color(
352        visibility: f32,
353        extinction_inscattering_color: Color,
354    ) -> FogFalloff {
355        FogFalloff::from_visibility_contrast_colors(
356            visibility,
357            FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
358            extinction_inscattering_color,
359            extinction_inscattering_color,
360        )
361    }
362
363    /// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
364    /// extinction and inscattering colors, using the revised Koschmieder contrast threshold,
365    /// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
366    ///
367    /// ## Tips
368    /// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
369    /// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
370    /// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
371    pub fn from_visibility_colors(
372        visibility: f32,
373        extinction_color: Color,
374        inscattering_color: Color,
375    ) -> FogFalloff {
376        FogFalloff::from_visibility_contrast_colors(
377            visibility,
378            FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
379            extinction_color,
380            inscattering_color,
381        )
382    }
383
384    /// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
385    /// a contrast threshold in the range of `0.0` to `1.0`, and a shared color for both extinction and inscattering.
386    pub fn from_visibility_contrast_color(
387        visibility: f32,
388        contrast_threshold: f32,
389        extinction_inscattering_color: Color,
390    ) -> FogFalloff {
391        FogFalloff::from_visibility_contrast_colors(
392            visibility,
393            contrast_threshold,
394            extinction_inscattering_color,
395            extinction_inscattering_color,
396        )
397    }
398
399    /// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
400    /// a contrast threshold in the range of `0.0` to `1.0`, extinction and inscattering colors.
401    ///
402    /// ## Tips
403    /// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
404    /// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
405    /// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
406    pub fn from_visibility_contrast_colors(
407        visibility: f32,
408        contrast_threshold: f32,
409        extinction_color: Color,
410        inscattering_color: Color,
411    ) -> FogFalloff {
412        use core::f32::consts::E;
413
414        let [r_e, g_e, b_e, a_e] = LinearRgba::from(extinction_color).to_f32_array();
415        let [r_i, g_i, b_i, a_i] = LinearRgba::from(inscattering_color).to_f32_array();
416
417        FogFalloff::Atmospheric {
418            extinction: Vec3::new(
419                // Values are subtracted from 1.0 here to preserve the intuitive/artistic meaning of
420                // colors, since they're later subtracted. (e.g. by giving a blue extinction color, you
421                // get blue and _not_ yellow results)
422                ops::powf(1.0 - r_e, E),
423                ops::powf(1.0 - g_e, E),
424                ops::powf(1.0 - b_e, E),
425            ) * FogFalloff::koschmieder(visibility, contrast_threshold)
426                * ops::powf(a_e, E),
427
428            inscattering: Vec3::new(ops::powf(r_i, E), ops::powf(g_i, E), ops::powf(b_i, E))
429                * FogFalloff::koschmieder(visibility, contrast_threshold)
430                * ops::powf(a_i, E),
431        }
432    }
433
434    /// A 2% contrast threshold was originally proposed by Koschmieder, being the
435    /// minimum visual contrast at which a human observer could detect an object.
436    /// We use a revised 5% contrast threshold, deemed more realistic for typical human observers.
437    pub const REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD: f32 = 0.05;
438
439    /// Calculates the extinction coefficient β, from V and Cₜ, where:
440    ///
441    /// - Cₜ is the contrast threshold, in the range of `0.0` to `1.0`
442    /// - V is the visibility distance in which a perfectly black object is still identifiable
443    ///   against the horizon sky within the contrast threshold
444    ///
445    /// We start with Koschmieder's equation:
446    ///
447    /// ```text
448    ///       -ln(Cₜ)
449    ///  V = ─────────
450    ///          β
451    /// ```
452    ///
453    /// Multiplying both sides by β/V, that gives us:
454    ///
455    /// ```text
456    ///       -ln(Cₜ)
457    ///  β = ─────────
458    ///          V
459    /// ```
460    ///
461    /// See:
462    /// - <https://en.wikipedia.org/wiki/Visibility>
463    /// - <https://www.biral.com/wp-content/uploads/2015/02/Introduction_to_visibility-v2-2.pdf>
464    pub fn koschmieder(v: f32, c_t: f32) -> f32 {
465        -ops::ln(c_t) / v
466    }
467}
468
469impl Default for DistanceFog {
470    fn default() -> Self {
471        DistanceFog {
472            color: Color::WHITE,
473            falloff: FogFalloff::Linear {
474                start: 0.0,
475                end: 100.0,
476            },
477            directional_light_color: Color::NONE,
478            directional_light_exponent: 8.0,
479        }
480    }
481}