On this page
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 component:
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 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:
ellipseuses the height fraction.ringuses the inner-radius fraction.roundeduses the corner-radius fraction.frameandlineuse a thickness fraction.capsuleuses a height fraction.pieuses a full-turn sweep fraction.staruses valley depth.glyphuses the distance-field range.textured,circle, andtriangleignore it.
Shader contract
A .glsl file under a material root defines one material function:
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.
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.
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 |
record | Describes one material by name, build-local id, and source. |
Functions
| Function | Kind | Description |
|---|---|---|
addRoot |
Static | Adds a directory to search before the ones already known, so a game's materials are found alongside the engine's. |
define |
Static | Supplies a material from memory rather than a file. |
find |
Static | Returns a material id, or nil when no material has that name. |
id |
Static | Returns a material id or raises an error that names the available materials. |
install |
Static | Reads the materials and publishes the dispatch. |
name |
Static | Returns the material name represented by an id, or nil. |
names |
Static | Returns every material name in id order. |
reload |
Static | Re-reads every material and republishes the dispatch. |
reset |
Static | Forgets everything read, so a spec can start from the files again. |
Values
| Value | Type | Description |
|---|---|---|
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.
tecs.gfx.materials.Material.name field
Read-only. Reports the file name without its extension, which a game uses to request the material.
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.
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.
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.
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.
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 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.
function tecs.gfx.materials.find(name: string): integerArguments
| 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.
function tecs.gfx.materials.id(name: string): integerArguments
| 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.
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.
function tecs.gfx.materials.name(id: integer): stringArguments
| 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.
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 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.
function tecs.gfx.materials.reload(): boolean, stringArguments
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.
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.
tecs.gfx.materials.defaultName: string