# Materials
A `Material` is a render pipeline asset — WGSL shader source plus the fixed-function state (vertex layouts, cull mode, depth, targets) needed to compile it. It follows the same [asset pipeline](./the-asset-pipeline.md) pattern as everything else:
```rust,ignore
fn setup(backend: Read<Backend>, mut materials: Write<Assets<Material>>) {
let handle = Material::new(SHADER_SOURCE)
.with_label("unlit")
.with_vertex_layouts(vec![Vertex::layout()])
.with_entries(vec![/* see Bind Groups and Layouts */])
.with_targets(vec![ColorTargetState {
format: backend.surface_format(),
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
}])
.build_asset("unlit", &mut materials);
}
```
Defaults: `vs_main`/`fs_main` entry points, back-face culling, fill mode, no depth testing, sample count 1. Override with `with_vertex_entry`/`without_vertex_entry`, `with_cull_mode`/`without_cull_mode`, `with_depth`/`without_depth`, `with_polygon_mode`, `with_sample_count`.
`ColorTargetState::DEFAULT_TARGET` is a ready-made single opaque `Rgba8Unorm` target, if you don't need custom blending. `DepthStencilState::DEFAULT` is likewise a ready-made `Depth32Float`/`Less`/depth-write-enabled state, for the common opaque-3D case. Both section types (`ColorTargetState`, `BlendState`/`BlendComponent`, `DepthStencilState`) also implement plain `Default` (matching these same values), for `..Default::default()` struct-update syntax or generic code — e.g. `ColorTargetState { format: my_format, ..Default::default() }` to override just the format.
## `Material::standard`: presets for the common case
If most of your materials are ordinary opaque 3D geometry using the built-in `Vertex` type, `Material::standard(shader_source)` saves retyping the same three calls every time — it's `Material::new(shader_source)` pre-chained with `.with_vertex_layouts(vec![Vertex::layout()])`, a single opaque target in the *actual* surface format, and `.with_depth(DepthStencilState::DEFAULT)`:
```rust,ignore
let handle = Material::standard(SHADER_SOURCE)
.with_label("unlit")
.with_entries(vec![/* see Bind Groups and Layouts */])
.build_asset("unlit", &mut materials);
```
The surface format isn't a hardcoded guess (the real one varies by platform/backend — `Bgra8Unorm` is common on Windows/DX12, not the `Rgba8Unorm` `DEFAULT_TARGET` assumes), and it isn't looked up when you call `standard()` either — `standard()` takes no `Backend` reference, same as `new`. It's resolved against the real `Backend` at upload time instead, the same way a `Texture`'s `MipLevels` gets resolved against the texture's actual size only once that's known, rather than at construction.
It's still a plain builder underneath — chain `.with_vertex_layouts(...)`/`.with_targets(...)`/`.with_depth(...)`/`.without_depth()` afterwards to override any one of the three for a material that doesn't fit the common case (a custom vertex type, a blended target, no depth test). For anything more different than that, start from `Material::new` instead.
## Supplying bind group values: `MaterialInstance`
A `Material` only declares what its bind group *shape* is. The actual textures/samplers/uniforms come from a separate asset, `MaterialInstance` (a type alias for `BindingInstance<Material>`):
```rust,ignore
fn make_instance(mut materials: Write<Assets<Material>>, mut instances: Write<Assets<MaterialInstance>>) {
let material_handle = /* from above */;
MaterialInstance::new(material_handle)
.with_texture("albedo", texture_handle)
.with_sampler("albedo_sampler", SamplerKind::LinearRepeat)
.with_uniform("camera", bytemuck::bytes_of(&camera_data).to_vec())
.build_asset("player_instance", &mut instances);
}
```
Names must match the names given in `with_entries` (see [Bind Groups and Layouts](./bind-groups.md)) — a mismatch fails to build silently (the upload retries forever, since the binding lookup returns `None`). The uploaded `GPUMaterialInstance` gives you `update(name, data)` to overwrite a bound uniform/storage buffer in place, without rebuilding the whole bind group — handy for a per-frame value like a camera matrix.
`.with_uniform`/`.with_storage` build a fresh buffer from raw bytes each time. If you already have a [`Buffer`](./buffers.md) — e.g. one written to by a compute pass — bind it directly with `.with_buffer(name, existing_buffer)` instead; no new buffer is created, so `existing_buffer` must already carry the usage flags (`BufferUsages::UNIFORM`/`::STORAGE`) matching how `name` was declared. `.with_dynamic_buffer(name, existing_dynamic_buffer)` does the same for a [`DynamicBuffer`](./buffers.md#dynamic-buffers) — the target's own entry for `name` must have been declared with `has_dynamic_offset: true` to match.