use bevy_camera::Camera;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;67/// 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/// ## Falloff12///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/// ## Example22///23/// ```24/// # use bevy_ecs::prelude::*;25/// # use bevy_render::prelude::*;26/// # use bevy_camera::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 usual32/// Camera3d::default(),33/// // Add fog to the same entity34/// 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 Override45///46/// Once enabled for a specific camera, the fog effect can also be disabled for individual47/// [`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, Clone)]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 without55/// changing the fog falloff mode or parameters.56pub color: Color,5758/// Color used to modulate the influence of directional light colors on the59/// 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.63pub directional_light_color: Color,6465/// The exponent applied to the directional light alignment calculation.66/// A higher value means a more concentrated “glow”.67pub directional_light_exponent: f32,6869/// Determines which falloff mode to use, and its parameters.70pub falloff: FogFalloff,71}7273/// Allows switching between different fog falloff modes, and configuring their parameters.74///75/// ## Convenience Methods76///77/// When using non-linear fog modes it can be hard to determine the right parameter values78/// for a given scene.79///80/// For easier artistic control, instead of creating the enum variants directly, you can use the81/// visibility-based convenience methods:82///83/// - For `FogFalloff::Exponential`:84/// - [`FogFalloff::from_visibility()`]85/// - [`FogFalloff::from_visibility_contrast()`]86///87/// - For `FogFalloff::ExponentialSquared`:88/// - [`FogFalloff::from_visibility_squared()`]89/// - [`FogFalloff::from_visibility_contrast_squared()`]90///91/// - For `FogFalloff::Atmospheric`:92/// - [`FogFalloff::from_visibility_color()`]93/// - [`FogFalloff::from_visibility_colors()`]94/// - [`FogFalloff::from_visibility_contrast_color()`]95/// - [`FogFalloff::from_visibility_contrast_colors()`]96#[derive(Debug, Clone, Reflect)]97#[reflect(Clone)]98pub enum FogFalloff {99/// A linear fog falloff that grows in intensity between `start` and `end` distances.100///101/// This falloff mode is simpler to control than other modes, however it can produce results that look “artificial”, depending on the scene.102///103/// ## Formula104///105/// The fog intensity for a given point in the scene is determined by the following formula:106///107/// ```text108/// let fog_intensity = 1.0 - ((end - distance) / (end - start)).clamp(0.0, 1.0);109/// ```110///111/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">112/// <title>Plot showing how linear fog falloff behaves for start and end values of 0.8 and 2.2, respectively.</title>113/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>114/// <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>115/// <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>116/// <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>117/// <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>118/// <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>119/// <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>120/// <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>121/// <path d="M43 150H117.227L263 48H331" stroke="#FF00E5"/>122/// <path d="M118 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>123/// <path d="M263 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>124/// <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>125/// <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>126/// </svg>127Linear {128/// Distance from the camera where fog is completely transparent, in world units.129start: f32,130131/// Distance from the camera where fog is completely opaque, in world units.132end: f32,133},134135/// An exponential fog falloff with a given `density`.136///137/// Initially gains intensity quickly with distance, then more slowly. Typically produces more natural results than [`FogFalloff::Linear`],138/// but is a bit harder to control.139///140/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.141///142/// ## Tips143///144/// - Use the [`FogFalloff::from_visibility()`] convenience method to create an exponential falloff with the proper145/// density for a desired visibility distance in world units;146/// - It's not _unusual_ to have very large or very small values for the density, depending on the scene147/// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values148/// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of149/// density;150/// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.151///152/// ## Formula153///154/// The fog intensity for a given point in the scene is determined by the following formula:155///156/// ```text157/// let fog_intensity = 1.0 - 1.0 / (distance * density).exp();158/// ```159///160/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">161/// <title>Plot showing how exponential fog falloff behaves for different density values</title>162/// <mask id="mask0_3_31" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">163/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>164/// </mask>165/// <g mask="url(#mask0_3_31)">166/// <path d="M42 150C42 150 98.3894 53 254.825 53L662 53" stroke="#FF003D" stroke-width="1"/>167/// <path d="M42 150C42 150 139.499 53 409.981 53L1114 53" stroke="#001AFF" stroke-width="1"/>168/// <path d="M42 150C42 150 206.348 53 662.281 53L1849 53" stroke="#14FF00" stroke-width="1"/>169/// </g>170/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>171/// <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>172/// <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>173/// <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>174/// <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>175/// <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>176/// <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>177/// <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>178/// <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>179/// <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>180/// <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>181/// </svg>182Exponential {183/// Multiplier applied to the world distance (within the exponential fog falloff calculation).184density: f32,185},186187/// A squared exponential fog falloff with a given `density`.188///189/// Similar to [`FogFalloff::Exponential`], but grows more slowly in intensity for closer distances190/// before “catching up”.191///192/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.193///194/// ## Tips195///196/// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff197/// with the proper density for a desired visibility distance in world units;198/// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.199///200/// ## Formula201///202/// The fog intensity for a given point in the scene is determined by the following formula:203///204/// ```text205/// let fog_intensity = 1.0 - 1.0 / (distance * density).squared().exp();206/// ```207///208/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">209/// <title>Plot showing how exponential squared fog falloff behaves for different density values</title>210/// <mask id="mask0_1_3" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">211/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>212/// </mask>213/// <g mask="url(#mask0_1_3)">214/// <path d="M42 150C75.4552 150 74.9241 53.1724 166.262 53.1724L404 53.1724" stroke="#FF003D" stroke-width="1"/>215/// <path d="M42 150C107.986 150 106.939 53.1724 287.091 53.1724L756 53.1724" stroke="#001AFF" stroke-width="1"/>216/// <path d="M42 150C166.394 150 164.42 53.1724 504.035 53.1724L1388 53.1724" stroke="#14FF00" stroke-width="1"/>217/// </g>218/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>219/// <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>220/// <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>221/// <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>222/// <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>223/// <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>224/// <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>225/// <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>226/// <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>227/// <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>228/// <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>229/// </svg>230ExponentialSquared {231/// Multiplier applied to the world distance (within the exponential squared fog falloff calculation).232density: f32,233},234235/// A more general form of the [`FogFalloff::Exponential`] mode. The falloff formula is separated into236/// two terms, `extinction` and `inscattering`, for a somewhat simplified atmospheric scattering model.237/// Additionally, individual color channels can have their own density values, resulting in a total of238/// six different configuration parameters.239///240/// ## Tips241///242/// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods243/// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and244/// extinction and inscattering colors;245/// - Combine the atmospheric fog parameters with the [`DistanceFog`] `color`'s alpha channel for easier artistic control.246///247/// ## Formula248///249/// Unlike other modes, atmospheric falloff doesn't use a simple intensity-based blend of fog color with250/// object color. Instead, it calculates per-channel extinction and inscattering factors, which are251/// then used to calculate the final color.252///253/// ```text254/// let extinction_factor = 1.0 - 1.0 / (distance * extinction).exp();255/// let inscattering_factor = 1.0 - 1.0 / (distance * inscattering).exp();256/// let result = input_color * (1.0 - extinction_factor) + fog_color * inscattering_factor;257/// ```258///259/// ## Equivalence to [`FogFalloff::Exponential`]260///261/// For a density value of `D`, the following two falloff modes will produce identical visual results:262///263/// ```264/// # use bevy_pbr::prelude::*;265/// # use bevy_math::prelude::*;266/// # const D: f32 = 0.5;267/// #268/// let exponential = FogFalloff::Exponential {269/// density: D,270/// };271///272/// let atmospheric = FogFalloff::Atmospheric {273/// extinction: Vec3::new(D, D, D),274/// inscattering: Vec3::new(D, D, D),275/// };276/// ```277///278/// **Note:** While the results are identical, [`FogFalloff::Atmospheric`] is computationally more expensive.279Atmospheric {280/// Controls how much light is removed due to atmospheric “extinction”, i.e. loss of light due to281/// photons being absorbed by atmospheric particles.282///283/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from284/// [`FogFalloff::Exponential`]: Multiplier applied to the world distance (within the fog285/// falloff calculation) for that specific channel.286///287/// **Note:**288/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.289/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.290extinction: Vec3,291292/// Controls how much light is added due to light scattering from the sun through the atmosphere.293///294/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from295/// [`FogFalloff::Exponential`]: A multiplier applied to the world distance (within the fog296/// falloff calculation) for that specific channel.297///298/// **Note:**299/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.300/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.301inscattering: Vec3,302},303}304305impl FogFalloff {306/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,307/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].308pub fn from_visibility(visibility: f32) -> FogFalloff {309FogFalloff::from_visibility_contrast(310visibility,311FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,312)313}314315/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,316/// and a given contrast threshold in the range of `0.0` to `1.0`.317pub fn from_visibility_contrast(visibility: f32, contrast_threshold: f32) -> FogFalloff {318FogFalloff::Exponential {319density: FogFalloff::koschmieder(visibility, contrast_threshold),320}321}322323/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,324/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].325pub fn from_visibility_squared(visibility: f32) -> FogFalloff {326FogFalloff::from_visibility_contrast_squared(327visibility,328FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,329)330}331332/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,333/// and a given contrast threshold in the range of `0.0` to `1.0`.334pub fn from_visibility_contrast_squared(335visibility: f32,336contrast_threshold: f32,337) -> FogFalloff {338FogFalloff::ExponentialSquared {339density: (FogFalloff::koschmieder(visibility, contrast_threshold) / visibility).sqrt(),340}341}342343/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,344/// and a shared color for both extinction and inscattering, using the revised Koschmieder contrast threshold,345/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].346pub fn from_visibility_color(347visibility: f32,348extinction_inscattering_color: Color,349) -> FogFalloff {350FogFalloff::from_visibility_contrast_colors(351visibility,352FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,353extinction_inscattering_color,354extinction_inscattering_color,355)356}357358/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,359/// extinction and inscattering colors, using the revised Koschmieder contrast threshold,360/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].361///362/// ## Tips363/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;364/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;365/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.366pub fn from_visibility_colors(367visibility: f32,368extinction_color: Color,369inscattering_color: Color,370) -> FogFalloff {371FogFalloff::from_visibility_contrast_colors(372visibility,373FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,374extinction_color,375inscattering_color,376)377}378379/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,380/// a contrast threshold in the range of `0.0` to `1.0`, and a shared color for both extinction and inscattering.381pub fn from_visibility_contrast_color(382visibility: f32,383contrast_threshold: f32,384extinction_inscattering_color: Color,385) -> FogFalloff {386FogFalloff::from_visibility_contrast_colors(387visibility,388contrast_threshold,389extinction_inscattering_color,390extinction_inscattering_color,391)392}393394/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,395/// a contrast threshold in the range of `0.0` to `1.0`, extinction and inscattering colors.396///397/// ## Tips398/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;399/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;400/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.401pub fn from_visibility_contrast_colors(402visibility: f32,403contrast_threshold: f32,404extinction_color: Color,405inscattering_color: Color,406) -> FogFalloff {407use core::f32::consts::E;408409let [r_e, g_e, b_e, a_e] = LinearRgba::from(extinction_color).to_f32_array();410let [r_i, g_i, b_i, a_i] = LinearRgba::from(inscattering_color).to_f32_array();411412FogFalloff::Atmospheric {413extinction: Vec3::new(414// Values are subtracted from 1.0 here to preserve the intuitive/artistic meaning of415// colors, since they're later subtracted. (e.g. by giving a blue extinction color, you416// get blue and _not_ yellow results)417ops::powf(1.0 - r_e, E),418ops::powf(1.0 - g_e, E),419ops::powf(1.0 - b_e, E),420) * FogFalloff::koschmieder(visibility, contrast_threshold)421* ops::powf(a_e, E),422423inscattering: Vec3::new(ops::powf(r_i, E), ops::powf(g_i, E), ops::powf(b_i, E))424* FogFalloff::koschmieder(visibility, contrast_threshold)425* ops::powf(a_i, E),426}427}428429/// A 2% contrast threshold was originally proposed by Koschmieder, being the430/// minimum visual contrast at which a human observer could detect an object.431/// We use a revised 5% contrast threshold, deemed more realistic for typical human observers.432pub const REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD: f32 = 0.05;433434/// Calculates the extinction coefficient β, from V and Cₜ, where:435///436/// - Cₜ is the contrast threshold, in the range of `0.0` to `1.0`437/// - V is the visibility distance in which a perfectly black object is still identifiable438/// against the horizon sky within the contrast threshold439///440/// We start with Koschmieder's equation:441///442/// ```text443/// -ln(Cₜ)444/// V = ─────────445/// β446/// ```447///448/// Multiplying both sides by β/V, that gives us:449///450/// ```text451/// -ln(Cₜ)452/// β = ─────────453/// V454/// ```455///456/// See:457/// - <https://en.wikipedia.org/wiki/Visibility>458/// - <https://www.biral.com/wp-content/uploads/2015/02/Introduction_to_visibility-v2-2.pdf>459pub fn koschmieder(v: f32, c_t: f32) -> f32 {460-ops::ln(c_t) / v461}462}463464impl Default for DistanceFog {465fn default() -> Self {466DistanceFog {467color: Color::WHITE,468falloff: FogFalloff::Linear {469start: 0.0,470end: 100.0,471},472directional_light_color: Color::NONE,473directional_light_exponent: 8.0,474}475}476}477478479