bevy_pbr/volumetric_fog/
mod.rs

1//! Volumetric fog and volumetric lighting, also known as light shafts or god
2//! rays.
3//!
4//! This module implements a more physically-accurate, but slower, form of fog
5//! than the [`crate::fog`] module does. Notably, this *volumetric fog* allows
6//! for light beams from directional lights to shine through, creating what is
7//! known as *light shafts* or *god rays*.
8//!
9//! To add volumetric fog to a scene, add [`VolumetricFog`] to the
10//! camera, and add [`VolumetricLight`] to directional lights that you wish to
11//! be volumetric. [`VolumetricFog`] feature numerous settings that
12//! allow you to define the accuracy of the simulation, as well as the look of
13//! the fog. Currently, only interaction with directional lights that have
14//! shadow maps is supported. Note that the overhead of the effect scales
15//! directly with the number of directional lights in use, so apply
16//! [`VolumetricLight`] sparingly for the best results.
17//!
18//! The overall algorithm, which is implemented as a postprocessing effect, is a
19//! combination of the techniques described in [Scratchapixel] and [this blog
20//! post]. It uses raymarching in screen space, transformed into shadow map
21//! space for sampling and combined with physically-based modeling of absorption
22//! and scattering. Bevy employs the widely-used [Henyey-Greenstein phase
23//! function] to model asymmetry; this essentially allows light shafts to fade
24//! into and out of existence as the user views them.
25//!
26//! [Scratchapixel]: https://www.scratchapixel.com/lessons/3d-basic-rendering/volume-rendering-for-developers/intro-volume-rendering.html
27//!
28//! [this blog post]: https://www.alexandre-pestana.com/volumetric-lights/
29//!
30//! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction
31
32#![expect(deprecated)]
33
34use bevy_app::{App, Plugin};
35use bevy_asset::{load_internal_asset, Assets, Handle};
36use bevy_color::Color;
37use bevy_core_pipeline::core_3d::{
38    graph::{Core3d, Node3d},
39    prepare_core_3d_depth_textures,
40};
41use bevy_ecs::{
42    bundle::Bundle, component::Component, reflect::ReflectComponent,
43    schedule::IntoSystemConfigs as _,
44};
45use bevy_image::Image;
46use bevy_math::{
47    primitives::{Cuboid, Plane3d},
48    Vec2, Vec3,
49};
50use bevy_reflect::{std_traits::ReflectDefault, Reflect};
51use bevy_render::{
52    mesh::{Mesh, Meshable},
53    render_graph::{RenderGraphApp, ViewNodeRunner},
54    render_resource::{Shader, SpecializedRenderPipelines},
55    sync_component::SyncComponentPlugin,
56    view::{InheritedVisibility, ViewVisibility, Visibility},
57    ExtractSchedule, Render, RenderApp, RenderSet,
58};
59use bevy_transform::components::{GlobalTransform, Transform};
60use render::{
61    VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH,
62    VOLUMETRIC_FOG_HANDLE,
63};
64
65use crate::graph::NodePbr;
66
67pub mod render;
68
69/// A plugin that implements volumetric fog.
70pub struct VolumetricFogPlugin;
71
72/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
73/// (`shadows_enabled: true`) to make volumetric fog interact with it.
74///
75/// This allows the light to generate light shafts/god rays.
76#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
77#[reflect(Component, Default, Debug)]
78pub struct VolumetricLight;
79
80/// When placed on a [`bevy_core_pipeline::core_3d::Camera3d`], enables
81/// volumetric fog and volumetric lighting, also known as light shafts or god
82/// rays.
83#[derive(Clone, Copy, Component, Debug, Reflect)]
84#[reflect(Component, Default, Debug)]
85pub struct VolumetricFog {
86    /// Color of the ambient light.
87    ///
88    /// This is separate from Bevy's [`AmbientLight`](crate::light::AmbientLight) because an
89    /// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight) is
90    /// still considered an ambient light for the purposes of volumetric fog. If you're using a
91    /// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight), for best results,
92    /// this should be a good approximation of the average color of the environment map.
93    ///
94    /// Defaults to white.
95    pub ambient_color: Color,
96
97    /// The brightness of the ambient light.
98    ///
99    /// If there's no [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight),
100    /// set this to 0.
101    ///
102    /// Defaults to 0.1.
103    pub ambient_intensity: f32,
104
105    /// The maximum distance to offset the ray origin randomly by, in meters.
106    ///
107    /// This is intended for use with temporal antialiasing. It helps fog look
108    /// less blocky by varying the start position of the ray, using interleaved
109    /// gradient noise.
110    pub jitter: f32,
111
112    /// The number of raymarching steps to perform.
113    ///
114    /// Higher values produce higher-quality results with less banding, but
115    /// reduce performance.
116    ///
117    /// The default value is 64.
118    pub step_count: u32,
119}
120
121#[deprecated(since = "0.15.0", note = "Renamed to `VolumetricFog`")]
122pub type VolumetricFogSettings = VolumetricFog;
123
124/// A convenient [`Bundle`] that contains all components necessary to generate a
125/// fog volume.
126#[derive(Bundle, Clone, Debug, Default)]
127#[deprecated(
128    since = "0.15.0",
129    note = "Use the `FogVolume` component instead. Inserting it will now also insert the other components required by it automatically."
130)]
131pub struct FogVolumeBundle {
132    /// The actual fog volume.
133    pub fog_volume: FogVolume,
134    /// Visibility.
135    pub visibility: Visibility,
136    /// Inherited visibility.
137    pub inherited_visibility: InheritedVisibility,
138    /// View visibility.
139    pub view_visibility: ViewVisibility,
140    /// The local transform. Set this to change the position, and scale of the
141    /// fog's axis-aligned bounding box (AABB).
142    pub transform: Transform,
143    /// The global transform.
144    pub global_transform: GlobalTransform,
145}
146
147#[derive(Clone, Component, Debug, Reflect)]
148#[reflect(Component, Default, Debug)]
149#[require(Transform, Visibility)]
150pub struct FogVolume {
151    /// The color of the fog.
152    ///
153    /// Note that the fog must be lit by a [`VolumetricLight`] or ambient light
154    /// in order for this color to appear.
155    ///
156    /// Defaults to white.
157    pub fog_color: Color,
158
159    /// The density of fog, which measures how dark the fog is.
160    ///
161    /// The default value is 0.1.
162    pub density_factor: f32,
163
164    /// Optional 3D voxel density texture for the fog.
165    pub density_texture: Option<Handle<Image>>,
166
167    /// Configurable offset of the density texture in UVW coordinates.
168    ///
169    /// This can be used to scroll a repeating density texture in a direction over time
170    /// to create effects like fog moving in the wind. Make sure to configure the texture
171    /// to use `ImageAddressMode::Repeat` if this is your intention.
172    ///
173    /// Has no effect when no density texture is present.
174    ///
175    /// The default value is (0, 0, 0).
176    pub density_texture_offset: Vec3,
177
178    /// The absorption coefficient, which measures what fraction of light is
179    /// absorbed by the fog at each step.
180    ///
181    /// Increasing this value makes the fog darker.
182    ///
183    /// The default value is 0.3.
184    pub absorption: f32,
185
186    /// The scattering coefficient, which measures the fraction of light that's
187    /// scattered toward, and away from, the viewer.
188    ///
189    /// The default value is 0.3.
190    pub scattering: f32,
191
192    /// Measures the fraction of light that's scattered *toward* the camera, as
193    /// opposed to *away* from the camera.
194    ///
195    /// Increasing this value makes light shafts become more prominent when the
196    /// camera is facing toward their source and less prominent when the camera
197    /// is facing away. Essentially, a high value here means the light shafts
198    /// will fade into view as the camera focuses on them and fade away when the
199    /// camera is pointing away.
200    ///
201    /// The default value is 0.8.
202    pub scattering_asymmetry: f32,
203
204    /// Applies a nonphysical color to the light.
205    ///
206    /// This can be useful for artistic purposes but is nonphysical.
207    ///
208    /// The default value is white.
209    pub light_tint: Color,
210
211    /// Scales the light by a fixed fraction.
212    ///
213    /// This can be useful for artistic purposes but is nonphysical.
214    ///
215    /// The default value is 1.0, which results in no adjustment.
216    pub light_intensity: f32,
217}
218
219impl Plugin for VolumetricFogPlugin {
220    fn build(&self, app: &mut App) {
221        load_internal_asset!(
222            app,
223            VOLUMETRIC_FOG_HANDLE,
224            "volumetric_fog.wgsl",
225            Shader::from_wgsl
226        );
227
228        let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
229        meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into());
230        meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into());
231
232        app.register_type::<VolumetricFog>()
233            .register_type::<VolumetricLight>();
234
235        app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
236
237        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
238            return;
239        };
240
241        render_app
242            .init_resource::<SpecializedRenderPipelines<VolumetricFogPipeline>>()
243            .init_resource::<VolumetricFogUniformBuffer>()
244            .add_systems(ExtractSchedule, render::extract_volumetric_fog)
245            .add_systems(
246                Render,
247                (
248                    render::prepare_volumetric_fog_pipelines.in_set(RenderSet::Prepare),
249                    render::prepare_volumetric_fog_uniforms.in_set(RenderSet::Prepare),
250                    render::prepare_view_depth_textures_for_volumetric_fog
251                        .in_set(RenderSet::Prepare)
252                        .before(prepare_core_3d_depth_textures),
253                ),
254            );
255    }
256
257    fn finish(&self, app: &mut App) {
258        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
259            return;
260        };
261
262        render_app
263            .init_resource::<VolumetricFogPipeline>()
264            .add_render_graph_node::<ViewNodeRunner<VolumetricFogNode>>(
265                Core3d,
266                NodePbr::VolumetricFog,
267            )
268            .add_render_graph_edges(
269                Core3d,
270                // Volumetric fog is a postprocessing effect. Run it after the
271                // main pass but before bloom.
272                (Node3d::EndMainPass, NodePbr::VolumetricFog, Node3d::Bloom),
273            );
274    }
275}
276
277impl Default for VolumetricFog {
278    fn default() -> Self {
279        Self {
280            step_count: 64,
281            // Matches `AmbientLight` defaults.
282            ambient_color: Color::WHITE,
283            ambient_intensity: 0.1,
284            jitter: 0.0,
285        }
286    }
287}
288
289impl Default for FogVolume {
290    fn default() -> Self {
291        Self {
292            absorption: 0.3,
293            scattering: 0.3,
294            density_factor: 0.1,
295            density_texture: None,
296            density_texture_offset: Vec3::ZERO,
297            scattering_asymmetry: 0.5,
298            fog_color: Color::WHITE,
299            light_tint: Color::WHITE,
300            light_intensity: 1.0,
301        }
302    }
303}