Files
Jérôme Bousquié 5ae978da23 doc
2026-09-21 10:07:32 +02:00

4.1 KiB
Raw Permalink Blame History

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), with an unlit mode for flat rendering.

1. Registering the shader

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 and the shaders/standard_shader.wgsl file).

2. Creating materials

// 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):

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 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():

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 and cube.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:

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) 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.