Please review: LLM-generated Craft shader documentation

I’ve been having Claude and ChatGPT make custom Craft shaders for a while now. I just asked Claude to generate documentation for them. This is what it did.

As raw markdown text:


# Craft Custom Shaders

Custom shaders in Craft allow you to define how 3D geometry is rendered by writing vertex and fragment (surface) shader code and controlling the rendering pipeline. Shaders are defined as Lua tables that describe material properties, rendering state, and GLSL shader code.

All custom shaders must be registered with the Craft rendering system before they can be used. Once registered, they become available as materials that can be applied to any renderable entity.

## Overview

A Craft shader consists of four main parts:

1. **Metadata** — name and unique identifier
1. **Options** — feature flags that enable rendering capabilities (lighting, shadows, etc.)
1. **Properties** — uniform variables exposed to Lua that control shader behavior
1. **Pass** — the rendering pipeline, including vertex/fragment code and render state

## Defining and Registering a Shader

Shaders are defined as Lua tables and must be registered before use:

```lua
myShader = {
    name = "My Custom Shader",
    options = { USE_COLOR = { true } },
    properties = { map = { "texture2D", nil } },
    pass = { base = "Surface", vertex = [[...]], surface = [[...]] }
}

craft.shader.add(myShader)
local material = craft.material("My Custom Shader")
```

## Shader Table Structure

### `name` (string, required)

The unique identifier for this shader. This name is used when calling `craft.material(name)`.

```lua
myShader = {
    name = "Terrain Shader",
    -- ...
}
```

### `options` (table, optional)

A table of feature flags that enable/disable rendering capabilities. Each option is a key-value pair where the value is a table containing `true` or conditional lists.

Options control which built-in rendering features are compiled into the shader. Enabling unnecessary options may impact performance.

**Common options:**

|Option                  |Effect                                                                   |
|------------------------|-------------------------------------------------------------------------|
|`USE_COLOR`             |Enable vertex color support in `Input.color`                             |
|`USE_LIGHTING`          |Enable surface normal and lighting calculations                          |
|`STANDARD`              |Enable physically-based rendering (PBR) with standard material properties|
|`PHYSICAL`              |Enhanced PBR with metalness and roughness support                        |
|`ENVMAP_TYPE_CUBE`      |Support cube texture environment maps                                    |
|`ENVMAP_MODE_REFLECTION`|Use environment map for reflections                                      |
|`USE_ENVMAP`            |Enable environment mapping (conditional on `{"envMap"}` property)        |

```lua
options = {
    USE_COLOR = { true },                    -- Always enabled
    USE_LIGHTING = { true },
    STANDARD = { true },
    USE_ENVMAP = { false, { "envMap" } }    -- Enabled only if envMap property is set
}
```

### `properties` (table, optional)

A table defining uniform variables that can be controlled from Lua code. Each property is a key-value pair: the key is the property name, the value is a descriptor table.

Property descriptors have two elements:

1. **Type** (string) — `"float"`, `"vec2"`, `"vec3"`, `"vec4"`, `"int"`, `"texture2D"`, `"cubeTexture"`, `"color"`, or `"bool"`
1. **Default value** — the initial value (or `nil` for uninitialized textures)

```lua
properties = {
    map = { "texture2D", nil },              -- Texture, no default
    tiling = { "float", 1.0 },               -- Float with default 1.0
    time = { "float", 0.0 },
    roughness = { "float", 0.7 },
    heightMap = { "cubeTexture", nil },      -- Cube texture
    diffuse = { "color", color(255, 255, 255) },
    enabled = { "bool", false }
}
```

Properties can be set on a material instance:

```lua
local mat = craft.material("My Shader")
mat.map = readImage(asset.MyTexture)
mat.tiling = 2.0
mat.roughness = 0.5
```

### `pass` (table, required)

Defines the rendering pipeline and shader code. Contains render state settings and the vertex/surface shader stages.

#### Pass: Render State

