bevy/crates/bevy_pbr/src/fog.rs
Gino Valente 9b32e09551
bevy_reflect: Add clone registrations project-wide (#18307)
# Objective

Now that #13432 has been merged, it's important we update our reflected
types to properly opt into this feature. If we do not, then this could
cause issues for users downstream who want to make use of
reflection-based cloning.

## Solution

This PR is broken into 4 commits:

1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that
are also `Clone`. This is mandatory as these types would otherwise cause
the cloning operation to fail for any type that contains it at any
depth.
2. Update the reflection example to suggest adding `#[reflect(Clone)]`
on opaque types.
3. Add `#[reflect(clone)]` attributes on all fields marked
`#[reflect(ignore)]` that are also `Clone`. This prevents the ignored
field from causing the cloning operation to fail.
   
Note that some of the types that contain these fields are also `Clone`,
and thus can be marked `#[reflect(Clone)]`. This makes the
`#[reflect(clone)]` attribute redundant. However, I think it's safer to
keep it marked in the case that the `Clone` impl/derive is ever removed.
I'm open to removing them, though, if people disagree.
4. Finally, I added `#[reflect(Clone)]` on all types that are also
`Clone`. While not strictly necessary, it enables us to reduce the
generated output since we can just call `Clone::clone` directly instead
of calling `PartialReflect::reflect_clone` on each variant/field. It
also means we benefit from any optimizations or customizations made in
the `Clone` impl, including directly dereferencing `Copy` values and
increasing reference counters.

Along with that change I also took the liberty of adding any missing
registrations that I saw could be applied to the type as well, such as
`Default`, `PartialEq`, and `Hash`. There were hundreds of these to
edit, though, so it's possible I missed quite a few.

That last commit is **_massive_**. There were nearly 700 types to
update. So it's recommended to review the first three before moving onto
that last one.

Additionally, I can break the last commit off into its own PR or into
smaller PRs, but I figured this would be the easiest way of doing it
(and in a timely manner since I unfortunately don't have as much time as
I used to for code contributions).

## Testing

You can test locally with a `cargo check`:

```
cargo check --workspace --all-features
```
2025-03-17 18:32:35 +00:00

477 lines
25 KiB
Rust

use bevy_color::{Color, ColorToComponents, LinearRgba};
use bevy_ecs::prelude::*;
use bevy_math::{ops, Vec3};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{extract_component::ExtractComponent, prelude::Camera};
/// Configures the “classic” computer graphics [distance fog](https://en.wikipedia.org/wiki/Distance_fog) effect,
/// in which objects appear progressively more covered in atmospheric haze the further away they are from the camera.
/// Affects meshes rendered via the PBR [`StandardMaterial`](crate::StandardMaterial).
///
/// ## Falloff
///
/// The rate at which fog intensity increases with distance is controlled by the falloff mode.
/// Currently, the following fog falloff modes are supported:
///
/// - [`FogFalloff::Linear`]
/// - [`FogFalloff::Exponential`]
/// - [`FogFalloff::ExponentialSquared`]
/// - [`FogFalloff::Atmospheric`]
///
/// ## Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_core_pipeline::prelude::*;
/// # use bevy_pbr::prelude::*;
/// # use bevy_color::Color;
/// # fn system(mut commands: Commands) {
/// commands.spawn((
/// // Setup your camera as usual
/// Camera3d::default(),
/// // Add fog to the same entity
/// DistanceFog {
/// color: Color::WHITE,
/// falloff: FogFalloff::Exponential { density: 1e-3 },
/// ..Default::default()
/// },
/// ));
/// # }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// ## Material Override
///
/// Once enabled for a specific camera, the fog effect can also be disabled for individual
/// [`StandardMaterial`](crate::StandardMaterial) instances via the `fog_enabled` flag.
#[derive(Debug, Clone, Component, Reflect, ExtractComponent)]
#[extract_component_filter(With<Camera>)]
#[reflect(Component, Default, Debug, Clone)]
pub struct DistanceFog {
/// The color of the fog effect.
///
/// **Tip:** The alpha channel of the color can be used to “modulate” the fog effect without
/// changing the fog falloff mode or parameters.
pub color: Color,
/// Color used to modulate the influence of directional light colors on the
/// fog, where the view direction aligns with each directional light direction,
/// producing a “glow” or light dispersion effect. (e.g. around the sun)
///
/// Use [`Color::NONE`] to disable the effect.
pub directional_light_color: Color,
/// The exponent applied to the directional light alignment calculation.
/// A higher value means a more concentrated “glow”.
pub directional_light_exponent: f32,
/// Determines which falloff mode to use, and its parameters.
pub falloff: FogFalloff,
}
/// Allows switching between different fog falloff modes, and configuring their parameters.
///
/// ## Convenience Methods
///
/// When using non-linear fog modes it can be hard to determine the right parameter values
/// for a given scene.
///
/// For easier artistic control, instead of creating the enum variants directly, you can use the
/// visibility-based convenience methods:
///
/// - For `FogFalloff::Exponential`:
/// - [`FogFalloff::from_visibility()`]
/// - [`FogFalloff::from_visibility_contrast()`]
///
/// - For `FogFalloff::ExponentialSquared`:
/// - [`FogFalloff::from_visibility_squared()`]
/// - [`FogFalloff::from_visibility_contrast_squared()`]
///
/// - For `FogFalloff::Atmospheric`:
/// - [`FogFalloff::from_visibility_color()`]
/// - [`FogFalloff::from_visibility_colors()`]
/// - [`FogFalloff::from_visibility_contrast_color()`]
/// - [`FogFalloff::from_visibility_contrast_colors()`]
#[derive(Debug, Clone, Reflect)]
#[reflect(Clone)]
pub enum FogFalloff {
/// A linear fog falloff that grows in intensity between `start` and `end` distances.
///
/// This falloff mode is simpler to control than other modes, however it can produce results that look “artificial”, depending on the scene.
///
/// ## Formula
///
/// The fog intensity for a given point in the scene is determined by the following formula:
///
/// ```text
/// let fog_intensity = 1.0 - ((end - distance) / (end - start)).clamp(0.0, 1.0);
/// ```
///
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
/// <title>Plot showing how linear fog falloff behaves for start and end values of 0.8 and 2.2, respectively.</title>
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <path d="M43 150H117.227L263 48H331" stroke="#FF00E5"/>
/// <path d="M118 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
/// <path d="M263 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
/// <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>
/// <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>
/// </svg>
Linear {
/// Distance from the camera where fog is completely transparent, in world units.
start: f32,
/// Distance from the camera where fog is completely opaque, in world units.
end: f32,
},
/// An exponential fog falloff with a given `density`.
///
/// Initially gains intensity quickly with distance, then more slowly. Typically produces more natural results than [`FogFalloff::Linear`],
/// but is a bit harder to control.
///
/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
///
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility()`] convenience method to create an exponential falloff with the proper
/// density for a desired visibility distance in world units;
/// - It's not _unusual_ to have very large or very small values for the density, depending on the scene
/// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values
/// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of
/// density;
/// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula
///
/// The fog intensity for a given point in the scene is determined by the following formula:
///
/// ```text
/// let fog_intensity = 1.0 - 1.0 / (distance * density).exp();
/// ```
///
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
/// <title>Plot showing how exponential fog falloff behaves for different density values</title>
/// <mask id="mask0_3_31" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
/// </mask>
/// <g mask="url(#mask0_3_31)">
/// <path d="M42 150C42 150 98.3894 53 254.825 53L662 53" stroke="#FF003D" stroke-width="1"/>
/// <path d="M42 150C42 150 139.499 53 409.981 53L1114 53" stroke="#001AFF" stroke-width="1"/>
/// <path d="M42 150C42 150 206.348 53 662.281 53L1849 53" stroke="#14FF00" stroke-width="1"/>
/// </g>
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// </svg>
Exponential {
/// Multiplier applied to the world distance (within the exponential fog falloff calculation).
density: f32,
},
/// A squared exponential fog falloff with a given `density`.
///
/// Similar to [`FogFalloff::Exponential`], but grows more slowly in intensity for closer distances
/// before “catching up”.
///
/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
///
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff
/// with the proper density for a desired visibility distance in world units;
/// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula
///
/// The fog intensity for a given point in the scene is determined by the following formula:
///
/// ```text
/// let fog_intensity = 1.0 - 1.0 / (distance * density).squared().exp();
/// ```
///
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
/// <title>Plot showing how exponential squared fog falloff behaves for different density values</title>
/// <mask id="mask0_1_3" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
/// </mask>
/// <g mask="url(#mask0_1_3)">
/// <path d="M42 150C75.4552 150 74.9241 53.1724 166.262 53.1724L404 53.1724" stroke="#FF003D" stroke-width="1"/>
/// <path d="M42 150C107.986 150 106.939 53.1724 287.091 53.1724L756 53.1724" stroke="#001AFF" stroke-width="1"/>
/// <path d="M42 150C166.394 150 164.42 53.1724 504.035 53.1724L1388 53.1724" stroke="#14FF00" stroke-width="1"/>
/// </g>
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// <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>
/// </svg>
ExponentialSquared {
/// Multiplier applied to the world distance (within the exponential squared fog falloff calculation).
density: f32,
},
/// A more general form of the [`FogFalloff::Exponential`] mode. The falloff formula is separated into
/// two terms, `extinction` and `inscattering`, for a somewhat simplified atmospheric scattering model.
/// Additionally, individual color channels can have their own density values, resulting in a total of
/// six different configuration parameters.
///
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods
/// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and
/// extinction and inscattering colors;
/// - Combine the atmospheric fog parameters with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula
///
/// Unlike other modes, atmospheric falloff doesn't use a simple intensity-based blend of fog color with
/// object color. Instead, it calculates per-channel extinction and inscattering factors, which are
/// then used to calculate the final color.
///
/// ```text
/// let extinction_factor = 1.0 - 1.0 / (distance * extinction).exp();
/// let inscattering_factor = 1.0 - 1.0 / (distance * inscattering).exp();
/// let result = input_color * (1.0 - extinction_factor) + fog_color * inscattering_factor;
/// ```
///
/// ## Equivalence to [`FogFalloff::Exponential`]
///
/// For a density value of `D`, the following two falloff modes will produce identical visual results:
///
/// ```
/// # use bevy_pbr::prelude::*;
/// # use bevy_math::prelude::*;
/// # const D: f32 = 0.5;
/// #
/// let exponential = FogFalloff::Exponential {
/// density: D,
/// };
///
/// let atmospheric = FogFalloff::Atmospheric {
/// extinction: Vec3::new(D, D, D),
/// inscattering: Vec3::new(D, D, D),
/// };
/// ```
///
/// **Note:** While the results are identical, [`FogFalloff::Atmospheric`] is computationally more expensive.
Atmospheric {
/// Controls how much light is removed due to atmospheric “extinction”, i.e. loss of light due to
/// photons being absorbed by atmospheric particles.
///
/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
/// [`FogFalloff::Exponential`]: Multiplier applied to the world distance (within the fog
/// falloff calculation) for that specific channel.
///
/// **Note:**
/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
extinction: Vec3,
/// Controls how much light is added due to light scattering from the sun through the atmosphere.
///
/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
/// [`FogFalloff::Exponential`]: A multiplier applied to the world distance (within the fog
/// falloff calculation) for that specific channel.
///
/// **Note:**
/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
inscattering: Vec3,
},
}
impl FogFalloff {
/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
pub fn from_visibility(visibility: f32) -> FogFalloff {
FogFalloff::from_visibility_contrast(
visibility,
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
)
}
/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
/// and a given contrast threshold in the range of `0.0` to `1.0`.
pub fn from_visibility_contrast(visibility: f32, contrast_threshold: f32) -> FogFalloff {
FogFalloff::Exponential {
density: FogFalloff::koschmieder(visibility, contrast_threshold),
}
}
/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
pub fn from_visibility_squared(visibility: f32) -> FogFalloff {
FogFalloff::from_visibility_contrast_squared(
visibility,
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
)
}
/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
/// and a given contrast threshold in the range of `0.0` to `1.0`.
pub fn from_visibility_contrast_squared(
visibility: f32,
contrast_threshold: f32,
) -> FogFalloff {
FogFalloff::ExponentialSquared {
density: (FogFalloff::koschmieder(visibility, contrast_threshold) / visibility).sqrt(),
}
}
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
/// and a shared color for both extinction and inscattering, using the revised Koschmieder contrast threshold,
/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
pub fn from_visibility_color(
visibility: f32,
extinction_inscattering_color: Color,
) -> FogFalloff {
FogFalloff::from_visibility_contrast_colors(
visibility,
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
extinction_inscattering_color,
extinction_inscattering_color,
)
}
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
/// extinction and inscattering colors, using the revised Koschmieder contrast threshold,
/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
///
/// ## Tips
/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
pub fn from_visibility_colors(
visibility: f32,
extinction_color: Color,
inscattering_color: Color,
) -> FogFalloff {
FogFalloff::from_visibility_contrast_colors(
visibility,
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
extinction_color,
inscattering_color,
)
}
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
/// a contrast threshold in the range of `0.0` to `1.0`, and a shared color for both extinction and inscattering.
pub fn from_visibility_contrast_color(
visibility: f32,
contrast_threshold: f32,
extinction_inscattering_color: Color,
) -> FogFalloff {
FogFalloff::from_visibility_contrast_colors(
visibility,
contrast_threshold,
extinction_inscattering_color,
extinction_inscattering_color,
)
}
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
/// a contrast threshold in the range of `0.0` to `1.0`, extinction and inscattering colors.
///
/// ## Tips
/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
pub fn from_visibility_contrast_colors(
visibility: f32,
contrast_threshold: f32,
extinction_color: Color,
inscattering_color: Color,
) -> FogFalloff {
use core::f32::consts::E;
let [r_e, g_e, b_e, a_e] = LinearRgba::from(extinction_color).to_f32_array();
let [r_i, g_i, b_i, a_i] = LinearRgba::from(inscattering_color).to_f32_array();
FogFalloff::Atmospheric {
extinction: Vec3::new(
// Values are subtracted from 1.0 here to preserve the intuitive/artistic meaning of
// colors, since they're later subtracted. (e.g. by giving a blue extinction color, you
// get blue and _not_ yellow results)
ops::powf(1.0 - r_e, E),
ops::powf(1.0 - g_e, E),
ops::powf(1.0 - b_e, E),
) * FogFalloff::koschmieder(visibility, contrast_threshold)
* ops::powf(a_e, E),
inscattering: Vec3::new(ops::powf(r_i, E), ops::powf(g_i, E), ops::powf(b_i, E))
* FogFalloff::koschmieder(visibility, contrast_threshold)
* ops::powf(a_i, E),
}
}
/// A 2% contrast threshold was originally proposed by Koschmieder, being the
/// minimum visual contrast at which a human observer could detect an object.
/// We use a revised 5% contrast threshold, deemed more realistic for typical human observers.
pub const REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD: f32 = 0.05;
/// Calculates the extinction coefficient β, from V and Cₜ, where:
///
/// - Cₜ is the contrast threshold, in the range of `0.0` to `1.0`
/// - V is the visibility distance in which a perfectly black object is still identifiable
/// against the horizon sky within the contrast threshold
///
/// We start with Koschmieder's equation:
///
/// ```text
/// -ln(Cₜ)
/// V = ─────────
/// β
/// ```
///
/// Multiplying both sides by β/V, that gives us:
///
/// ```text
/// -ln(Cₜ)
/// β = ─────────
/// V
/// ```
///
/// See:
/// - <https://en.wikipedia.org/wiki/Visibility>
/// - <https://www.biral.com/wp-content/uploads/2015/02/Introduction_to_visibility-v2-2.pdf>
pub fn koschmieder(v: f32, c_t: f32) -> f32 {
-ops::ln(c_t) / v
}
}
impl Default for DistanceFog {
fn default() -> Self {
DistanceFog {
color: Color::WHITE,
falloff: FogFalloff::Linear {
start: 0.0,
end: 100.0,
},
directional_light_color: Color::NONE,
directional_light_exponent: 8.0,
}
}
}