- 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)
5.1 KiB
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_PATHpoints 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() (clone them out of the
borrow before touching app.scene again — see the pattern in every textured example):
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.
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:
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:
Textureis alwaysRgba8UnormSrgb, 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. Seeload_normal_mapinpbr.rs.
Two conditions for a texture to show up:
- the material is created via
add_material_texture(otherwise the 1×1 white placeholder is bound — no visual effect, no regression); - the
Geometrycarries 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.