|Parameter       |Type  |Default      |Purpose                                                                   |
|----------------|------|-------------|--------------------------------------------------------------------------|
|`base`          |string|`"Surface"`  |Base rendering mode; typically `"Surface"` for standard rendering         |
|`blendMode`     |string|`"disabled"` |How to blend with framebuffer: `"disabled"`, `"additive"`, `"multiply"`   |
|`depthWrite`    |bool  |`true`       |Whether to write depth values for depth testing                           |
|`depthFunc`     |string|`"lessEqual"`|Depth test function: `"less"`, `"lessEqual"`, `"equal"`, `"greater"`, etc.|
|`renderQueue`   |string|`"solid"`    |Render order: `"solid"` (opaque), `"transparent"`, or `"overlay"`         |
|`colorMask`     |table |`{"rgba"}`   |Which color channels to write: `{"r"}`, `{"rg"}`, `{"rgba"}`, etc.        |
|`cullFace`      |string|`"back"`     |Which faces to cull: `"back"`, `"front"`, `"disabled"`                    |
|`receiveShadows`|bool  |`false`      |Whether the material receives shadow maps                                 |
|`castShadows`   |bool  |`false`      |Whether objects with this material cast shadows                           |

```lua
pass = {
    base = "Surface",
    blendMode = "disabled",
    depthWrite = true,
    depthFunc = "lessEqual",
    renderQueue = "solid",
    colorMask = { "rgba" },
    cullFace = "back",
    receiveShadows = true,
    castShadows = true,
    -- ... vertex and surface code follow
}
```

#### Pass: Vertex Stage

```lua
vertex = [[
    uniform mat4 modelViewProjection;
    uniform mat4 modelMatrix;
    
    void vertex(inout Vertex v, out Input o)
    {
        // Modify vertex position, normal, color, etc.
        // Prepare data for fragment stage
    }
]]
```

The `vertex` function receives:

- **`v : Vertex`** (inout) — The current vertex with position, normal, color, uv
- **`o : Input`** (out) — Data to pass to the fragment stage

**Vertex struct fields:**

- `position : vec3` — Object-space vertex position
- `normal : vec3` — Object-space normal
- `color : vec4` — Vertex color (R, G, B, A)
- `tangent : vec3` — Tangent for normal mapping (if available)
- `uv : vec2` — Texture coordinates

**Available uniforms in vertex stage:**

- `modelViewProjection : mat4` — Combined transform to screen space
- `modelMatrix : mat4` — Object-to-world transform
- `normalMatrix : mat3` — Normal transformation (inverse transpose of modelMatrix)

#### Pass: Fragment/Surface Stage

```lua
surface = [[
    void surface(in Input IN, inout SurfaceOutput o)
    {
        // Sample textures and compute material properties
        // Set diffuse, normal, roughness, metalness, emission, etc.
    }
]]
```

The `surface` function receives:

- **`IN : Input`** (in) — Interpolated data from vertex stage
- **`o : SurfaceOutput`** (inout) — Material properties for lighting

**Input struct fields** (populated by vertex stage or built-in):

- `uv : vec2` — Texture coordinates
- `color : vec4` — Interpolated vertex color
- `normal : vec3` — Interpolated surface normal (if `USE_LIGHTING` enabled)
- `position : vec3` — World-space position (if `USE_LIGHTING` enabled)
- `viewDir : vec3` — Normalized direction toward camera (if `USE_LIGHTING` enabled)

**SurfaceOutput struct fields:**

- `diffuse : vec3` — Surface color (RGB)
- `emission : vec3` — Emitted color (for glow effects)
- `emissive : float` — Emissive intensity multiplier
- `roughness : float` — Surface roughness (0.0 = mirror, 1.0 = rough)
- `metalness : float` — Metallic appearance (0.0 = non-metal, 1.0 = metal)
- `opacity : float` — Alpha transparency (0.0 = transparent, 1.0 = opaque)
- `occlusion : float` — Ambient occlusion factor
- `normal : vec3` — Perturbed surface normal (for normal maps)

## Common Shader Patterns

### Unlit Shader (Emissive)

Renders without lighting, useful for UI, skyboxes, or glowing surfaces:

```lua
unlit = {
    name = "Unlit",
    options = { USE_COLOR = { true } },
    properties = { map = { "texture2D", nil } },
    pass = {
        base = "Surface",
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                vec4 col = texture(map, IN.uv);
                o.diffuse = col.rgb;
                o.emissive = 1.0;
                o.emission = col.rgb;
            }
        ]]
    }
}
```

### Lit Shader with PBR

Uses physically-based rendering with roughness and metalness:

```lua
pbr = {
    name = "PBR",
    options = {
        USE_COLOR = { true },
        USE_LIGHTING = { true },
        STANDARD = { true },
        PHYSICAL = { true }
    },
    properties = {
        map = { "texture2D", nil },
        roughness = { "float", 0.7 },
        metalness = { "float", 0.0 }
    },
    pass = {
        base = "Surface",
        receiveShadows = true,
        castShadows = true,
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            uniform float roughness;
            uniform float metalness;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = texture(map, IN.uv).rgb;
                o.roughness = roughness;
                o.metalness = metalness;
            }
        ]]
    }
}
```

