---
title: "3D renderer"
---

> Documentation Index
> Fetch the complete documentation index at: https://aesthetic-docs.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 3D renderer

Argument of `render_3d`. Positions are [vec3](/vec3) values in **world** coordinates —
camera offset is handled for you.

| Function | What it does |
|----------|--------------|
| `camera()` | Camera position as [vec3](/vec3) |
| `camera_rotation()` | Camera `yaw, pitch` in degrees |
| `camera_forward()` | View direction as a unit [vec3](/vec3) |
| `line(from, to, color[, width[, through_walls]])` | Line between two world points |
| `box(min, max, color[, width[, through_walls]])` | Box outline |
| `filled_box(min, max, color[, through_walls])` | Solid translucent box (all six faces) |
| `quad(a, b, c, d, color[, through_walls])` | Filled quad |
| `triangle(a, b, c, color[, through_walls])` | Filled triangle |
| `point(pos, color[, size[, through_walls]])` | Point sprite (`size` in pixels, default 4) |
| `text(str, pos[, color[, scale[, through_walls[, background]]]])` | Camera-facing billboard text; returns width |
| `tracer(pos, color[, width[, through_walls]])` | Line from crosshair (screen center) to a world point |
| `begin(mode[, width[, through_walls]])` / `vertex(pos[, color])` / `finish()` | Arbitrary mesh |
| `begin_layer(layer)` / `uv(u, v)` | Mesh through a custom [gfx pipeline](/gfx) |
| `push()` / `pop()` | Matrix transform scope — coordinates become local |
| `origin(pos)` | Moves local origin to a world position |
| `translate(offset)` / `rotate(deg[, axis])` / `scale(sx[, sy, sz])` | Local transforms (`rotate` axis defaults to Y) |
| `rotate_camera()` | Turns the local frame to face the camera (billboard) |

`through_walls = true` makes the shape visible through blocks.

```lua
module:event("render_3d", function(render)
for _, p in ipairs(world:players()) do
    if not p:is_self() then
        local pos = p:position()
        local half = p:width() / 2
        render.box(pos + vec3(-half, 0, -half),
                   pos + vec3(half, p:height(), half),
                   0xFF4FF2A6, 2)
        render.text(p:name(), pos + vec3(0, p:height() + 0.4, 0), 0xFFFFFFFF, 1, true)
    end
end
end)
```

## Meshes

Modes: `"lines"`, `"line_strip"`, `"quads"`, `"triangles"`, `"triangle_strip"`,
`"triangle_fan"`, `"points"`. (`"quads"` and `"triangle_fan"` are 3D-only — not on the
2D canvas.) Vertex color sticks until changed; `width` is line width / point size:

```lua
render.begin("quads", nil, true)   -- mode, default width, through_walls
render.vertex(pos, 0x604FF2A6)
render.vertex(pos + vec3(1, 0, 0))
render.vertex(pos + vec3(1, 0, 1))
render.vertex(pos + vec3(0, 0, 1))
render.finish()
```

For a custom GLSL shader instead of built-in shading, use `begin_layer(layer)` — see
[Custom pipelines](/gfx).

## Transforms

Inside `push`/`pop`, all draw coordinates are **local** to the transform.
`origin` places the local origin at a world position (usual first call after `push`).
`rotate_camera` faces the camera with the exact orientation quaternion — no trigonometry:

```lua
module:event("render_3d", function(render)
for _, p in ipairs(world:players()) do
    if not p:is_self() then
        render.push()
        render.origin(p:position() + vec3(0, p:height() / 2, 0))
        render.rotate_camera()
        render.begin("quads")
        render.vertex(vec3(-0.6, 0.6, 0), 0x904FF2A6)
        render.vertex(vec3(0.6, 0.6, 0))
        render.vertex(vec3(0.6, -0.6, 0))
        render.vertex(vec3(-0.6, -0.6, 0))
        render.finish()
        render.pop()
    end
end
end)
```

`rotate(degrees[, axis])` spins around a [vec3](/vec3) axis (Y by default).
`scale` and `translate` work in the local frame. Transforms nest; anything left
unbalanced is popped automatically after the callback.

Source: https://aesthetic-docs.pages.dev/world-render/index.md
