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}