### Displacement Mapping (Height-based Vertex Deformation)

Displaces vertices along normals using a height map:

```lua
displaced = {
    name = "Displaced",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        heightMap = { "texture2D", nil },
        scale = { "float", 1.0 }
    },
    pass = {
        base = "Surface",
        vertex = [[
            uniform sampler2D heightMap;
            uniform float scale;
            void vertex(inout Vertex v, out Input o)
            {
                float h = texture(heightMap, v.uv).r;
                v.position += v.normal * (h * scale);
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.8);
            }
        ]]
    }
}
```

### Animated Vertex Deformation

Uses elapsed time to animate vertex positions:

```lua
wave = {
    name = "Wave",
    properties = { time = { "float", 0.0 } },
    pass = {
        base = "Surface",
        vertex = [[
            uniform float time;
            void vertex(inout Vertex v, out Input o)
            {
                float wave = sin(v.position.x + time) * 0.1;
                v.position.y += wave;
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.5, 0.7, 1.0);
            }
        ]]
    }
}
```

### Cubemap Displacement (Procedural Planet)

Displaces a sphere along view-dependent directions using a cube texture:

```lua
planetDisplaced = {
    name = "Planet Displaced",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        heightMap = { "cubeTexture", nil },
        scale = { "float", 1.0 }
    },
    pass = {
        base = "Surface",
        vertex = [[
            uniform samplerCube heightMap;
            uniform float scale;
            void vertex(inout Vertex v, out Input o)
            {
                vec3 dir = normalize(v.normal);
                float h = texture(heightMap, dir).r;
                v.position += v.normal * (h * scale);
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.2, 0.8, 0.3);
            }
        ]]
    }
}
```

## Updating Shader Properties at Runtime

Shader properties can be updated each frame from Lua:

```lua
function setup()
    craft.shader.add(myTimeShader)
    mat = craft.material("My Time Shader")
    local e = scene:entity()
    e.model = craft.model.cube(vec3(1,1,1))
    e.material = mat
end

function draw()
    scene:update(DeltaTime)
    mat.time = ElapsedTime  -- Update time uniform each frame
    scene:draw()
end
```

## Texture Coordinates and Sampling

Texture coordinates are stored in `Input.uv` and typically range from (0, 0) at one corner to (1, 1) at the opposite corner. Tiling can be applied by multiplying the UV:

```lua
surface = [[
    uniform sampler2D map;
    uniform float tiling;
    void surface(in Input IN, inout SurfaceOutput o)
    {
        vec4 col = texture(map, IN.uv * tiling);
        o.diffuse = col.rgb;
    }
]]
```

For cube textures, sample using a normalized 3D direction vector:

```lua
surface = [[
    uniform samplerCube envMap;
    void surface(in Input IN, inout SurfaceOutput o)
    {
        vec3 dir = normalize(IN.viewDir);
        vec4 col = texture(envMap, dir);
        o.diffuse = col.rgb;
    }
]]
```

## Normal Mapping

Perturb surface normals using a normal map texture to add surface detail without extra geometry:

```lua
normalMapped = {
    name = "Normal Mapped",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        map = { "texture2D", nil },
        normalMap = { "texture2D", nil }
    },
    pass = {
        base = "Surface",
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            uniform sampler2D normalMap;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = texture(map, IN.uv).rgb;
                vec3 n = texture(normalMap, IN.uv).rgb;
                o.normal = normalize(n * 2.0 - 1.0);
            }
        ]]
    }
}
```

## Blending and Transparency

Control how a material blends with the framebuffer:

```lua
pass = {
    blendMode = "additive",    -- Add color (for light, glow)
    -- OR
    blendMode = "multiply",    -- Multiply color (for shadows)
    -- OR
    blendMode = "disabled"     -- Replace (default, for opaque)
}
```

For transparency, set `renderQueue` and control `opacity` in the surface function:

```lua
pass = {
    renderQueue = "transparent",
    surface = [[
        void surface(in Input IN, inout SurfaceOutput o)
        {
            o.opacity = 0.5;  -- 50% transparent
        }
    ]]
}
```

## Debugging and Edge Cases

**Shader Compilation Errors:** If a shader fails to compile, Craft will fall back to a default material. Check the device log for GLSL error messages.

