dof
This commit is contained in:
@@ -18,6 +18,7 @@ cargo run -p wsg-lib --example <nom>
|
||||
| `shadow` | Shadow mapping (ombre portée directionnelle) |
|
||||
| `culling` | Culling GPU-driven (grille 15×15, objets hors frustum ignorés) |
|
||||
| `msaa` | MSAA 4× (anti-aliasing multi-échantillons, arêtes lisses) |
|
||||
| `fog` | Brouillard de distance (3 modes : linéaire, exp, exp²) |
|
||||
| `manual` | Workflow bas niveau (Context + Renderer + PipelineCache) |
|
||||
| `import` | Import de fichier OBJ (non graphique, stdout) |
|
||||
|
||||
@@ -251,6 +252,44 @@ identique, seules les arêtes diffèrent (escaler vs lisse).
|
||||
|
||||
---
|
||||
|
||||
## `fog` — Brouillard de distance
|
||||
|
||||
Démontre les 3 modes de brouillard : **linéaire**, **exponentiel**, **exponentiel²**.
|
||||
La scène contient une rangée de cubes qui s'éloignent et des sphères dispersées sur
|
||||
un grand plan au sol. Le brouillard fond les objets vers une couleur de fond,
|
||||
créant l'illusion d'un monde infini.
|
||||
|
||||
```sh
|
||||
cargo run -p wsg-lib --example fog --features "all-prims"
|
||||
```
|
||||
|
||||
**Touches** : `1` = linéaire, `2` = exp, `3` = exp², `4` = désactivé, `R` = reset.
|
||||
|
||||
> Le brouillard est appliqué dans le shader fragment principal (après l'éclairage,
|
||||
> avant le tone mapping). Il utilise la distance euclidienne du fragment à la caméra.
|
||||
|
||||
---
|
||||
|
||||
## `dof` — Depth of Field (bokeh cinématique)
|
||||
|
||||
Démontre le flou de profondeur de champ : un objet au centre reste net tandis que
|
||||
le premier et arrière-plan se flouent selon leur distance au plan de mise au point.
|
||||
Crée un effet d'attention naturelle (type cinématique).
|
||||
|
||||
La scène contient un cube de focus au centre, des sphères en premier plan (proches)
|
||||
et des cubes en arrière-plan (loin), sur un plan au sol.
|
||||
|
||||
```sh
|
||||
cargo run -p wsg-lib --example dof --features "all-prims"
|
||||
```
|
||||
|
||||
**Touches** : `1` = cinématique, `2` = subtil, `3` = focus 2m, `4` = focus 10m, `5` = off, `R` = reset.
|
||||
|
||||
> DoF opère en HDR linéaire (après bloom, avant tone mapping). Deux passes :
|
||||
> CoC (depth → rayon de flou par pixel) puis blur disque 12-taps à rayon variable.
|
||||
|
||||
---
|
||||
|
||||
## `manual` — Workflow bas niveau
|
||||
|
||||
Démontre l'API **sans** la façade `App` : utilisation directe de `Context`,
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
//! # Depth of Field Example (Étape 26)
|
||||
//!
|
||||
//! Demonstrates cinematic DoF: a row of cubes receding into the distance,
|
||||
//! with the focus plane at a configurable depth. Cubes at the focus distance
|
||||
//! stay sharp; those closer or farther blur proportionally.
|
||||
//!
|
||||
//! ## Pipeline
|
||||
//! DoF operates in linear HDR space **after** bloom and **before** tone mapping:
|
||||
//! 1. CoC pass: reads the depth buffer, linearizes to world distance, computes
|
||||
//! per-pixel blur radius.
|
||||
//! 2. Blur pass: 12-tap disc blur with variable radius (from CoC), producing
|
||||
//! natural circular bokeh.
|
||||
//!
|
||||
//! ## Controls
|
||||
//! | Key | Action |
|
||||
//! |-----|--------|
|
||||
//! | Drag (LMB) | Orbit camera |
|
||||
//! | Wheel | Zoom |
|
||||
//! | `1` | Cinematic preset (focus=8m, strong blur) |
|
||||
//! | `2` | Subtle preset (focus=8m, gentle blur) |
|
||||
//! | `3` | Focus at 3m (near cubes sharp, far blurred) |
|
||||
//! | `4` | Focus at 15m (far cubes sharp, near blurred) |
|
||||
//! | `5` | DoF OFF |
|
||||
//! | `R` | Reset camera |
|
||||
//!
|
||||
//! ## Build & Run
|
||||
//! ```sh
|
||||
//! cargo run -p wsg-lib --example dof --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::{DoFConfig, ToneMapper, Transform};
|
||||
use wsg_lib::mesh::{cube, icosphere, plane};
|
||||
use wsg_lib::AppHandler;
|
||||
use wsg_lib::utils::WsgError;
|
||||
|
||||
struct DoFDemo {
|
||||
camera: CameraController,
|
||||
}
|
||||
|
||||
impl AppHandler for DoFDemo {
|
||||
fn setup(&mut self, app: &mut wsg_lib::App) {
|
||||
app.scene
|
||||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||||
.unwrap();
|
||||
|
||||
// Ground plane.
|
||||
app.scene
|
||||
.create_mesh("ground_mesh", plane(80.0, 80.0, 1, 1), None)
|
||||
.unwrap();
|
||||
app.scene
|
||||
.add_entity_with_transform(
|
||||
"ground",
|
||||
"ground_mesh",
|
||||
Transform::identity(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Row of cubes receding along -Z (distance ≈ 2 to 25 from camera at dist=8).
|
||||
app.scene
|
||||
.create_mesh("cube_mesh", cube(1.0), None)
|
||||
.unwrap();
|
||||
|
||||
for i in 0..20 {
|
||||
let z = 3.0 - i as f32 * 1.5; // from z=3 (close) to z=-25.5 (far)
|
||||
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();
|
||||
}
|
||||
|
||||
// A few spheres scattered to the sides for visual interest.
|
||||
app.scene
|
||||
.create_mesh("sphere_mesh", icosphere(0.7, 3), None)
|
||||
.unwrap();
|
||||
let sphere_positions = [
|
||||
Vec3::new(2.5, 0.7, -2.0),
|
||||
Vec3::new(-3.0, 0.7, -6.0),
|
||||
Vec3::new(3.5, 0.7, -10.0),
|
||||
Vec3::new(-2.0, 0.7, -14.0),
|
||||
Vec3::new(2.0, 0.7, -18.0),
|
||||
];
|
||||
for (i, pos) in sphere_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.4, -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.1;
|
||||
self.camera.distance = 8.0;
|
||||
self.camera.target = Vec3::new(0.0, 0.5, -8.0);
|
||||
self.camera.apply_to(app.scene.camera_mut());
|
||||
|
||||
eprintln!("[DoF] Initial: Cinematic (focus=8m, aperture=0.3, max_blur=12)");
|
||||
eprintln!("[DoF] Keys: 1=cinematic 2=subtle 3=focus 3m 4=focus 15m 5=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);
|
||||
|
||||
// DoF presets.
|
||||
if app.input.key_pressed(KeyCode::Digit1) {
|
||||
app.renderer_mut()
|
||||
.set_dof(Some(DoFConfig::cinematic(8.0)));
|
||||
eprintln!("[DoF] → Cinematic (focus=8m, aperture=0.3, max_blur=12)");
|
||||
}
|
||||
if app.input.key_pressed(KeyCode::Digit2) {
|
||||
app.renderer_mut()
|
||||
.set_dof(Some(DoFConfig::subtle(8.0)));
|
||||
eprintln!("[DoF] → Subtle (focus=8m, aperture=0.1, max_blur=8)");
|
||||
}
|
||||
if app.input.key_pressed(KeyCode::Digit3) {
|
||||
app.renderer_mut()
|
||||
.set_dof(Some(DoFConfig::new(3.0, 0.3, 12.0)));
|
||||
eprintln!("[DoF] → Focus 3m (near sharp, far blurred)");
|
||||
}
|
||||
if app.input.key_pressed(KeyCode::Digit4) {
|
||||
app.renderer_mut()
|
||||
.set_dof(Some(DoFConfig::new(15.0, 0.3, 12.0)));
|
||||
eprintln!("[DoF] → Focus 15m (far sharp, near blurred)");
|
||||
}
|
||||
if app.input.key_pressed(KeyCode::Digit5) {
|
||||
app.renderer_mut().set_dof(None);
|
||||
eprintln!("[DoF] → OFF");
|
||||
}
|
||||
if app.input.key_pressed(KeyCode::KeyR) {
|
||||
self.camera.yaw = 0.0;
|
||||
self.camera.pitch = 0.1;
|
||||
self.camera.distance = 8.0;
|
||||
self.camera.target = Vec3::new(0.0, 0.5, -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 — Depth of Field (Étape 26)")
|
||||
.size(1280, 720)
|
||||
.with_hdr(ToneMapper::Aces)
|
||||
.with_dof(DoFConfig::cinematic(8.0))
|
||||
.build()
|
||||
.await?;
|
||||
app.run(DoFDemo {
|
||||
camera: CameraController::default(),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//! # 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(),
|
||||
})
|
||||
}
|
||||
@@ -63,7 +63,7 @@ impl ApplicationHandler for App {
|
||||
|
||||
// Flat 2D rendering: `standard` in unlit mode (the frame+object bind groups are set by
|
||||
// draw_entity, the default frame matrix is the identity → NDC positions unchanged).
|
||||
let mut renderer = Renderer::new(&context, format, 800, 600, &ShadowConfig::default(), None, None, None);
|
||||
let mut renderer = Renderer::new(&context, format, 800, 600, &ShadowConfig::default(), None, None, None, None, None);
|
||||
renderer.set_unlit(true);
|
||||
|
||||
// 3. Material: uses renderer.device() and renderer.format()
|
||||
|
||||
Reference in New Issue
Block a user