//! Demonstrates volumetric fog and lighting (light shafts or god rays). use bevy::{ color::palettes::css::RED, core_pipeline::{bloom::Bloom, tonemapping::Tonemapping, Skybox}, math::vec3, pbr::{FogVolume, VolumetricFog, VolumetricLight}, prelude::*, }; const DIRECTIONAL_LIGHT_MOVEMENT_SPEED: f32 = 0.02; /// The current settings that the user has chosen. #[derive(Resource)] struct AppSettings { /// Whether volumetric spot light is on. volumetric_spotlight: bool, /// Whether volumetric point light is on. volumetric_pointlight: bool, } impl Default for AppSettings { fn default() -> Self { Self { volumetric_spotlight: true, volumetric_pointlight: true, } } } // Define a struct to store parameters for the point light's movement. #[derive(Component)] struct MoveBackAndForthHorizontally { min_x: f32, max_x: f32, speed: f32, } fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(ClearColor(Color::Srgba(Srgba { red: 0.02, green: 0.02, blue: 0.02, alpha: 1.0, }))) .insert_resource(AmbientLight::NONE) .init_resource::() .add_systems(Startup, setup) .add_systems(Update, tweak_scene) .add_systems(Update, (move_directional_light, move_point_light)) .add_systems(Update, adjust_app_settings) .run(); } /// Initializes the scene. fn setup(mut commands: Commands, asset_server: Res, app_settings: Res) { // Spawn the glTF scene. commands.spawn(SceneRoot(asset_server.load( GltfAssetLabel::Scene(0).from_asset("models/VolumetricFogExample/VolumetricFogExample.glb"), ))); // Spawn the camera. commands .spawn(( Camera3d::default(), Camera { hdr: true, ..default() }, Transform::from_xyz(-1.7, 1.5, 4.5).looking_at(vec3(-1.5, 1.7, 3.5), Vec3::Y), Tonemapping::TonyMcMapface, Bloom::default(), )) .insert(Skybox { image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), brightness: 1000.0, ..default() }) .insert(VolumetricFog { // This value is explicitly set to 0 since we have no environment map light ambient_intensity: 0.0, ..default() }); // Add the point light commands.spawn(( Transform::from_xyz(-0.4, 1.9, 1.0), PointLight { shadows_enabled: true, range: 150.0, color: RED.into(), intensity: 1000.0, ..default() }, VolumetricLight, MoveBackAndForthHorizontally { min_x: -1.93, max_x: -0.4, speed: -0.2, }, )); // Add the spot light commands.spawn(( Transform::from_xyz(-1.8, 3.9, -2.7).looking_at(Vec3::ZERO, Vec3::Y), SpotLight { intensity: 5000.0, // lumens color: Color::WHITE, shadows_enabled: true, inner_angle: 0.76, outer_angle: 0.94, ..default() }, VolumetricLight, )); // Add the fog volume. commands.spawn(( FogVolume::default(), Transform::from_scale(Vec3::splat(35.0)), )); // Add the help text. commands.spawn( TextBundle { text: create_text(&app_settings), ..default() } .with_style(Style { position_type: PositionType::Absolute, top: Val::Px(12.0), left: Val::Px(12.0), ..default() }), ); } fn create_text(app_settings: &AppSettings) -> Text { Text::from_section( format!( "{}\n{}\n{}", "Press WASD or the arrow keys to change the direction of the directional light", if app_settings.volumetric_pointlight { "Press P to turn volumetric point light off" } else { "Press P to turn volumetric point light on" }, if app_settings.volumetric_spotlight { "Press L to turn volumetric spot light off" } else { "Press L to turn volumetric spot light on" } ), TextStyle::default(), ) } /// A system that makes directional lights in the glTF scene into volumetric /// lights with shadows. fn tweak_scene( mut commands: Commands, mut lights: Query<(Entity, &mut DirectionalLight), Changed>, ) { for (light, mut directional_light) in lights.iter_mut() { // Shadows are needed for volumetric lights to work. directional_light.shadows_enabled = true; commands.entity(light).insert(VolumetricLight); } } /// Processes user requests to move the directional light. fn move_directional_light( input: Res>, mut directional_lights: Query<&mut Transform, With>, ) { let mut delta_theta = Vec2::ZERO; if input.pressed(KeyCode::KeyW) || input.pressed(KeyCode::ArrowUp) { delta_theta.y += DIRECTIONAL_LIGHT_MOVEMENT_SPEED; } if input.pressed(KeyCode::KeyS) || input.pressed(KeyCode::ArrowDown) { delta_theta.y -= DIRECTIONAL_LIGHT_MOVEMENT_SPEED; } if input.pressed(KeyCode::KeyA) || input.pressed(KeyCode::ArrowLeft) { delta_theta.x += DIRECTIONAL_LIGHT_MOVEMENT_SPEED; } if input.pressed(KeyCode::KeyD) || input.pressed(KeyCode::ArrowRight) { delta_theta.x -= DIRECTIONAL_LIGHT_MOVEMENT_SPEED; } if delta_theta == Vec2::ZERO { return; } let delta_quat = Quat::from_euler(EulerRot::XZY, delta_theta.y, 0.0, delta_theta.x); for mut transform in directional_lights.iter_mut() { transform.rotate(delta_quat); } } // Toggle point light movement between left and right. fn move_point_light( timer: Res