**Coordinate Systems:** Object-space coordinates (vertex positions, normals) are local to the model. Use `modelMatrix` to transform to world space. Screen coordinates in `modelViewProjection` transform directly to clip space.

**Precision:** Use `highp` (high precision) for calculations that require accuracy; use `lowp` for performance-sensitive mobile code.

```lua
surface = [[
    precision highp float;
    // ... shader code
]]
```

## See Also

- `craft.material(name)` — Create a material instance from a registered shader
- `craft.entity` — Apply materials to renderable entities
- `craft.renderer` — The component that applies materials
- `craft.shader.add(shader)` — Register a shader definition

And pasted here directly:

Craft Custom Shaders

Custom shaders in Craft allow you to define how 3D geometry is rendered by writing vertex and fragment (surface) shader code and controlling the rendering pipeline. Shaders are defined as Lua tables that describe material properties, rendering state, and GLSL shader code.

All custom shaders must be registered with the Craft rendering system before they can be used. Once registered, they become available as materials that can be applied to any renderable entity.

Overview

A Craft shader consists of four main parts:

  1. Metadata — name and unique identifier
  2. Options — feature flags that enable rendering capabilities (lighting, shadows, etc.)
  3. Properties — uniform variables exposed to Lua that control shader behavior
  4. Pass — the rendering pipeline, including vertex/fragment code and render state

Defining and Registering a Shader

Shaders are defined as Lua tables and must be registered before use:

myShader = {
    name = "My Custom Shader",
    options = { USE_COLOR = { true } },
    properties = { map = { "texture2D", nil } },
    pass = { base = "Surface", vertex = [[...]], surface = [[...]] }
}

craft.shader.add(myShader)
local material = craft.material("My Custom Shader")

Shader Table Structure

name (string, required)

The unique identifier for this shader. This name is used when calling craft.material(name).

myShader = {
    name = "Terrain Shader",
    -- ...
}

options (table, optional)

A table of feature flags that enable/disable rendering capabilities. Each option is a key-value pair where the value is a table containing true or conditional lists.

Options control which built-in rendering features are compiled into the shader. Enabling unnecessary options may impact performance.

Common options:

Option Effect
USE_COLOR Enable vertex color support in Input.color
USE_LIGHTING Enable surface normal and lighting calculations
STANDARD Enable physically-based rendering (PBR) with standard material properties
PHYSICAL Enhanced PBR with metalness and roughness support
ENVMAP_TYPE_CUBE Support cube texture environment maps
ENVMAP_MODE_REFLECTION Use environment map for reflections
USE_ENVMAP Enable environment mapping (conditional on {"envMap"} property)
options = {
    USE_COLOR = { true },                    -- Always enabled
    USE_LIGHTING = { true },
    STANDARD = { true },
    USE_ENVMAP = { false, { "envMap" } }    -- Enabled only if envMap property is set
}

properties (table, optional)

A table defining uniform variables that can be controlled from Lua code. Each property is a key-value pair: the key is the property name, the value is a descriptor table.

Property descriptors have two elements:

  1. Type (string) — "float", "vec2", "vec3", "vec4", "int", "texture2D", "cubeTexture", "color", or "bool"
  2. Default value — the initial value (or nil for uninitialized textures)
properties = {
    map = { "texture2D", nil },              -- Texture, no default
    tiling = { "float", 1.0 },               -- Float with default 1.0
    time = { "float", 0.0 },
    roughness = { "float", 0.7 },
    heightMap = { "cubeTexture", nil },      -- Cube texture
    diffuse = { "color", color(255, 255, 255) },
    enabled = { "bool", false }
}

Properties can be set on a material instance:

local mat = craft.material("My Shader")
mat.map = readImage(asset.MyTexture)
mat.tiling = 2.0
mat.roughness = 0.5

pass (table, required)

Defines the rendering pipeline and shader code. Contains render state settings and the vertex/surface shader stages.

Pass: Render State

Parameter Type Default Purpose
base string "Surface" Base rendering mode; typically "Surface" for standard rendering
blendMode string "disabled" How to blend with framebuffer: "disabled", "additive", "multiply"
depthWrite bool true Whether to write depth values for depth testing
depthFunc string "lessEqual" Depth test function: "less", "lessEqual", "equal", "greater", etc.
renderQueue string "solid" Render order: "solid" (opaque), "transparent", or "overlay"
colorMask table {"rgba"} Which color channels to write: {"r"}, {"rg"}, {"rgba"}, etc.
cullFace string "back" Which faces to cull: "back", "front", "disabled"
receiveShadows bool false Whether the material receives shadow maps
castShadows bool false Whether objects with this material cast shadows
pass = {
    base = "Surface",
    blendMode = "disabled",
    depthWrite = true,
    depthFunc = "lessEqual",
    renderQueue = "solid",
    colorMask = { "rgba" },
    cullFace = "back",
    receiveShadows = true,
    castShadows = true,
    -- ... vertex and surface code follow
}

