//! # Fog Example (Étape 25) //! //! Demonstrates distance fog: objects fade into the fog color as they recede, //! creating the illusion of an infinite world (Skyrim/GTA pattern). //! //! The scene has a row of cubes receding into the distance and scattered spheres, //! all sitting on a large ground plane. Switch fog modes with number keys to //! compare the three attenuation curves. //! //! ## Pipeline //! Fog is applied in the main pass fragment shader (after lighting, before tone //! mapping). It uses the fragment's world-space distance to the camera and //! blends the final color toward `fog_color`. //! //! ## Controls //! | Key | Action | //! |-----|--------| //! | Drag (LMB) | Orbit camera | //! | Wheel | Zoom | //! | `1` | Linear fog (near=5, far=30) | //! | `2` | Exponential fog (density=0.04) | //! | `3` | Exponential² fog (density=0.06) — best for masking | //! | `4` | Fog OFF | //! | `R` | Reset camera | //! //! ## Build & Run //! ```sh //! cargo run -p wsg-lib --example fog --features "all-prims" //! ``` use glam::Vec3; use winit::event::MouseButton; use winit::keyboard::KeyCode; use wsg_lib::app::AppBuilder; use wsg_lib::camera::CameraController; use wsg_lib::core::{FogConfig, ToneMapper, Transform}; use wsg_lib::mesh::{cube, icosphere, plane}; use wsg_lib::AppHandler; use wsg_lib::utils::WsgError; struct FogDemo { camera: CameraController, } impl AppHandler for FogDemo { fn setup(&mut self, app: &mut wsg_lib::App) { app.scene .register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH) .unwrap(); // Large ground plane — will fade into fog at distance. app.scene .create_mesh("ground_mesh", plane(80.0, 80.0, 1, 1), None) .unwrap(); app.scene.add_entity("ground", "ground_mesh").unwrap(); // Row of cubes receding into the distance. app.scene .create_mesh("cube_mesh", cube(1.0), None) .unwrap(); for i in 0..15 { let z = -2.0 - i as f32 * 2.5; let mut tf = Transform::identity(); tf.translation = Vec3::new(0.0, 0.5, z); app.scene .add_entity_with_transform(&format!("cube_{i}"), "cube_mesh", tf) .unwrap(); } // Scattered spheres at various distances. app.scene .create_mesh("sphere_mesh", icosphere(0.8, 3), None) .unwrap(); let positions = [ Vec3::new(3.0, 0.8, -5.0), Vec3::new(-4.0, 0.8, -10.0), Vec3::new(5.0, 0.8, -15.0), Vec3::new(-3.0, 0.8, -20.0), Vec3::new(0.0, 0.8, -30.0), ]; for (i, pos) in positions.iter().enumerate() { let mut tf = Transform::identity(); tf.translation = *pos; app.scene .add_entity_with_transform(&format!("sphere_{i}"), "sphere_mesh", tf) .unwrap(); } // Directional light. let light_dir = Vec3::new(-0.5, -1.0, -0.3).normalize(); app.scene .add_directional_light(light_dir, [1.0, 0.95, 0.85], 1.2) .unwrap(); app.scene.set_ambient([0.08, 0.08, 0.1]); // Camera — positioned to look down the row of cubes. self.camera.yaw = 0.0; self.camera.pitch = 0.15; self.camera.distance = 8.0; self.camera.target = Vec3::new(0.0, 0.5, -8.0); self.camera.apply_to(app.scene.camera_mut()); // Print initial fog status. eprintln!("[Fog] Initial: Exponential² (density=0.06)"); eprintln!("[Fog] Keys: 1=linear 2=exp 3=exp² 4=off R=reset"); } fn update(&mut self, app: &mut wsg_lib::App) { // Orbit camera. let (dx, dy) = app.input.mouse_delta(); if app.input.mouse_button_held(MouseButton::Left) { self.camera.orbit(dx, dy); } let (_, sy) = app.input.scroll_delta(); self.camera.zoom(sy); // Fog mode switching. if app.input.key_pressed(KeyCode::Digit1) { app.renderer_mut() .set_fog(Some(FogConfig::linear([0.7, 0.75, 0.85], 5.0, 30.0))); eprintln!("[Fog] → Linear (near=5, far=30)"); } if app.input.key_pressed(KeyCode::Digit2) { app.renderer_mut() .set_fog(Some(FogConfig::exponential([0.7, 0.75, 0.85], 0.04))); eprintln!("[Fog] → Exponential (density=0.04)"); } if app.input.key_pressed(KeyCode::Digit3) { app.renderer_mut() .set_fog(Some(FogConfig::exponential2([0.7, 0.75, 0.85], 0.06))); eprintln!("[Fog] → Exponential² (density=0.06)"); } if app.input.key_pressed(KeyCode::Digit4) { app.renderer_mut().set_fog(None); eprintln!("[Fog] → OFF"); } if app.input.key_pressed(KeyCode::KeyR) { self.camera.yaw = 0.0; self.camera.pitch = 0.15; self.camera.distance = 8.0; } self.camera.apply_to(app.scene.camera_mut()); } fn render(&mut self, app: &mut wsg_lib::App, frame: &wsg_lib::core::Frame) { app.render_scene(frame.view()); } } #[pollster::main] async fn main() -> Result<(), WsgError> { let app = AppBuilder::new() .title("WSG — Fog (3 modes)") .size(1024, 640) .with_fog(FogConfig::exponential2([0.7, 0.75, 0.85], 0.06)) .with_hdr(ToneMapper::Aces) .build() .await?; app.run(FogDemo { camera: CameraController::default(), }) }