Skip to main content

bevy_pbr/render/
fog.rs

1use bevy_app::{App, Plugin};
2use bevy_color::{ColorToComponents, LinearRgba};
3use bevy_ecs::prelude::*;
4use bevy_math::{Vec3, Vec4};
5use bevy_render::{
6    extract_component::ExtractComponentPlugin,
7    render_resource::{DynamicUniformBuffer, ShaderType},
8    renderer::{RenderDevice, RenderQueue},
9    view::ExtractedView,
10    GpuResourceAppExt, Render, RenderApp, RenderSystems,
11};
12use bevy_shader::load_shader_library;
13
14use crate::{DistanceFog, FogFalloff};
15
16/// The GPU-side representation of the fog configuration that's sent as a uniform to the shader
17#[derive(#[automatically_derived]
impl ::core::marker::Copy for GpuFog { }Copy, #[automatically_derived]
impl ::core::clone::Clone for GpuFog {
    #[inline]
    fn clone(&self) -> GpuFog {
        let _: ::core::clone::AssertParamIsClone<Vec4>;
        let _: ::core::clone::AssertParamIsClone<Vec3>;
        let _: ::core::clone::AssertParamIsClone<f32>;
        let _: ::core::clone::AssertParamIsClone<u32>;
        *self
    }
}Clone, impl bevy_render::render_resource::encase::private::ShaderSize for GpuFog
    where Vec4: bevy_render::render_resource::encase::private::ShaderSize,
    Vec4: bevy_render::render_resource::encase::private::ShaderSize,
    Vec3: bevy_render::render_resource::encase::private::ShaderSize,
    f32: bevy_render::render_resource::encase::private::ShaderSize,
    Vec3: bevy_render::render_resource::encase::private::ShaderSize,
    u32: bevy_render::render_resource::encase::private::ShaderSize {}ShaderType, #[automatically_derived]