Pass: Vertex Stage

vertex = [[
    uniform mat4 modelViewProjection;
    uniform mat4 modelMatrix;
    
    void vertex(inout Vertex v, out Input o)
    {
        // Modify vertex position, normal, color, etc.
        // Prepare data for fragment stage
    }
]]

The vertex function receives:

  • v : Vertex (inout) — The current vertex with position, normal, color, uv
  • o : Input (out) — Data to pass to the fragment stage

Vertex struct fields:

  • position : vec3 — Object-space vertex position
  • normal : vec3 — Object-space normal
  • color : vec4 — Vertex color (R, G, B, A)
  • tangent : vec3 — Tangent for normal mapping (if available)
  • uv : vec2 — Texture coordinates

Available uniforms in vertex stage:

  • modelViewProjection : mat4 — Combined transform to screen space
  • modelMatrix : mat4 — Object-to-world transform
  • normalMatrix : mat3 — Normal transformation (inverse transpose of modelMatrix)

Pass: Fragment/Surface Stage

surface = [[
    void surface(in Input IN, inout SurfaceOutput o)
    {
        // Sample textures and compute material properties
        // Set diffuse, normal, roughness, metalness, emission, etc.
    }
]]

The surface function receives:

  • IN : Input (in) — Interpolated data from vertex stage
  • o : SurfaceOutput (inout) — Material properties for lighting

Input struct fields (populated by vertex stage or built-in):

  • uv : vec2 — Texture coordinates
  • color : vec4 — Interpolated vertex color
  • normal : vec3 — Interpolated surface normal (if USE_LIGHTING enabled)
  • position : vec3 — World-space position (if USE_LIGHTING enabled)
  • viewDir : vec3 — Normalized direction toward camera (if USE_LIGHTING enabled)

SurfaceOutput struct fields:

  • diffuse : vec3 — Surface color (RGB)
  • emission : vec3 — Emitted color (for glow effects)
  • emissive : float — Emissive intensity multiplier
  • roughness : float — Surface roughness (0.0 = mirror, 1.0 = rough)
  • metalness : float — Metallic appearance (0.0 = non-metal, 1.0 = metal)
  • opacity : float — Alpha transparency (0.0 = transparent, 1.0 = opaque)
  • occlusion : float — Ambient occlusion factor
  • normal : vec3 — Perturbed surface normal (for normal maps)

Common Shader Patterns

Unlit Shader (Emissive)

Renders without lighting, useful for UI, skyboxes, or glowing surfaces:

unlit = {
    name = "Unlit",
    options = { USE_COLOR = { true } },
    properties = { map = { "texture2D", nil } },
    pass = {
        base = "Surface",
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                vec4 col = texture(map, IN.uv);
                o.diffuse = col.rgb;
                o.emissive = 1.0;
                o.emission = col.rgb;
            }
        ]]
    }
}

Lit Shader with PBR

Uses physically-based rendering with roughness and metalness:

pbr = {
    name = "PBR",
    options = {
        USE_COLOR = { true },
        USE_LIGHTING = { true },
        STANDARD = { true },
        PHYSICAL = { true }
    },
    properties = {
        map = { "texture2D", nil },
        roughness = { "float", 0.7 },
        metalness = { "float", 0.0 }
    },
    pass = {
        base = "Surface",
        receiveShadows = true,
        castShadows = true,
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            uniform float roughness;
            uniform float metalness;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = texture(map, IN.uv).rgb;
                o.roughness = roughness;
                o.metalness = metalness;
            }
        ]]
    }
}

Displacement Mapping (Height-based Vertex Deformation)

Displaces vertices along normals using a height map:

displaced = {
    name = "Displaced",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        heightMap = { "texture2D", nil },
        scale = { "float", 1.0 }
    },
    pass = {
        base = "Surface",
        vertex = [[
            uniform sampler2D heightMap;
            uniform float scale;
            void vertex(inout Vertex v, out Input o)
            {
                float h = texture(heightMap, v.uv).r;
                v.position += v.normal * (h * scale);
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.8);
            }
        ]]
    }
}

Animated Vertex Deformation

Uses elapsed time to animate vertex positions:

