Files
wsg/lib/examples/effects/fog.rs
T
Jérôme Bousquié e5f3636b42 examples: apply real texture assets to multi-mesh examples
- meshes/cube: procedural checkerboard -> uv_texture.jpg (8x8 UV grid)
- meshes/pbr: floor -> ground.jpeg, bump cube -> cave.jpg + caveNormal.jpg
  (normal map pre-encoded via sRGB OETF to cancel the GPU sRGB decode)
- lights/shadow: ground -> ground.jpeg, cube -> uv_texture.jpg
- effects/demo: ground -> ground.jpeg, cube -> uv_texture.jpg
- effects/fog: ground -> ground.jpeg (tiled 80x80), cubes -> stonewall.jpg
- effects/dof: ground -> ground.jpeg, cubes -> uv_texture.jpg
- cameras/culling: shared cube mesh -> uv_texture.jpg
- add lib/examples/assets/textures/ (19 assets, 6.5 MB)
- document assets + usage in examples READMEs, docs/user/examples.md,
  docs/user/meshes/materials.md (CARGO_MANIFEST_DIR pattern, sRGB caveat)
2026-09-26 10:49:13 +02:00

187 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # 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::resources::Texture;
use wsg_lib::AppHandler;
use wsg_lib::utils::WsgError;
/// Texture asset directory, resolved against the crate root so the example works from any CWD.
const TEXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/assets/textures");
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();
// Textured ground + cubes: the fog falloff reads much better on textured surfaces.
// ground.jpeg tiles across the 80×80 floor (Repeat sampler); stonewall.jpg on the cubes.
let (device, queue) = {
let ctx = app.context();
(ctx.device.clone(), ctx.queue.clone())
};
let ground_tex =
Texture::from_file(&device, &queue, "ground", &format!("{TEXTURES}/ground.jpeg")).unwrap();
app.scene.add_texture("ground_texture", ground_tex).unwrap();
app.scene
.add_material_texture("ground_mat", "standard", "ground_texture")
.unwrap();
let wall_tex =
Texture::from_file(&device, &queue, "wall", &format!("{TEXTURES}/stonewall.jpg")).unwrap();
app.scene.add_texture("wall_texture", wall_tex).unwrap();
app.scene
.add_material_texture("wall_mat", "standard", "wall_texture")
.unwrap();
// Large ground plane — will fade into fog at distance.
app.scene
.create_mesh("ground_mesh", plane(80.0, 80.0, 1, 1), Some("ground_mat"))
.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), Some("wall_mat"))
.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(),
})
}