eng doc
This commit is contained in:
+96
-99
@@ -1,132 +1,129 @@
|
||||
# Quickstart
|
||||
|
||||
Goal: a window showing an object, with the render loop handled by the library. You will only
|
||||
write three things: a struct implementing `AppHandler`, your scene declaration in `setup()`,
|
||||
and your `main()`.
|
||||
Get a window with a rotating cube on screen in ~30 lines. The full version with comments is
|
||||
in the [`simple`](../../lib/examples/meshes/simple.rs) example (2D quad, unlit) and
|
||||
[`cube`](../../lib/examples/meshes/cube.rs) (3D cube, lit).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A recent Rust toolchain (the library is **edition 2024** — run `rustup update` if needed).
|
||||
- A windowing environment (X11/Wayland on Linux, or native macOS/Windows).
|
||||
- WSG is **not published on crates.io**: it is consumed by file path.
|
||||
|
||||
## 1. Dependencies
|
||||
|
||||
In your application's `Cargo.toml`:
|
||||
## 1. Add the dependency
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
wsg-lib = { path = "/path/to/wsg/lib" }
|
||||
pollster = { version = "1", features = ["macro"] } # for #[pollster::main] (AppBuilder is async)
|
||||
wsg-lib = { path = "../lib" }
|
||||
glam = "0.29" # Vec3/Quat — re-exported but you need it in your own code
|
||||
winit = "0.30" # KeyCode/MouseButton for the input (only if you use app.input)
|
||||
```
|
||||
|
||||
## 2. The minimal application
|
||||
> The workspace pins `glam 0.29` and `winit 0.30`; match these versions to avoid
|
||||
> type mismatches.
|
||||
|
||||
This snippet is the [`simple`](../../lib/examples/meshes/simple.rs) example from the repo, almost
|
||||
verbatim: a flat two-tone quad, rendered automatically every frame.
|
||||
## 2. Implement `AppHandler`
|
||||
|
||||
Three mandatory methods (`setup`, `update`, `render`) and an optional event hook.
|
||||
|
||||
```rust
|
||||
use wsg_lib::app::AppBuilder;
|
||||
use wsg_lib::resources::Geometry;
|
||||
use wsg_lib::utils::WsgError;
|
||||
use wsg_lib::AppHandler;
|
||||
use glam::{Quat, Vec3};
|
||||
use winit::event::MouseButton;
|
||||
use winit::keyboard::KeyCode;
|
||||
use wsg_lib::camera::CameraController;
|
||||
use wsg_lib::prelude::*;
|
||||
|
||||
struct MyQuad;
|
||||
struct MyHandler {
|
||||
camera: CameraController,
|
||||
}
|
||||
|
||||
impl AppHandler for MyQuad {
|
||||
fn setup(&mut self, app: &mut wsg_lib::App) {
|
||||
// Flat 2D: the `standard` shader in unlit mode returns the vertex color as-is.
|
||||
app.renderer_mut().set_unlit(true);
|
||||
impl AppHandler for MyHandler {
|
||||
fn new() -> Self {
|
||||
Self { camera: CameraController::default() }
|
||||
}
|
||||
|
||||
fn setup(&mut self, app: &mut App) -> Result<(), String> {
|
||||
// A cube (primitive) + the standard material.
|
||||
app.scene.create_mesh("cube_mesh", cube(1.0), Some("cube_mat"))?;
|
||||
app.scene.add_entity("cube", "cube_mesh")?;
|
||||
|
||||
// A warm directional light + shadows on it.
|
||||
app.scene
|
||||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||||
.unwrap();
|
||||
.add_directional_light(Vec3::new(1.0, 1.2, 1.0).normalize(), [1.0, 0.98, 0.92], 1.5)?;
|
||||
app.scene.set_shadow_caster(Some(0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let geometry = Geometry::new(vec![
|
||||
[-0.5, 0.5, 0.0],
|
||||
[ 0.5, 0.5, 0.0],
|
||||
[ 0.5, -0.5, 0.0],
|
||||
[-0.5, -0.5, 0.0],
|
||||
])
|
||||
.with_normals(vec![[0.0, 0.0, 1.0]; 4])
|
||||
.with_colors(vec![
|
||||
[1.0, 0.0, 0.0, 1.0], // red
|
||||
[0.0, 1.0, 0.0, 1.0], // green
|
||||
[0.0, 0.0, 1.0, 1.0], // blue
|
||||
[1.0, 1.0, 0.0, 1.0], // yellow
|
||||
])
|
||||
.with_indices(vec![0, 1, 2, 0, 2, 3]);
|
||||
fn update(&mut self, app: &mut App) {
|
||||
// Spin the cube.
|
||||
let mut tf = *app.scene.entity_transform("cube").unwrap();
|
||||
tf.rotation = Quat::from_rotation_y(self.t) * tf.rotation;
|
||||
app.scene.set_entity_transform("cube", tf);
|
||||
self.t += 0.02;
|
||||
|
||||
// `None`: the scene injects its default material (`standard`) at render time.
|
||||
app.scene.create_mesh("quad_mesh", geometry, None).unwrap();
|
||||
app.scene.add_entity("quad", "quad_mesh").unwrap();
|
||||
// Camera: orbit (left-drag), zoom (wheel), reset (R), presets (1/2/3).
|
||||
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.6;
|
||||
self.camera.pitch = 0.35;
|
||||
self.camera.distance = 6.5;
|
||||
}
|
||||
self.camera.apply_to(app.scene.camera_mut());
|
||||
}
|
||||
|
||||
fn render(&mut self, app: &mut App) -> Result<(), String> {
|
||||
// Default implementation: renders the whole scene. Override only for custom passes.
|
||||
app.renderer().render_scene(app.context())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> Result<(), WsgError> {
|
||||
let app = AppBuilder::new().title("WSG Simple").build().await?;
|
||||
app.run(MyQuad)
|
||||
## 3. Build the app
|
||||
|
||||
```rust
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let app = AppBuilder::new()
|
||||
.title("My WSG app")
|
||||
.size(1024, 768)
|
||||
.with_culling(true) // optional: skip off-screen entities
|
||||
.with_shadows() // optional: enable shadow mapping
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let mut handler = MyHandler::new();
|
||||
app.run(&mut handler).await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Note: **no `wgpu` or `winit` imports** — the `App` facade encapsulates them entirely.
|
||||
`AppBuilder` methods you will use early:
|
||||
|
||||
## 3. What the library does for you
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `.title(…)` / `.size(w, h)` | Window |
|
||||
| `.with_vsync(false)` / `.with_frame_limit(n)` | Frame pacing (vsync off + 144 fps cap in the `demo`) |
|
||||
| `.with_culling(true)` | Opt-in frustum culling (see [GPU-driven](cameras/gpu-driven.md)) |
|
||||
| `.with_shadows()` | Opt-in shadow mapping (see [Shadows](lights/shadows.md)) |
|
||||
| `.with_hdr(ToneMapper::Aces)` | Opt-in HDR + tone mapping (see [HDR](effects/hdr.md)) |
|
||||
|
||||
The full lifecycle, as driven by `App::run` (technical details in
|
||||
[FRAME_LOOP](../tech/FRAME_LOOP.md)):
|
||||
## 4. Run it
|
||||
|
||||
```
|
||||
AppBuilder::build() creates the event loop
|
||||
│
|
||||
App::run(handler) starts the loop
|
||||
│
|
||||
resumed (winit) window + GPU (Instance/Surface/Adapter/Device/Queue) + Renderer
|
||||
│
|
||||
handler.setup(&mut app) ← you declare the scene here (once, GPU ready)
|
||||
│
|
||||
▼ per frame, in a loop:
|
||||
input.begin_frame() current frame's keyboard/mouse state
|
||||
handler.update(&mut app) ← your logic (motion, input, …)
|
||||
input.end_frame()
|
||||
handler.render(app, frame) ← default: app.render_scene(frame.view())
|
||||
│ (the whole scene is drawn automatically, one pass per frame)
|
||||
└─ present → next frame
|
||||
```sh
|
||||
cargo run -p wsg-lib --example cube # the reference "hello world" of the engine
|
||||
```
|
||||
|
||||
So you implement:
|
||||
|
||||
| Hook | When | Role | Default |
|
||||
|------|-------|------|---------|
|
||||
| `setup(&mut self, app)` | once, GPU ready | declare shaders, materials, textures, meshes, entities, lights, camera | empty |
|
||||
| `update(&mut self, app)` | every frame, before render | animate: transforms, input, lights… | empty |
|
||||
| `render(&mut self, app, frame)` | every frame, after update | **default**: draws the whole scene; override for custom rendering | `app.render_scene(frame.view())` |
|
||||
|
||||
Golden rule: **mutate the scene in `update()`** (and `setup()`), only read it in `render()`
|
||||
(model detailed in [ARCHI_RENDU](../tech/ARCHI_RENDU.md)).
|
||||
|
||||
## 4. Running it
|
||||
|
||||
From the WSG repo root (the examples live in `lib/examples/`, one folder per
|
||||
category: `meshes/`, `lights/`, `cameras/`, `effects/`):
|
||||
|
||||
| Command | What you see |
|
||||
|----------|--------------|
|
||||
| `cargo run -p wsg-lib --example simple` | the quad above (flat 2D, unlit) |
|
||||
| `cargo run -p wsg-lib --example cube` | a textured, lit, spinning cube (3D) |
|
||||
| `cargo run -p wsg-lib --example demo` | the full showcase: 6 primitives + lights + shadows + orbital camera |
|
||||
|
||||
For your own application: create a crate, add the §1 dependency, paste the §2 code into
|
||||
`src/main.rs`, and `cargo run`.
|
||||
Controls (in the `cube`/`demo` examples): **left-drag** orbit, **wheel** zoom, **R** reset
|
||||
camera, **1/2/3** view presets, **H** help overlay, **Esc** quit.
|
||||
|
||||
## 5. Where to go next
|
||||
|
||||
- Want a 3D object? → [Meshes](meshes.md)
|
||||
- Want to change the look / add a texture? → [Materials & textures](materials.md)
|
||||
- Want lights? → [Lights](lights.md)
|
||||
- Want to see everything at once? → the `demo` example ([Examples](examples.md))
|
||||
- [Meshes](meshes/meshes.md) — entities, transforms, custom geometries
|
||||
- [Materials & textures](meshes/materials.md) — diffuse textures, unlit mode
|
||||
- [Lights](lights/lights.md) — point/spot lights, ambient
|
||||
- [Camera & input](cameras/camera-input.md) — the full input API
|
||||
- [GPU-driven](cameras/gpu-driven.md) — culling, LOD, debugging the GPU path
|
||||
|
||||
## Links
|
||||
|
||||
- [User README](README.md) · [Meshes](meshes.md) · [Examples](examples.md)
|
||||
- [Root README](../../README.md) · [FRAME_LOOP](../tech/FRAME_LOOP.md) · [ARCHI_APP](../tech/ARCHI_APP.md)
|
||||
- [User README](README.md) · [Meshes](meshes/meshes.md) · [Examples](examples.md)
|
||||
- [Root README](../../README.md) · [FRAME_LOOP](../tech/FRAME_LOOP.md)
|
||||
|
||||
Reference in New Issue
Block a user