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(Copy, Clone, ShaderType, Default, 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(Default, 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(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        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}