← Back

3D ASCII Art

Real-time 3D rendering in text characters — demoscene culture meets terminal graphics

The Classic: Spinning ASCII Donut

Based on Andy Sloane's famous "donut.c" — a parametric torus rendered in real-time with 3D rotation matrices, z-buffering, and luminance-based character mapping.

Parametric torus → 3D rotation → perspective projection → z-buffer → luminance → character

Wireframe ASCII Cube

A 3D cube rendered with wireframe lines. Vertices are rotated in 3D space, projected to 2D, and edges are drawn using Bresenham's line algorithm on a character grid.

ASCII Sphere with Dynamic Lighting

A sphere rendered with surface normal calculations and luminance-based character mapping. The light source rotates around the sphere, creating dynamic shading in real-time.

Character Density Map

dark
.
,
:
;
=
+
*
#
%
@ bright

Surface normal · light = luminance → character lookup

ASCII Terrain Landscape

A procedural 3D terrain generated with sine wave height maps, viewed from above. The terrain scrolls continuously, with height values mapped to character density.

How 3D-to-ASCII Rendering Works

1. 3D Mathematics

Define 3D geometry using parametric equations (torus, sphere) or vertex coordinates (cube).

// Torus surface point
x = (R2 + R1·cos(θ)) · cos(φ)
y = (R2 + R1·cos(θ)) · sin(φ)
z = R1 · sin(θ)

2. Rotation

Apply rotation matrices to transform points in 3D space around X, Y, and Z axes.

// Rotate around Y-axis
x' = x·cos(θ) + z·sin(θ)
z' = -x·sin(θ) + z·cos(θ)

3. Projection

Project 3D coordinates to 2D screen space using perspective division (divide by Z).

// Perspective projection
screenX = (K · x) / z
screenY = (K · y) / z

4. Z-Buffering

Track depth for each pixel to determine which surfaces are visible (closer surfaces obscure farther ones).

if (1/z > zbuffer[x,y]) {
  zbuffer[x,y] = 1/z
  draw_pixel(x, y)
}

5. Surface Normals

Calculate the normal vector (perpendicular) for each surface point to determine how it faces light.

// Normal for parametric surface
N = ∂P/∂u × ∂P/∂v
normalize(N)

6. Luminance

Compute brightness using the dot product between surface normal and light direction.

// Lambertian shading
L = N · lightDir
L = max(0, L)

7. Character Mapping

Map luminance values to characters based on visual density. Bright = dense (@, #), dark = sparse (., ␣).

chars = " .,:;=+*#%@"
idx = floor(L · chars.length)
char = chars[idx]

8. Output

Render the 2D character buffer to a <pre> element with monospace font and proper line-height.

// Character ramp (light→dark)
" .,-~:;=!*#$@"
or
" .,:;=+*#%@"

The Demoscene Heritage

ASCII art rendering has deep roots in demoscene culture — the underground computer art movement focused on creating audiovisual demonstrations under extreme constraints. 3D ASCII art showcases the beauty of limitations: expressing complex 3D graphics within a fixed-width text grid, often in tiny file sizes. The famous "donut.c" by Andy Sloane is just 1KB of code yet renders a fully shaded, rotating 3D torus in real-time.

Classic terminal palettes: Green on black (#00ff41 / #0a0a0a), amber on black (#ffb000 / #0a0a0a), white on black (#e0e0e0 / #0a0a0a).