3D
The panel has a small 3D pipeline built in. Your script describes the scene (a camera, lights, meshes and what they are made of) and the panel does the per-pixel work. At 64 × 64 a low-polygon scene runs comfortably.
A spinning cube
Section titled “A spinning cube”local cube, paintlocal angle = 0
function _init() cube = gfx.cube() paint = gfx.material{ color = {255, 140, 60} } gfx.projection(mat4.perspective(50, gfx.width() / gfx.height(), 0.1, 20)) gfx.view(mat4.look_at(0, 1.5, 3.5, 0, 0, 0, 0, 1, 0)) gfx.ambient(40, 40, 60) gfx.light(1, { dir = {0.4, -1, -0.3}, color = {255, 240, 220} })end
function _update(dt) angle = angle + dtend
function _draw() gfx.cls(0, 0, 10) gfx.draw(cube, paint, mat4.rotate_y(angle) * mat4.rotate_x(angle * 0.7))endFour ideas are at work:
- A mesh is the shape:
gfx.cube(). - A material is how its surface looks:
gfx.material{ ... }. - The camera is two matrices: a projection (the lens) and a view (where it stands and what it looks at).
gfx.draw(mesh, material, model)puts the mesh into the scene. The model matrix places, turns and scales it.
Build meshes, textures and materials once, in _init. Only the matrices change per frame.
Conventions
Section titled “Conventions”- Y is up. The camera looks down the negative Z axis. The world is right-handed, as in OpenGL.
- A triangle faces you when its corners go counter-clockwise.
- Angles are radians, except the field of view of
mat4.perspective, which is degrees. - Colours are 0 to 255, as in the 2D calls.
Matrices
Section titled “Matrices”| Call | Result |
|---|---|
mat4.identity() |
No change |
mat4.translate(x, y, z) |
Move |
mat4.scale(x, y, z) |
Resize |
mat4.rotate_x(a), mat4.rotate_y(a), mat4.rotate_z(a) |
Turn around an axis |
mat4.perspective(fov, aspect, near, far) |
A lens with a field of view in degrees |
mat4.ortho(left, right, bottom, top, near, far) |
A lens without perspective |
mat4.look_at(ex, ey, ez, tx, ty, tz, ux, uy, uz) |
A camera at e, looking at t, with u as up |
a * b |
The matrix that applies b first, then a |
m:transform(x, y, z) |
Where the matrix sends a point; returns x, y, z, w |
Read a chain of matrices from right to left: mat4.translate(2, 0, 0) * mat4.rotate_y(a) turns the object in place, then moves it.
Meshes
Section titled “Meshes”Three shapes are built in, each centred on the origin and about one unit across:
| Call | Shape |
|---|---|
gfx.cube() |
A cube |
gfx.sphere(segments) |
A sphere; segments is 3 to 32, 12 if left out |
gfx.plane(divisions) |
A flat square facing up; divisions is 1 to 31 |
Your own mesh is a table of flat number lists:
local triangle, paint
function _init() triangle = gfx.mesh{ pos = { -1, -1, 0, 1, -1, 0, 0, 1, 0 }, -- three numbers per corner color = { 255, 0, 0, 0, 255, 0, 0, 0, 255 }, -- optional index = { 1, 2, 3 }, -- corners per triangle, counted from 1 } paint = gfx.material{ shading = "unlit", cull = "none" } gfx.projection(mat4.perspective(60, 1, 0.1, 10)) gfx.view(mat4.look_at(0, 0, 3, 0, 0, 0, 0, 1, 0))end
function _draw() gfx.cls() gfx.draw(triangle, paint)end| Field | Contents |
|---|---|
pos |
Required. Three numbers per vertex |
normal |
Three per vertex; needed for smooth lighting |
uv |
Two per vertex; texture coordinates |
color |
Three per vertex, 0 to 255 |
index |
Which vertices make each triangle, counted from 1. Without it, vertices are used in order |
mode |
"triangles" (the default), "lines" or "points" |
A mesh holds up to 1,024 vertices and cannot be changed after it is made.
Materials
Section titled “Materials”paint = gfx.material{ color = {255, 200, 160}, shading = "smooth" }| Field | Values | Default |
|---|---|---|
color |
{r, g, b} |
White |
shading |
"smooth" (lit per vertex), "flat" (lit per triangle), "unlit" |
"smooth" |
texture |
A texture; its colours are multiplied by color |
None |
cull |
"back", "front", "none": which faces to skip |
"back" |
depth |
"on", "read" (test but do not write), "off" |
"on" |
blend |
"none", "alpha", "add" |
"none" |
alpha |
0 to 255, used by blend = "alpha" |
255 |
material:set{ ... } changes fields later, with the same names.
Lights and fog
Section titled “Lights and fog”| Call | Effect |
|---|---|
gfx.ambient(r, g, b) |
Light that reaches every surface. White until you set it, so a scene with no lights is still visible |
gfx.light(i, { dir = {x, y, z}, color = {r, g, b} }) |
Light i (1 to 4) shining along a direction, like the sun |
gfx.light(i, { pos = {x, y, z}, range = r, color = {r, g, b} }) |
A lamp at a point, fading out at range |
gfx.light(i, nil) |
Turns light i off |
gfx.fog(near, far, r, g, b) |
Fades surfaces to a colour between two distances; gfx.fog() turns it off |
Lighting is diffuse only: surfaces facing a light are brighter. There are no highlights or shadows.
Textures
Section titled “Textures”A texture is a small image written as text: four hex digits per pixel in the panel’s 16-bit colour format, rows from the top. Sides are powers of two up to 64.
local checker, floor_paint, ground
function _init() checker = gfx.texture(2, 2, "FFFF" .. "2104" .. "2104" .. "FFFF") floor_paint = gfx.material{ texture = checker, shading = "unlit" } ground = gfx.plane(4) gfx.projection(mat4.perspective(60, 1, 0.1, 20)) gfx.view(mat4.look_at(0, 1, 2, 0, 0, 0, 0, 1, 0))end
function _draw() gfx.cls() gfx.draw(ground, floor_paint, mat4.scale(4, 1, 4))endgfx.rgb(r, g, b) gives the number for a colour; string.format("%04X", gfx.rgb(255, 0, 0)) gives its four digits. A fourth argument, {r, g, b}, makes that colour transparent.
Mixing 2D and 3D
Section titled “Mixing 2D and 3D”Everything draws onto the same canvas in the order you call it. Draw the scene, then a score or a frame around it with gfx.rectfill and gfx.line.
Two calls help when combining them:
gfx.project(x, y, z)returns the canvasx, y(and depth) of a point in the world, for placing a label over an object.gfx.clear_depth()forgets what has been drawn so far in 3D, so a second layer (a cockpit, a held item) always draws in front.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Vertices per mesh | 1,024 |
| Triangles per frame | 2,048 |
| Pixels filled per frame | 16 times the canvas |
| Lights | 4 |
| Texture size | 64 × 64 |
Past the per-frame limits, gfx.draw stops the script with an error.