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:
- Metadata — name and unique identifier
- Options — feature flags that enable rendering capabilities (lighting, shadows, etc.)
- Properties — uniform variables exposed to Lua that control shader behavior
- 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:
- Type (string) —
"float","vec2","vec3","vec4","int","texture2D","cubeTexture","color", or"bool" - Default value — the initial value (or
nilfor 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, uvo : Input(out) — Data to pass to the fragment stage
Vertex struct fields:
position : vec3— Object-space vertex positionnormal : vec3— Object-space normalcolor : 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 spacemodelMatrix : mat4— Object-to-world transformnormalMatrix : 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 stageo : SurfaceOutput(inout) — Material properties for lighting
Input struct fields (populated by vertex stage or built-in):
uv : vec2— Texture coordinatescolor : vec4— Interpolated vertex colornormal : vec3— Interpolated surface normal (ifUSE_LIGHTINGenabled)position : vec3— World-space position (ifUSE_LIGHTINGenabled)viewDir : vec3— Normalized direction toward camera (ifUSE_LIGHTINGenabled)
SurfaceOutput struct fields:
diffuse : vec3— Surface color (RGB)emission : vec3— Emitted color (for glow effects)emissive : float— Emissive intensity multiplierroughness : 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 factornormal : 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 shadercraft.entity— Apply materials to renderable entitiescraft.renderer— The component that applies materialscraft.shader.add(shader)— Register a shader definition
@sim ….. so how did it do? ![]()
If it got it right, would you consider adding this to the official Codea Reference?