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    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`
36const GPU_FOG_MODE_OFF: u32 = 0;
37const GPU_FOG_MODE_LINEAR: u32 = 1;
38const GPU_FOG_MODE_EXPONENTIAL: u32 = 2;
39const GPU_FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3;
40const GPU_FOG_MODE_ATMOSPHERIC: u32 = 4;
41
42/// Metadata for fog
43#[derive(Default, Resource)]
44pub struct FogMeta {
45    pub gpu_fogs: DynamicUniformBuffer<GpuFog>,
46}
47
48/// Prepares fog metadata and writes the fog-related uniform buffers to the GPU
49pub fn prepare_fog(
50    mut commands: Commands,
51    render_device: Res<RenderDevice>,
52    render_queue: Res<RenderQueue>,
53    mut fog_meta: ResMut<FogMeta>,
54    views: Query<(Entity, Option<&DistanceFog>), With<ExtractedView>>,
55) {
56    let views_iter = views.iter();
57    let view_count = views_iter.len();
58    let Some(mut writer) = fog_meta
59        .gpu_fogs
60        .get_writer(view_count, &render_device, &render_queue)
61    else {
62        return;
63    };
64    for (entity, fog) in views_iter {
65        let gpu_fog = if let Some(fog) = fog {
66            match &fog.falloff {
67                FogFalloff::Linear { start, end } => GpuFog {
68                    mode: GPU_FOG_MODE_LINEAR,
69                    base_color: LinearRgba::from(fog.color).to_vec4(),
70                    directional_light_color: LinearRgba::from(fog.directional_light_color)
71                        .to_vec4(),
72                    directional_light_exponent: fog.directional_light_exponent,
73                    be: Vec3::new(*start, *end, 0.0),
74                    ..Default::default()
75                },
76                FogFalloff::Exponential { density } => GpuFog {
77                    mode: GPU_FOG_MODE_EXPONENTIAL,
78                    base_color: LinearRgba::from(fog.color).to_vec4(),
79                    directional_light_color: LinearRgba::from(fog.directional_light_color)
80                        .to_vec4(),
81                    directional_light_exponent: fog.directional_light_exponent,
82                    be: Vec3::new(*density, 0.0, 0.0),
83                    ..Default::default()
84                },
85                FogFalloff::ExponentialSquared { density } => GpuFog {
86                    mode: GPU_FOG_MODE_EXPONENTIAL_SQUARED,
87                    base_color: LinearRgba::from(fog.color).to_vec4(),
88                    directional_light_color: LinearRgba::from(fog.directional_light_color)
89                        .to_vec4(),
90                    directional_light_exponent: fog.directional_light_exponent,
91                    be: Vec3::new(*density, 0.0, 0.0),
92                    ..Default::default()
93                },
94                FogFalloff::Atmospheric {
95                    extinction,
96                    inscattering,
97                } => GpuFog {
98                    mode: GPU_FOG_MODE_ATMOSPHERIC,
99                    base_color: LinearRgba::from(fog.color).to_vec4(),
100                    directional_light_color: LinearRgba::from(fog.directional_light_color)
101                        .to_vec4(),
102                    directional_light_exponent: fog.directional_light_exponent,
103                    be: *extinction,
104                    bi: *inscattering,
105                },
106            }
107        } else {
108            // If no fog is added to a camera, by default it's off
109            GpuFog {
110                mode: GPU_FOG_MODE_OFF,
111                ..Default::default()
112            }
113        };
114
115        // This is later read by `SetMeshViewBindGroup<I>`
116        commands.entity(entity).insert(ViewFogUniformOffset {
117            offset: writer.write(&gpu_fog),
118        });
119    }
120}
121
122/// Inserted on each `Entity` with an `ExtractedView` to keep track of its offset
123/// in the `gpu_fogs` `DynamicUniformBuffer` within `FogMeta`
124#[derive(Component)]
125pub struct ViewFogUniformOffset {
126    pub offset: u32,
127}
128
129/// A plugin that consolidates fog extraction, preparation and related resources/assets
130pub struct FogPlugin;
131
132impl Plugin for FogPlugin {
133    fn build(&self, app: &mut App) {
134        load_shader_library!(app, "fog.wgsl");
135
136        app.add_plugins(ExtractComponentPlugin::<DistanceFog>::default());
137
138        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
139            render_app
140                .init_resource::<FogMeta>()
141                .add_systems(Render, prepare_fog.in_set(RenderSystems::PrepareResources));
142        }
143    }
144}