impl ::core::default::Default for GpuFog {
    #[inline]
    fn default() -> GpuFog {
        GpuFog {
            base_color: ::core::default::Default::default(),
            directional_light_color: ::core::default::Default::default(),
            be: ::core::default::Default::default(),
            directional_light_exponent: ::core::default::Default::default(),
            bi: ::core::default::Default::default(),
            mode: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::fmt::Debug for GpuFog {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["base_color", "directional_light_color", "be",
                        "directional_light_exponent", "bi", "mode"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.base_color, &self.directional_light_color, &self.be,
                        &self.directional_light_exponent, &self.bi, &&self.mode];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "GpuFog", names,
            values)
    }
}Debug)]
18pub struct GpuFog {
19    /// Fog color
20    base_color: Vec4,
21    /// The color used for the fog where the view direction aligns with directional lights
22    directional_light_color: Vec4,
23    /// Allocated differently depending on fog mode.
24    /// See `mesh_view_types.wgsl` for a detailed explanation
25    be: Vec3,
26    /// The exponent applied to the directional light alignment calculation
27    directional_light_exponent: f32,
28    /// Allocated differently depending on fog mode.
29    /// See `mesh_view_types.wgsl` for a detailed explanation
30    bi: Vec3,
31    /// Unsigned int representation of the active fog falloff mode
32    mode: u32,
33}
34
35// Important: These must be kept in sync with `mesh_view_types.wgsl`
36#[expect(unused, reason = "Kept in sync with `mesh_view_types.wgsl`")]
37const GPU_FOG_MODE_OFF: u32 = 0;
38const GPU_FOG_MODE_LINEAR: u32 = 1;
39const GPU_FOG_MODE_EXPONENTIAL: u32 = 2;
40const GPU_FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3;
41const GPU_FOG_MODE_ATMOSPHERIC: u32 = 4;
42
43/// Metadata for fog
44#[derive(#[automatically_derived]
impl ::core::default::Default for FogMeta {
    #[inline]
    fn default() -> FogMeta {
        FogMeta { gpu_fogs: ::core::default::Default::default() }
    }
}Default, impl bevy_ecs::resource::Resource for FogMeta where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
45pub struct FogMeta {
46    pub gpu_fogs: DynamicUniformBuffer<GpuFog>,
47}
48
49/// Prepares fog metadata and writes the fog-related uniform buffers to the GPU
50pub fn prepare_fog(
51    mut commands: Commands,
52    render_device: Res<RenderDevice>,
53    render_queue: Res<RenderQueue>,
54    mut fog_meta: ResMut<FogMeta>,
55    views: Query<(Entity, &DistanceFog), With<ExtractedView>>,
56) {
57    let views_iter = views.iter();
58    let view_count = views_iter.len();
59    let Some(mut writer) = fog_meta
60        .gpu_fogs
61        .get_writer(view_count, &render_device, &render_queue)
62    else {
63        return;
64    };
65    for (entity, fog) in views_iter {
66        let gpu_fog = {
67            match &fog.falloff {
68                FogFalloff::Linear { start, end } => GpuFog {
69                    mode: GPU_FOG_MODE_LINEAR,
70                    base_color: LinearRgba::from(fog.color).to_vec4(),
71                    directional_light_color: LinearRgba::from(fog.directional_light_color)
72                        .to_vec4(),
73                    directional_light_exponent: fog.directional_light_exponent,
74                    be: Vec3::new(*start, *end, 0.0),
75                    ..Default::default()
76                },
77                FogFalloff::Exponential { density } => GpuFog {
78                    mode: GPU_FOG_MODE_EXPONENTIAL,
79                    base_color: LinearRgba::from(fog.color).to_vec4(),
80                    directional_light_color: LinearRgba::from(fog.directional_light_color)
81                        .to_vec4(),
82                    directional_light_exponent: fog.directional_light_exponent,
83                    be: Vec3::new(*density, 0.0, 0.0),
84                    ..Default::default()
85                },
86                FogFalloff::ExponentialSquared { density } => GpuFog {
87                    mode: GPU_FOG_MODE_EXPONENTIAL_SQUARED,
88                    base_color: LinearRgba::from(fog.color).to_vec4(),
89                    directional_light_color: LinearRgba::from(fog.directional_light_color)
90                        .to_vec4(),
91                    directional_light_exponent: fog.directional_light_exponent,
92                    be: Vec3::new(*density, 0.0, 0.0),
93                    ..Default::default()
94                },
95                FogFalloff::Atmospheric {
96                    extinction,
97                    inscattering,
98                } => GpuFog {
99                    mode: GPU_FOG_MODE_ATMOSPHERIC,
100                    base_color: LinearRgba::from(fog.color).to_vec4(),
101                    directional_light_color: LinearRgba::from(fog.directional_light_color)
102                        .to_vec4(),
103                    directional_light_exponent: fog.directional_light_exponent,
104                    be: *extinction,
105                    bi: *inscattering,
106                },
107            }
108        };
109
110        // This is later read by `SetMeshViewBindGroup<I>`
111        commands.entity(entity).insert(ViewFogUniformOffset {
112            offset: writer.write(&gpu_fog),
113        });
114    }
115}
116
117/// Inserted on each `Entity` with an `ExtractedView` to keep track of its offset
118/// in the `gpu_fogs` `DynamicUniformBuffer` within `FogMeta`
119#[derive(impl bevy_ecs::component::Component for ViewFogUniformOffset where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {}
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}Component)]
120pub struct ViewFogUniformOffset {
121    pub offset: u32,
122}
123
124/// A plugin that consolidates fog extraction, preparation and related resources/assets
125pub struct FogPlugin;
126
127impl Plugin for FogPlugin {
128    fn build(&self, app: &mut App) {
129        {
    {
        let mut embedded =
            app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
        let path =
            {
                let crate_name =
                    "bevy_pbr::render::fog".split(':').next().unwrap();
                ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                    "src".as_ref(), "src/render/fog.rs".as_ref(),
                    "fog.wgsl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/render/fog.rs",
                "fog.wgsl");
        embedded.insert_asset(watched_path, &path,
            b"#define_import_path bevy_pbr::fog\n\n#import bevy_pbr::{\n    mesh_view_bindings::fog,\n    mesh_view_types::Fog,\n}\n\n// Fog formulas adapted from:\n// https://learn.microsoft.com/en-us/windows/win32/direct3d9/fog-formulas\n// https://catlikecoding.com/unity/tutorials/rendering/part-14/\n// https://iquilezles.org/articles/fog/ (Atmospheric Fog and Scattering)\n\nfn scattering_adjusted_fog_color(\n    fog_params: Fog,\n    scattering: vec3<f32>,\n) -> vec4<f32> {\n    if (fog_params.directional_light_color.a > 0.0) {\n        return vec4<f32>(\n            fog_params.base_color.rgb\n                + scattering * fog_params.directional_light_color.rgb * fog_params.directional_light_color.a,\n            fog_params.base_color.a,\n        );\n    } else {\n        return fog_params.base_color;\n    }\n}\n\nfn linear_fog(\n    fog_params: Fog,\n    input_color: vec4<f32>,\n    distance: f32,\n    scattering: vec3<f32>,\n) -> vec4<f32> {\n    var fog_color = scattering_adjusted_fog_color(fog_params, scattering);\n    let start = fog_params.be.x;\n    let end = fog_params.be.y;\n    fog_color.a *= 1.0 - clamp((end - distance) / (end - start), 0.0, 1.0);\n    return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);\n}\n\nfn exponential_fog(\n    fog_params: Fog,\n    input_color: vec4<f32>,\n    distance: f32,\n    scattering: vec3<f32>,\n) -> vec4<f32> {\n    var fog_color = scattering_adjusted_fog_color(fog_params, scattering);\n    let density = fog_params.be.x;\n    fog_color.a *= 1.0 - 1.0 / exp(distance * density);\n    return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);\n}\n\nfn exponential_squared_fog(\n    fog_params: Fog,\n    input_color: vec4<f32>,\n    distance: f32,\n    scattering: vec3<f32>,\n) -> vec4<f32> {\n    var fog_color = scattering_adjusted_fog_color(fog_params, scattering);\n    let distance_times_density = distance * fog_params.be.x;\n    fog_color.a *= 1.0 - 1.0 / exp(distance_times_density * distance_times_density);\n    return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);\n}\n\nfn atmospheric_fog(\n    fog_params: Fog,\n    input_color: vec4<f32>,\n    distance: f32,\n    scattering: vec3<f32>,\n) -> vec4<f32> {\n    var fog_color = scattering_adjusted_fog_color(fog_params, scattering);\n    let extinction_factor = 1.0 - 1.0 / exp(distance * fog_params.be);\n    let inscattering_factor = 1.0 - 1.0 / exp(distance * fog_params.bi);\n    return vec4<f32>(\n        input_color.rgb * (1.0 - extinction_factor * fog_color.a)\n            + fog_color.rgb * inscattering_factor * fog_color.a,\n        input_color.a\n    );\n}\n");
    }
};
let handle:
        ::bevy_shader::_macro::bevy_asset::prelude::Handle<::bevy_shader::prelude::Shader> =
    {
        let (path, asset_server) =
            {
                let path =
                    {
                        {
                            let crate_name =
                                "bevy_pbr::render::fog".split(':').next().unwrap();
                            ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                                "src".as_ref(), "src/render/fog.rs".as_ref(),
                                "fog.wgsl".as_ref())
                        }
                    };
                let path =
                    ::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
                let asset_server =
                    ::bevy_asset::io::embedded::GetAssetServer::get_asset_server(app);
                (path, asset_server)
            };
        asset_server.load(path)
    };
core::mem::forget(handle);load_shader_library!(app, "fog.wgsl");
130
131        app.add_plugins(ExtractComponentPlugin::<DistanceFog>::default());
132
133        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
134            render_app
135                .init_gpu_resource::<FogMeta>()
136                .add_systems(Render, prepare_fog.in_set(RenderSystems::PrepareResources));
137        }
138    }
139}