Files
wsg/lib/examples/lights/emissive.rs
T
Jérôme Bousquié 24fbafc810 réorg doc
2026-09-25 19:08:20 +02:00

198 lines
6.5 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.
//! **Emissive Materials** — demonstrates the emissive property of the standard material.
//!
//! Shows objects with varying emissive intensities. Without HDR, emissive values > 1.0
//! are clamped to white (LDR). With HDR, they produce true "glow" that can feed the
//! bloom post-process.
//!
//! The scene contains 5 spheres with increasing emissive intensity (0.0 → 4.0),
//! arranged in a row. A lit cube serves as a non-emissive reference.
//!
//! ## Controls
//! | Key | Action |
//! |-----|--------|
//! | Drag (LMB) | Orbit camera |
//! | Wheel | Zoom |
//! | `R` | Reset camera |
//! | `E` | Exposure up (×1.3) |
//! | `Q` | Exposure down (÷1.3) |
//! | `0` | Reset exposure |
//! | `C` | Cycle emissive intensity (re-applies to all glow spheres) |
//!
//! ## Build & Run
//! ```sh
//! cargo run -p wsg-lib --example emissive
//! ```
//!
//! Run with `--features all-prims` if you don't have the default features.
use glam::{Quat, 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::AppHandler;
use wsg_lib::utils::WsgError;
/// Emissive intensities for the 5 glow spheres (left to right).
const INTENSITIES: [f32; 5] = [0.0, 0.5, 1.0, 2.0, 4.0];
/// RGB colors for the 5 glow spheres (rainbow-ish).
const COLORS: [[f32; 3]; 5] = [
[0.5, 0.5, 0.5], // gray (no glow)
[1.0, 0.3, 0.1], // orange
[1.0, 0.8, 0.0], // yellow
[0.2, 1.0, 0.4], // green
[0.3, 0.5, 1.0], // blue
];
struct EmissiveDemo {
camera: CameraController,
angle: f32,
/// Which intensity preset to apply (0-4 maps to a multiplier).
cycle_idx: usize,
}
impl AppHandler for EmissiveDemo {
fn setup(&mut self, app: &mut wsg_lib::App) {
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
// Ground.
app.scene
.create_mesh("ground_mesh", plane(10.0, 10.0, 1, 1), None)
.unwrap();
app.scene.add_entity("ground", "ground_mesh").unwrap();
// Reference cube (non-emissive).
app.scene
.create_mesh("cube_mesh", cube(0.6), None)
.unwrap();
let mut cube_tf = Transform::identity();
cube_tf.translation = Vec3::new(0.0, 0.3, 1.5);
app.scene
.add_entity_with_transform("cube_e", "cube_mesh", cube_tf)
.unwrap();
// 5 glow spheres in a row.
for i in 0..5 {
let mat_id = format!("glow_mat_{}", i);
let mesh_id = format!("glow_mesh_{}", i);
let entity_id = format!("glow_e_{}", i);
app.scene.add_material_shader(&mat_id, "standard").unwrap();
let c = COLORS[i];
let intensity = INTENSITIES[i];
app.scene
.set_material_emissive(&mat_id, [c[0], c[1], c[2], intensity])
.unwrap();
app.scene
.create_mesh(&mesh_id, icosphere(0.3, 3), Some(&mat_id))
.unwrap();
let x = (i as f32 - 2.0) * 0.9;
let mut tf = Transform::identity();
tf.translation = Vec3::new(x, 0.4, 0.0);
app.scene
.add_entity_with_transform(&entity_id, &mesh_id, tf)
.unwrap();
}
// Directional light.
let light_dir = Vec3::new(0.5, 1.0, 0.5).normalize();
app.scene
.add_directional_light(light_dir, [1.0, 0.95, 0.88], 1.0)
.unwrap();
app.scene.set_ambient([0.15, 0.15, 0.18]);
// Camera.
self.camera.yaw = 0.0;
self.camera.pitch = 0.2;
self.camera.distance = 5.5;
self.camera.target = Vec3::new(0.0, 0.3, 0.0);
self.camera.apply_to(app.scene.camera_mut());
}
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);
if app.input.key_pressed(KeyCode::KeyR) {
self.camera.yaw = 0.0;
self.camera.pitch = 0.2;
self.camera.distance = 5.5;
}
self.camera.apply_to(app.scene.camera_mut());
// Exposure.
if app.input.key_pressed(KeyCode::KeyE) {
app.set_exposure(app.exposure() * 1.3);
eprintln!("exposure = {:.2}", app.exposure());
}
if app.input.key_pressed(KeyCode::KeyQ) {
app.set_exposure(app.exposure() / 1.3);
eprintln!("exposure = {:.2}", app.exposure());
}
if app.input.key_pressed(KeyCode::Digit0) {
app.set_exposure(1.0);
eprintln!("exposure reset to 1.0");
}
// C: cycle emissive intensity multiplier (1x → 2x → 0.5x → back).
if app.input.key_pressed(KeyCode::KeyC) {
self.cycle_idx = (self.cycle_idx + 1) % 3;
let multiplier = match self.cycle_idx {
0 => 1.0,
1 => 2.0,
_ => 0.5,
};
for i in 0..5 {
let mat_id = format!("glow_mat_{}", i);
let c = COLORS[i];
let intensity = INTENSITIES[i] * multiplier;
if let Ok(()) = app.scene.set_material_emissive(&mat_id, [c[0], c[1], c[2], intensity]) {
eprintln!("emissive multiplier = {:.1}x", multiplier);
}
}
}
// Slow rotation.
self.angle += 0.01;
for i in 0..5 {
let entity_id = format!("glow_e_{}", i);
if let Some(base) = app.scene.entity_transform(&entity_id) {
let mut tf = *base;
tf.rotation = Quat::from_rotation_y(self.angle * (1.0 + i as f32 * 0.2));
app.scene.set_entity_transform(&entity_id, tf);
}
}
}
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> {
// HDR enabled so emissive > 1.0 produces true glow (not clamped to white).
let app = AppBuilder::new()
.title("WSG Emissive")
.size(960, 640)
.with_hdr(ToneMapper::Aces)
.build()
.await?;
app.run(EmissiveDemo {
camera: CameraController::default(),
angle: 0.0,
cycle_idx: 0,
})
}