réorg doc
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
//! # Exemple PBR — Metallic/Roughness + Normal Mapping (Étape 27)
|
||||
//!
|
||||
//! Démonstration du workflow PBR Cook-Torrance (GGX + Smith + Schlick) avec IBL hémisphérique.
|
||||
//!
|
||||
//! ## Scène
|
||||
//! - Sol : plan 20×20, PBR matte (metallic=0, roughness=0.8)
|
||||
//! - Cube métal : metallic=1.0, roughness=0.1 → reflet spéculaire net (miroir)
|
||||
//! - Cube plastique : metallic=0.0, roughness=0.4 → spéculaire large et doux
|
||||
//! - Cube rouillé : metallic=0.8, roughness=0.7 → métal rugueux
|
||||
//! - Sphere céramique : metallic=0.3, roughness=0.3
|
||||
//! - Cube normal map : bump procédural (sin wave)
|
||||
//!
|
||||
//! ## Contrôles
|
||||
//! | Touche | Action |
|
||||
//! |--------|--------|
|
||||
//! | Drag (LMB) | Orbite caméra |
|
||||
//! | Molette | Zoom |
|
||||
//! | `R` | Reset caméra |
|
||||
//!
|
||||
//! ## Lancement
|
||||
//! ```bash
|
||||
//! cargo run -p wsg-lib --example pbr
|
||||
//! ```
|
||||
|
||||
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::{ToneMapper, Transform};
|
||||
use wsg_lib::mesh::{cube, icosphere, plane};
|
||||
use wsg_lib::resources::Texture;
|
||||
use wsg_lib::AppHandler;
|
||||
use wsg_lib::utils::WsgError;
|
||||
|
||||
struct PbrDemo {
|
||||
camera: CameraController,
|
||||
}
|
||||
|
||||
impl Default for PbrDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
camera: CameraController::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppHandler for PbrDemo {
|
||||
fn setup(&mut self, app: &mut wsg_lib::App) {
|
||||
app.scene
|
||||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||||
.unwrap();
|
||||
|
||||
// Normal map procédurale 256×256 : bump sin(x)*sin(y).
|
||||
let bump_map = make_bump_normal_map(&app.context().device, &app.context().queue);
|
||||
app.scene.add_texture("bump_nm", bump_map).unwrap();
|
||||
|
||||
// Matériaux PBR.
|
||||
app.scene.add_material_pbr("floor", "standard", 0.0, 0.8).unwrap();
|
||||
app.scene.add_material_pbr("metal", "standard", 1.0, 0.1).unwrap();
|
||||
app.scene.add_material_pbr("plastic", "standard", 0.0, 0.4).unwrap();
|
||||
app.scene.add_material_pbr("rust", "standard", 0.8, 0.7).unwrap();
|
||||
app.scene.add_material_pbr("ceramic", "standard", 0.3, 0.3).unwrap();
|
||||
app.scene
|
||||
.add_material_pbr_textured("bump", "standard", 0.0, 0.5, None, Some("bump_nm"))
|
||||
.unwrap();
|
||||
|
||||
// Sol (plan 20×20).
|
||||
app.scene
|
||||
.create_mesh("floor_mesh", plane(1.0, 1.0, 1, 1), Some("floor"))
|
||||
.unwrap();
|
||||
{
|
||||
let mut tf = Transform::identity();
|
||||
tf.translation = Vec3::new(0.0, 0.0, 0.0);
|
||||
tf.scale = Vec3::new(20.0, 1.0, 20.0);
|
||||
app.scene.add_entity_with_transform("floor", "floor_mesh", tf).unwrap();
|
||||
}
|
||||
|
||||
// Cubes.
|
||||
app.scene.create_mesh("cube_mesh", cube(1.0), None).unwrap();
|
||||
let cubes: [(&str, &str, Vec3); 4] = [
|
||||
("c_metal", "metal", Vec3::new(-3.0, 0.5, 0.0)),
|
||||
("c_plastic", "plastic", Vec3::new(-1.0, 0.5, 0.0)),
|
||||
("c_rust", "rust", Vec3::new(1.0, 0.5, 0.0)),
|
||||
("c_bump", "bump", Vec3::new(3.0, 0.5, 0.0)),
|
||||
];
|
||||
for (id, mat, pos) in &cubes {
|
||||
app.scene
|
||||
.create_mesh(&format!("{id}_mesh"), cube(1.0), Some(mat))
|
||||
.unwrap();
|
||||
let mut tf = Transform::identity();
|
||||
tf.translation = *pos;
|
||||
app.scene
|
||||
.add_entity_with_transform(id, &format!("{id}_mesh"), tf)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Sphere céramique.
|
||||
app.scene
|
||||
.create_mesh("sphere_mesh", icosphere(0.5, 4), Some("ceramic"))
|
||||
.unwrap();
|
||||
{
|
||||
let mut tf = Transform::identity();
|
||||
tf.translation = Vec3::new(0.0, 0.5, -3.0);
|
||||
app.scene
|
||||
.add_entity_with_transform("s_ceramic", "sphere_mesh", tf)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Lumières.
|
||||
app.scene
|
||||
.add_directional_light(Vec3::new(-1.0, 2.0, 1.0).normalize(), [1.0, 0.95, 0.9], 2.0)
|
||||
.unwrap();
|
||||
app.scene
|
||||
.add_point_light(Vec3::new(0.0, 3.0, 2.0), [0.3, 0.5, 1.0], 8.0, 5.0)
|
||||
.unwrap();
|
||||
|
||||
|
||||
// Ambiance (IBL hémisphérique).
|
||||
app.scene.set_ambient([0.3, 0.35, 0.4]);
|
||||
|
||||
// Caméra.
|
||||
self.camera.yaw = 0.0;
|
||||
self.camera.pitch = 0.3;
|
||||
self.camera.distance = 8.0;
|
||||
self.camera.target = Vec3::new(0.0, 0.5, 0.0);
|
||||
self.camera.apply_to(app.scene.camera_mut());
|
||||
|
||||
eprintln!("[PBR] Scene: 6 PBR materials (metal/plastic/rust/ceramic/bump/floor)");
|
||||
eprintln!("[PBR] Drag=orbit, Wheel=zoom, R=reset");
|
||||
}
|
||||
|
||||
fn update(&mut self, app: &mut wsg_lib::App) {
|
||||
// Orbite caméra.
|
||||
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);
|
||||
self.camera.apply_to(app.scene.camera_mut());
|
||||
|
||||
// R = reset.
|
||||
if app.input.key_pressed(KeyCode::KeyR) {
|
||||
self.camera = CameraController::default();
|
||||
self.camera.target = Vec3::new(0.0, 0.5, 0.0);
|
||||
self.camera.apply_to(app.scene.camera_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Génère une normal map procédurale 256×256 : pattern sin(x*freq)*sin(y*freq) → bump.
|
||||
/// Chaque pixel : normale perturbée encodée en RGB (nx*0.5+0.5, ny*0.5+0.5, nz*0.5+0.5) * 255.
|
||||
fn make_bump_normal_map(device: &wgpu::Device, queue: &wgpu::Queue) -> Texture {
|
||||
let size = 256u32;
|
||||
let freq = 8.0;
|
||||
let mut pixels: Vec<u8> = vec![0u8; (size * size * 4) as usize];
|
||||
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let u = x as f32 / size as f32;
|
||||
let v = y as f32 / size as f32;
|
||||
let h = (u * freq * std::f32::consts::PI).sin()
|
||||
* (v * freq * std::f32::consts::PI).sin();
|
||||
let eps = 1.0 / size as f32;
|
||||
let hx = ((u + eps) * freq * std::f32::consts::PI).sin()
|
||||
* (v * freq * std::f32::consts::PI).sin();
|
||||
let hy = (u * freq * std::f32::consts::PI).sin()
|
||||
* ((v + eps) * freq * std::f32::consts::PI).sin();
|
||||
let dhdx = (hx - h) / eps;
|
||||
let dhdy = (hy - h) / eps;
|
||||
let n = Vec3::new(-dhdx, -dhdy, 1.0).normalize();
|
||||
let idx = ((y * size + x) * 4) as usize;
|
||||
pixels[idx] = ((n.x * 0.5 + 0.5) * 255.0).clamp(0.0, 255.0) as u8;
|
||||
pixels[idx + 1] = ((n.y * 0.5 + 0.5) * 255.0).clamp(0.0, 255.0) as u8;
|
||||
pixels[idx + 2] = ((n.z * 0.5 + 0.5) * 255.0).clamp(0.0, 255.0) as u8;
|
||||
pixels[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
Texture::from_rgba8(device, queue, size, size, &pixels, "bump_normal_map")
|
||||
.expect("bump normal map creation failed")
|
||||
}
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> Result<(), WsgError> {
|
||||
let app = AppBuilder::new()
|
||||
.title("WSG — PBR Metallic/Roughness")
|
||||
.size(1280, 720)
|
||||
.with_hdr(ToneMapper::Aces)
|
||||
.build()
|
||||
.await?;
|
||||
app.run(PbrDemo::default())
|
||||
}
|
||||
Reference in New Issue
Block a user