# tecs.gfx.materials
Material selection, shader authoring, built-ins, and reload rules.
A material decides a fragment's color, coverage, surface normal, and lighting
response. Resolve its id by name when creating a
[`Material`](/modules/gfx/#tecs.gfx.Material) component:
```teal
tecs.gfx.materials.addRoot("game/materials/")
world:spawn(
tecs.Transform2D(120, 80, 0, 1, 0, 64, 64),
tecs.gfx.Material(tecs.gfx.materials.id("rounded"), 0.25),
tecs.gfx.Tint(0.2, 0.6, 1.0, 1.0),
tecs.gfx.Renderable2D()
)
```
Add game roots before loading shaders or resolving ids. An entity without a
[`Material`](/modules/gfx/#tecs.gfx.Material) uses `textured` at id zero. Remaining ids follow
sorted material names. Adding or removing a file may renumber them, so
persisted state stores a name and resolves it again.
`Material.param` supplies one scalar from zero to one. Built-ins use it as
follows:
- `ellipse` uses the height fraction.
- `ring` uses the inner-radius fraction.
- `rounded` uses the corner-radius fraction.
- `frame` and `line` use a thickness fraction.
- `capsule` uses a height fraction.
- `pie` uses a full-turn sweep fraction.
- `star` uses valley depth.
- `glyph` uses the distance-field range.
- `textured`, `circle`, and `triangle` ignore it.
## Shader contract
A `.glsl` file under a material root defines one `material` function:
```glsl
MaterialOutput material(MaterialInput frag) {
MaterialOutput result = materialDefaults();
float radius = mix(0.02, 0.20, frag.param);
result.albedo = texture(images, frag.uv) * frag.color;
result.coverage = -sdRoundedBox(
frag.local,
vec2(0.5),
radius
);
result.lit = 1.0;
return result;
}
```
`frag.local` runs from -0.5 to 0.5 inside the quad. `frag.uv`, `frag.color`,
and `frag.param` carry the image coordinates, tint, and instance parameter.
`frag.blended` says whether the fragment reaches a pass that blends it. Start
from `materialDefaults`, then set `albedo`, `normal`, `orm`, `lit`, `emission`,
and `coverage`.
Coverage above zero keeps a fragment; zero or below discards it. The deferred
lane does not blend partial coverage. A tint alpha below one routes the
instance to the blended lane instead, and so does a particle effect whose
`render.blend` asks for it. A material that resolves an edge by discarding should
put the edge in alpha where `frag.blended` is set, which is what `textured` does.
## Surface properties
`orm` carries ambient occlusion, roughness, and metallic in RGB. Alpha is
reserved. `materialDefaults` returns `vec4(1.0, 0.5, 0.0, 1.0)`: fully
unoccluded, medium roughness, and non-metallic.
Ambient occlusion multiplies ambient lighting only. A point light is a known
directional contribution and keeps its own brightness; shadow components
control whether that light reaches a fragment. Roughness and metallic are
stored in the G-buffer for the physically based light term. The current Lambert
term does not consume them yet.
```glsl
MaterialOutput material(MaterialInput frag) {
MaterialOutput result = materialDefaults();
result.albedo = texture(images, frag.uv) * frag.color;
result.coverage = 1.0;
// The instance parameter controls authored ambient occlusion.
result.orm = vec4(frag.param, 0.8, 0.0, 1.0);
return result;
}
```
The ORM attachment is eight bits a channel, so values outside zero to one are
clamped when geometry writes them.
## Emission
`emission` is light the surface gives off: `rgb` its color and `a` how much of
it. The renderer adds `rgb * a` to the resolved pixel after the lighting, so an
emissive surface is as bright in total darkness as under a lamp and an occluder's
shadow does not dim it. This differs from `lit = 0.0`, which replaces the
lighting with the albedo; a surface may take light and emit at the same time,
which is what a lit lamp with a glowing filament is.
```glsl
MaterialOutput material(MaterialInput frag) {
MaterialOutput result = materialDefaults();
result.albedo = texture(images, frag.uv) * frag.color;
result.coverage = 1.0;
// A warm glow at the strength the instance asked for.
result.emission = vec4(1.0, 0.6, 0.2, frag.param);
return result;
}
```
Every built-in material emits nothing, so a scene glows only where a material
says it does. Per-entity strength and color come from `frag.param` and
`frag.color`, which a material reads as it chooses.
The emission attachment is eight bits a channel, so a value above one is clamped
to one. Keep the color in range and vary the strength.
An entity in the blended lane adds its own emission to its own color and reaches
the emission attachment not at all, because the forward pass runs after the
G-buffer has been resolved. It therefore glows, and it does not reach a later
pass that reads the attachment.
## Reloads
`reload` accepts edits to existing material bodies. It refuses additions,
removals, and renames because those changes can renumber live components. A
refusal restores the previous set. Packaged builds cannot compile changed
material source at runtime.
## Module contents
### Types
| Type | Kind | Description |
| --- | --- | --- |
| [`Material`](/modules/gfx/materials/#tecs.gfx.materials.Material) | record | Describes one material by name, build-local id, and source. |
### Functions
| Function | Kind | Description |
| --- | --- | --- |
| [`addRoot`](/modules/gfx/materials/#tecs.gfx.materials.addRoot) | Static | Adds a directory to search before the ones already known, so a game's materials are found alongside the engine's. |
| [`define`](/modules/gfx/materials/#tecs.gfx.materials.define) | Static | Supplies a material from memory rather than a file. |
| [`find`](/modules/gfx/materials/#tecs.gfx.materials.find) | Static | Returns a material id, or nil when no material has that name. |
| [`id`](/modules/gfx/materials/#tecs.gfx.materials.id) | Static | Returns a material id or raises an error that names the available materials. |
| [`install`](/modules/gfx/materials/#tecs.gfx.materials.install) | Static | Reads the materials and publishes the dispatch. |
| [`name`](/modules/gfx/materials/#tecs.gfx.materials.name) | Static | Returns the material name represented by an id, or nil. |
| [`names`](/modules/gfx/materials/#tecs.gfx.materials.names) | Static | Returns every material name in id order. |
| [`reload`](/modules/gfx/materials/#tecs.gfx.materials.reload) | Static | Re-reads every material and republishes the dispatch. |
| [`reset`](/modules/gfx/materials/#tecs.gfx.materials.reset) | Static | Forgets everything read, so a spec can start from the files again. |
### Values
| Value | Type | Description |
| --- | --- | --- |
| [`defaultName`](/modules/gfx/materials/#tecs.gfx.materials.defaultName) | `string` | Read-only. Reports the material name used by entities without a Material component. |
## Types
### tecs.gfx.materials.Material record
Describes one material by name, build-local id, and source.
```teal
record tecs.gfx.materials.Material
name: string
id: integer
source: string
end
```
#### tecs.gfx.materials.Material.name field
Read-only. Reports the file name without its extension, which a
game uses to request the material.
```teal
tecs.gfx.materials.Material.name: string
```
#### tecs.gfx.materials.Material.id field
Read-only. Reports the id an instance carries to select this material. Assigned by
`install` from sorted order, so it holds only for the set of files
that were present when the dispatch was built.
```teal
tecs.gfx.materials.Material.id: integer
```
#### tecs.gfx.materials.Material.source field
Read-only. Contains the material's GLSL as read. Renamed on the way into the dispatch
rather than here, so this is still the text the file holds.
```teal
tecs.gfx.materials.Material.source: string
```
## Functions
### tecs.gfx.materials.addRoot Static
Adds a directory to search before the ones already known, so a game's
materials are found alongside the engine's.
```teal
function tecs.gfx.materials.addRoot(path: string)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string` | The function searches this directory before every existing root, so a game's file of a given name beats the engine's. It adds a trailing slash when needed. This call does not read the directory; it schedules the next `install`. |
#### Returns
None.
### tecs.gfx.materials.define Static
Supplies a material from memory rather than a file.
For a spec, and for a game that generates one at build time. Beats a
file of the same name in any root, and renumbers the set on the next
`install` as adding a file would.
```teal
function tecs.gfx.materials.define(name: string, source: string)
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | The name a game asks for, matching a file stem. |
| `source` | `string` | The material's GLSL, taken as given and renamed only on the way into the dispatch. |
#### Returns
None.
### tecs.gfx.materials.find Static
Returns a material id, or nil when no material has that name.
What `id` is built on, for a caller with somewhere better to put the
refusal than an error raised from here. Reading a
[`Material`](/modules/gfx/#tecs.gfx.Material) back out of a snapshot is that
caller: the name it holds is a fact about the file
rather than about the call site, and the message says so.
```teal
function tecs.gfx.materials.find(name: string): integer
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | The material's file name without its extension. |
#### Returns
| Type | Description |
| --- | --- |
| `integer` | Returns the id, or nil for an unknown name. The function installs first and resolves against the complete on-disk set. |
### tecs.gfx.materials.id Static
Returns a material id or raises an error that names the available
materials.
Resolve by name because the file set determines numbering. Adding an
earlier material may change later ids.
```teal
function tecs.gfx.materials.id(name: string): integer
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | The material's file name without its extension. |
#### Returns
| Type | Description |
| --- | --- |
| `integer` | The id an instance carries to select it. Raises on a name nothing has. The error lists available names because a misspelled material indicates a build mistake, not a runtime condition. |
### tecs.gfx.materials.install Static
Reads the materials and publishes the dispatch. Idempotent.
Called by shader loading rather than by a game, so a fragment shader
cannot be built before the materials it dispatches to are known. Adding
a root or defining a material puts this back on, and the next load
rebuilds.
```teal
function tecs.gfx.materials.install()
```
#### Arguments
None.
#### Returns
None.
### tecs.gfx.materials.name Static
Returns the material name represented by an id, or nil.
The reverse of `id`, and what a snapshot writes in place of the number.
Nothing is kept in step to answer it: `order` already holds the names at
their ids, counting from one, so this is the lookup the numbering was
assigned from rather than a second copy of it.
```teal
function tecs.gfx.materials.name(id: integer): string
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `id` | `integer` | An id as an instance carries it. Nil answers nil rather than raising, so a component read back with no material needs no guard. |
#### Returns
| Type | Description |
| --- | --- |
| `string` | The material's name, or nil when nothing has that id. |
### tecs.gfx.materials.names Static
Returns every material name in id order.
The default occupies id zero, followed by the other names
alphabetically. The module fixes the default's position because zero on
an instance must select it.
```teal
function tecs.gfx.materials.names(): {string}
```
#### Arguments
None.
#### Returns
| Type | Description |
| --- | --- |
| `{string}` | A fresh list the caller owns, indexed from one while the ids it describes count from zero. |
### tecs.gfx.materials.reload Static
Re-reads every material and republishes the dispatch.
This function refuses a changed material set. Ids come from sorted name
order, so a file appearing or disappearing renumbers every later
material. A live [`Material`](/modules/gfx/#tecs.gfx.Material) component still holds
the number resolved before that change. Editing a body reloads; adding
or removing one requires a restart.
The module retains materials supplied through `define` because they came
from memory rather than a root.
```teal
function tecs.gfx.materials.reload(): boolean, string
```
#### Arguments
None.
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | Returns true when the function rebuilds the dispatch. |
| `string` | Returns the refusal reason, or nil after a rebuild. |
### tecs.gfx.materials.reset Static
Forgets everything read, so a spec can start from the files again.
```teal
function tecs.gfx.materials.reset()
```
#### Arguments
None.
#### Returns
None.
## Values
### tecs.gfx.materials.defaultName variable
Read-only. Reports the material name used by entities without a
Material component.
```teal
tecs.gfx.materials.defaultName: string
```