bevy_pbr/
fog.rs

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