bevy_pbr/render/
fog.rs

1use bevy_app::{App, Plugin};
2use bevy_asset::{load_internal_asset, Handle};
3use bevy_color::{ColorToComponents, LinearRgba};
4use bevy_ecs::prelude::*;
5use bevy_math::{Vec3, Vec4};
6use bevy_render::{
7    extract_component::ExtractComponentPlugin,
8    render_resource::{DynamicUniformBuffer, Shader, ShaderType},
9    renderer::{RenderDevice, RenderQueue},
10    view::ExtractedView,
11    Render, RenderApp, RenderSet,
12};
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/// Handle for the fog WGSL Shader internal asset
130pub const FOG_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4913569193382610166);
131
132/// A plugin that consolidates fog extraction, preparation and related resources/assets
133pub struct FogPlugin;
134
135impl Plugin for FogPlugin {
136    fn build(&self, app: &mut App) {
137        load_internal_asset!(app, FOG_SHADER_HANDLE, "fog.wgsl", Shader::from_wgsl);
138
139        app.register_type::<DistanceFog>();
140        app.add_plugins(ExtractComponentPlugin::<DistanceFog>::default());
141
142        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
143            render_app
144                .init_resource::<FogMeta>()
145                .add_systems(Render, prepare_fog.in_set(RenderSet::PrepareResources));
146        }
147    }
148}