diff --git a/Cargo.toml b/Cargo.toml index 4fa6215bbb..474058b45f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3330,6 +3330,17 @@ description = "Demonstrates fog volumes" category = "3D Rendering" wasm = false +[[example]] +name = "scrolling_fog" +path = "examples/3d/scrolling_fog.rs" +doc-scrape-examples = true + +[package.metadata.example.scrolling_fog] +name = "Scrolling fog" +description = "Demonstrates how to create the effect of fog moving in the wind" +category = "3D Rendering" +wasm = false + [[example]] name = "physics_in_fixed_timestep" path = "examples/movement/physics_in_fixed_timestep.rs" diff --git a/assets/volumes/fog_noise.ktx2 b/assets/volumes/fog_noise.ktx2 new file mode 100644 index 0000000000..0f17706c00 Binary files /dev/null and b/assets/volumes/fog_noise.ktx2 differ diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index fbd455627a..ddc9b976c9 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -150,8 +150,20 @@ pub struct FogVolume { /// The default value is 0.1. pub density_factor: f32, + /// Optional 3D voxel density texture for the fog. pub density_texture: Option>, + /// Configurable offset of the density texture in UVW coordinates. Values 1.0 or higher + /// will be wrapped into the (0.0..1.0) range. + /// + /// This can be used to scroll a repeating density texture in a direction over time + /// to create effects like fog moving in the wind. + /// + /// Has no effect when no density texture is present. + /// + /// The default value is (0, 0, 0). + pub density_texture_offset: Vec3, + /// The absorption coefficient, which measures what fraction of light is /// absorbed by the fog at each step. /// @@ -268,6 +280,7 @@ impl Default for FogVolume { scattering: 0.3, density_factor: 0.1, density_texture: None, + density_texture_offset: Vec3::ZERO, scattering_asymmetry: 0.5, fog_color: Color::WHITE, light_tint: Color::WHITE, diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index de158762d5..618e984db0 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -183,6 +183,7 @@ pub struct VolumetricFogUniform { absorption: f32, scattering: f32, density: f32, + density_texture_offset: Vec3, scattering_asymmetry: f32, light_intensity: f32, jitter_strength: f32, @@ -727,6 +728,7 @@ pub fn prepare_volumetric_fog_uniforms( absorption: fog_volume.absorption, scattering: fog_volume.scattering, density: fog_volume.density_factor, + density_texture_offset: fog_volume.density_texture_offset, scattering_asymmetry: fog_volume.scattering_asymmetry, light_intensity: fog_volume.light_intensity, jitter_strength: volumetric_fog_settings.jitter, diff --git a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl index 72f3d6d824..68e9291032 100644 --- a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl +++ b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl @@ -43,6 +43,7 @@ struct VolumetricFog { absorption: f32, scattering: f32, density_factor: f32, + density_texture_offset: vec3, scattering_asymmetry: f32, light_intensity: f32, jitter_strength: f32, @@ -101,6 +102,7 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { let absorption = volumetric_fog.absorption; let scattering = volumetric_fog.scattering; let density_factor = volumetric_fog.density_factor; + let density_texture_offset = volumetric_fog.density_texture_offset; let light_tint = volumetric_fog.light_tint; let light_intensity = volumetric_fog.light_intensity; let jitter_strength = volumetric_fog.jitter_strength; @@ -236,7 +238,11 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { // case. let P_uvw = Ro_uvw + Rd_step_uvw * f32(step); if (all(P_uvw >= vec3(0.0)) && all(P_uvw <= vec3(1.0))) { - density *= textureSample(density_texture, density_sampler, P_uvw).r; + // Add density texture offset and wrap values exceeding the (0, 0, 0) to (1, 1, 1) range. + var uvw = P_uvw + density_texture_offset; + uvw -= floor(uvw); + + density *= textureSample(density_texture, density_sampler, uvw).r; } else { density = 0.0; } diff --git a/examples/3d/scrolling_fog.rs b/examples/3d/scrolling_fog.rs new file mode 100644 index 0000000000..cfec3b255d --- /dev/null +++ b/examples/3d/scrolling_fog.rs @@ -0,0 +1,117 @@ +//! Showcases a `FogVolume`'s density texture being scrolled over time to create +//! the effect of fog moving in the wind. +//! +//! The density texture is a repeating 3d noise texture and the `density_texture_offset` +//! is moved every frame to achieve this. +//! +//! The example also utilizes the jitter option of `VolumetricFogSettings` in tandem +//! with temporal anti-aliasing to improve the visual quality of the effect. +//! +//! The camera is looking at a pillar with the sun peaking behind it. The light +//! interactions change based on the density of the fog. + +use bevy::core_pipeline::bloom::BloomSettings; +use bevy::core_pipeline::experimental::taa::{TemporalAntiAliasBundle, TemporalAntiAliasPlugin}; +use bevy::pbr::{DirectionalLightShadowMap, FogVolume, VolumetricFogSettings, VolumetricLight}; +use bevy::prelude::*; + +/// Initializes the example. +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Bevy Scrolling Fog".into(), + ..default() + }), + ..default() + })) + .insert_resource(DirectionalLightShadowMap { size: 4096 }) + .add_plugins(TemporalAntiAliasPlugin) + .add_systems(Startup, setup) + .add_systems(Update, scroll_fog) + .run(); +} + +/// Spawns all entities into the scene. +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + assets: Res, +) { + // Spawn camera with temporal anti-aliasing and a VolumetricFogSettings configuration. + commands.spawn(( + Camera3dBundle { + transform: Transform::from_xyz(0.0, 2.0, 0.0) + .looking_at(Vec3::new(-5.0, 3.5, -6.0), Vec3::Y), + camera: Camera { + hdr: true, + ..default() + }, + msaa: Msaa::Off, + ..default() + }, + TemporalAntiAliasBundle::default(), + BloomSettings::default(), + VolumetricFogSettings { + ambient_intensity: 0.0, + jitter: 0.5, + ..default() + }, + )); + + // Spawn a directional light shining at the camera with the VolumetricLight component. + commands.spawn(( + DirectionalLightBundle { + transform: Transform::from_xyz(-5.0, 5.0, -7.0) + .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), + directional_light: DirectionalLight { + shadows_enabled: true, + ..default() + }, + ..default() + }, + VolumetricLight, + )); + + // Spawn ground mesh. + commands.spawn(PbrBundle { + transform: Transform::from_xyz(0.0, -0.5, 0.0), + mesh: meshes.add(Cuboid::new(64.0, 1.0, 64.0)), + material: materials.add(StandardMaterial { + base_color: Color::BLACK, + perceptual_roughness: 1.0, + ..default() + }), + ..default() + }); + + // Spawn pillar standing between the camera and the sun. + commands.spawn(PbrBundle { + transform: Transform::from_xyz(-10.0, 4.5, -11.0), + mesh: meshes.add(Cuboid::new(2.0, 9.0, 2.0)), + material: materials.add(Color::BLACK), + ..default() + }); + + // Spawn FogVolume with repeating 3d noise density texture. + commands.spawn(( + SpatialBundle { + visibility: Visibility::Visible, + transform: Transform::from_xyz(0.0, 32.0, 0.0).with_scale(Vec3::splat(64.0)), + ..default() + }, + FogVolume { + density_texture: Some(assets.load("volumes/fog_noise.ktx2")), + density_factor: 0.05, + ..default() + }, + )); +} + +/// Moves fog density texture offset every frame. +fn scroll_fog(time: Res