e5f3636b42
- 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)
125 lines
5.1 KiB
Markdown
125 lines
5.1 KiB
Markdown
# Materials & textures
|
||
|
||
A **`Material`** describes a mesh's appearance: it references a shader (by id) and
|
||
optionally a **diffuse texture**. Several materials pointing at the same shader share the
|
||
same compiled GPU pipeline (the `PipelineCache` held by the scene).
|
||
|
||
The engine ships a single shader: **`standard`** — multi-light Phong lighting (see
|
||
[Lights](../lights/lights.md)), with an **unlit** mode for flat rendering.
|
||
|
||
## 1. Registering the shader
|
||
|
||
```rust
|
||
app.scene
|
||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||
.unwrap();
|
||
```
|
||
|
||
> **Note**: `STANDARD_SHADER_PATH` points to an optional file on disk; if it is missing
|
||
> (the normal case for the embedded library), loading falls back to the shader **embedded at
|
||
> compile time** (`include_str!`, byte-identical). The fallback message you may see is
|
||
> therefore **expected and harmless**.
|
||
|
||
For a custom shader: register your `.wgsl` file path under an id of your choice (it must
|
||
expose the same bind groups as `standard` — frame @0, object @1, texture @2, shadow @3 — see
|
||
[ARCHI_RENDU](../../tech/ARCHI_RENDU.md) and the
|
||
[`shaders/standard_shader.wgsl`](../../../lib/src/shaders/standard_shader.wgsl) file).
|
||
|
||
## 2. Creating materials
|
||
|
||
```rust
|
||
// Textureless material: the color comes from per-vertex colors (or white by default).
|
||
app.scene.add_material_shader("mat", "standard").unwrap();
|
||
|
||
// Textured material: the texture must first be registered in the scene (below).
|
||
app.scene.add_material_texture("mat_textured", "standard", "my_texture").unwrap();
|
||
```
|
||
|
||
Binding a material to a mesh happens at mesh creation (see [Meshes](meshes.md)):
|
||
|
||
```rust
|
||
app.scene.create_mesh("cube_mesh", cube(1.0), Some("mat_textured")).unwrap();
|
||
```
|
||
|
||
A mesh created with `material = None` is rendered with the scene's **default material**
|
||
(`standard`, built once then cached) — that is the behavior of the
|
||
[`simple`](../../../lib/examples/meshes/simple.rs) example.
|
||
|
||
## 3. Diffuse textures
|
||
|
||
`Texture` is a GPU image in `Rgba8UnormSrgb` (linear sampler, repeat addressing).
|
||
Four constructors:
|
||
|
||
| Constructor | Usage |
|
||
|--------------|-------|
|
||
| `Texture::from_rgba8(device, queue, w, h, rgba, label)` | raw RGBA8 bytes (procedural) |
|
||
| `Texture::from_bytes(device, queue, label, bytes)` | encoded data (PNG/JPEG… via the `image` crate) |
|
||
| `Texture::from_file(device, queue, label, path)` | image file on disk |
|
||
| `Texture::white_placeholder(device, queue)` | 1×1 white — used internally when a material has no texture |
|
||
|
||
You get `device`/`queue` in `setup()` via `app.context()` (clone them out of the
|
||
borrow before touching `app.scene` again — see the pattern in every textured example):
|
||
|
||
```rust
|
||
let (device, queue) = {
|
||
let ctx = app.context();
|
||
(ctx.device.clone(), ctx.queue.clone())
|
||
};
|
||
let texture = Texture::from_rgba8(&device, &queue, 8, 8, &my_rgba, "checker").unwrap();
|
||
app.scene.add_texture("checker_texture", texture).unwrap();
|
||
app.scene.add_material_texture("ground_mat", "standard", "checker_texture").unwrap();
|
||
```
|
||
|
||
The exact snippet (8×8 checkerboard + stripes generation) is in
|
||
[`demo.rs`](../../../lib/examples/effects/demo.rs).
|
||
|
||
For **file textures** (the pattern used by `cube`, `demo`, `shadow`, `fog`, `dof`,
|
||
`culling` and `pbr`), load from `assets/textures/` and resolve the path against
|
||
`CARGO_MANIFEST_DIR` so the example works from any working directory:
|
||
|
||
```rust
|
||
const TEXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/assets/textures");
|
||
let texture = Texture::from_file(
|
||
&device,
|
||
&queue,
|
||
"uv_atlas",
|
||
&format!("{TEXTURES}/uv_texture.jpg"),
|
||
)
|
||
.unwrap();
|
||
```
|
||
|
||
The sampler is `Linear` + `Repeat`, so textures tile automatically when UVs exceed
|
||
[0,1] (e.g. the 80×80 fog floor tiles `ground.jpeg`).
|
||
|
||
> **Normal maps**: `Texture` is always `Rgba8UnormSrgb`, so the GPU sRGB-decodes on
|
||
> sample. A normal map is *linear* data — pre-encode its channels with the sRGB OETF
|
||
> before upload so the round-trip is the identity. See `load_normal_map` in
|
||
> [`pbr.rs`](../../../lib/examples/meshes/pbr.rs).
|
||
|
||
Two conditions for a texture to show up:
|
||
1. the material is created via `add_material_texture` (otherwise the 1×1 white placeholder
|
||
is bound — no visual effect, no regression);
|
||
2. the `Geometry` carries **UVs** (`.with_uvs(…)`). Without UVs, sampling is constant.
|
||
The procedural primitives (`uv_sphere`, `cube`, …) already provide them.
|
||
|
||
## 4. Unlit mode (flat / 2D rendering)
|
||
|
||
"Flat" rendering (vertex colors as-is, no lighting) is a **renderer switch**, not a material:
|
||
|
||
```rust
|
||
app.renderer_mut().set_unlit(true); // in setup()
|
||
```
|
||
|
||
This is the mode of the `simple` example (2D quad). In this mode the scene's lights are
|
||
ignored; per-vertex colors (or white) are rendered directly. 2D is a special case of 3D:
|
||
the single `standard` pipeline serves both.
|
||
|
||
> `clear_lights()` (see [Lights](../lights/lights.md)) gives a similar result but keeps the lit
|
||
> pipeline: only ambient stays active. Use it when you want to "turn off the lights" without
|
||
> switching to unlit.
|
||
|
||
## Links
|
||
|
||
- [User README](../README.md) · [Meshes](meshes.md) · [Lights](../lights/lights.md) · [Examples](../examples.md)
|
||
- [Root README](../../../README.md) · [ARCHI_RENDU](../../tech/ARCHI_RENDU.md)
|