wave = {
    name = "Wave",
    properties = { time = { "float", 0.0 } },
    pass = {
        base = "Surface",
        vertex = [[
            uniform float time;
            void vertex(inout Vertex v, out Input o)
            {
                float wave = sin(v.position.x + time) * 0.1;
                v.position.y += wave;
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.5, 0.7, 1.0);
            }
        ]]
    }
}

Cubemap Displacement (Procedural Planet)

Displaces a sphere along view-dependent directions using a cube texture:

planetDisplaced = {
    name = "Planet Displaced",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        heightMap = { "cubeTexture", nil },
        scale = { "float", 1.0 }
    },
    pass = {
        base = "Surface",
        vertex = [[
            uniform samplerCube heightMap;
            uniform float scale;
            void vertex(inout Vertex v, out Input o)
            {
                vec3 dir = normalize(v.normal);
                float h = texture(heightMap, dir).r;
                v.position += v.normal * (h * scale);
            }
        ]],
        surface = [[
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = vec3(0.2, 0.8, 0.3);
            }
        ]]
    }
}

Updating Shader Properties at Runtime

Shader properties can be updated each frame from Lua:

function setup()
    craft.shader.add(myTimeShader)
    mat = craft.material("My Time Shader")
    local e = scene:entity()
    e.model = craft.model.cube(vec3(1,1,1))
    e.material = mat
end

function draw()
    scene:update(DeltaTime)
    mat.time = ElapsedTime  -- Update time uniform each frame
    scene:draw()
end

Texture Coordinates and Sampling

Texture coordinates are stored in Input.uv and typically range from (0, 0) at one corner to (1, 1) at the opposite corner. Tiling can be applied by multiplying the UV:

surface = [[
    uniform sampler2D map;
    uniform float tiling;
    void surface(in Input IN, inout SurfaceOutput o)
    {
        vec4 col = texture(map, IN.uv * tiling);
        o.diffuse = col.rgb;
    }
]]

For cube textures, sample using a normalized 3D direction vector:

surface = [[
    uniform samplerCube envMap;
    void surface(in Input IN, inout SurfaceOutput o)
    {
        vec3 dir = normalize(IN.viewDir);
        vec4 col = texture(envMap, dir);
        o.diffuse = col.rgb;
    }
]]

Normal Mapping

Perturb surface normals using a normal map texture to add surface detail without extra geometry:

normalMapped = {
    name = "Normal Mapped",
    options = { USE_LIGHTING = { true }, STANDARD = { true } },
    properties = {
        map = { "texture2D", nil },
        normalMap = { "texture2D", nil }
    },
    pass = {
        base = "Surface",
        vertex = [[ void vertex(inout Vertex v, out Input o) {} ]],
        surface = [[
            uniform sampler2D map;
            uniform sampler2D normalMap;
            void surface(in Input IN, inout SurfaceOutput o)
            {
                o.diffuse = texture(map, IN.uv).rgb;
                vec3 n = texture(normalMap, IN.uv).rgb;
                o.normal = normalize(n * 2.0 - 1.0);
            }
        ]]
    }
}

Blending and Transparency

Control how a material blends with the framebuffer:

pass = {
    blendMode = "additive",    -- Add color (for light, glow)
    -- OR
    blendMode = "multiply",    -- Multiply color (for shadows)
    -- OR
    blendMode = "disabled"     -- Replace (default, for opaque)
}

For transparency, set renderQueue and control opacity in the surface function:

pass = {
    renderQueue = "transparent",
    surface = [[
        void surface(in Input IN, inout SurfaceOutput o)
        {
            o.opacity = 0.5;  -- 50% transparent
        }
    ]]
}

Debugging and Edge Cases

Shader Compilation Errors: If a shader fails to compile, Craft will fall back to a default material. Check the device log for GLSL error messages.

Coordinate Systems: Object-space coordinates (vertex positions, normals) are local to the model. Use modelMatrix to transform to world space. Screen coordinates in modelViewProjection transform directly to clip space.

Precision: Use highp (high precision) for calculations that require accuracy; use lowp for performance-sensitive mobile code.

surface = [[
    precision highp float;
    // ... shader code
]]

See Also

  • craft.material(name) — Create a material instance from a registered shader
  • craft.entity — Apply materials to renderable entities
  • craft.renderer — The component that applies materials
  • craft.shader.add(shader) — Register a shader definition

@sim ….. so how did it do? :grimacing:

If it got it right, would you consider adding this to the official Codea Reference?