Prism
Prism logo

Software rasterizer · Rust

Real triangles.
Drawn on the CPU.

Prism is a 3D rendering pipeline written from scratch in Rust. No GPU, no graphics crates. Just a framebuffer, a z-buffer, and barycentric math turning vertices into pixels.

View on GitHub How it works

cargo run -- render -o out.ppm

Live in the browser.

drag to orbit · rasterized on the CPU, per pixel

Speed {{ speedLabel }}
How to use this playground

Use the Simulation dropdown to switch scenes, from the CPU-rasterized rotating cube to boids, Game of Life, and more. On the cube, drag to orbit the camera, toggle Wireframe to see the triangles, use Play/Pause to freeze it, and the Speed slider to change the rotation rate. Everything is drawn per pixel in your browser, the same pipeline as the Rust renderer.

Barycentric fill,
pixel by pixel.

Each triangle walks the bounding box of its three screen-space vertices. For every pixel center, three edge functions decide whether the point lies inside; their signed ratios are the barycentric weights.

Those same weights interpolate depth across the face, so every covered pixel arrives at the depth test with a z of its own.

let l0 = w0 / area;
let l1 = w1 / area;
let l2 = w2 / area;
let z = l0 * v0.z
      + l1 * v1.z
      + l2 * v2.z;

fb.set_with_depth(x, y, z, color);

The z-buffer
settles overlaps.

One depth value per pixel, holding the nearest surface drawn so far. A fragment writes color only if it is closer, so triangles behind others are hidden regardless of the order they were submitted in.

Depth test per pixel
Shading flat, directional
Transforms hand-written 4×4
Output PPM (P6)
Dependencies clap

The same pipeline,
from the command line.

One subcommand renders the built-in demo scene, a rotated, lit cube, and writes it as a binary PPM. No image codec, no runtime dependencies beyond clap. The output is just a header and raw bytes, so its size is checkable by hand.

$ cargo run -- render -o out.ppm
wrote out.ppm (640x480)

$ cargo run -- render -o wide.ppm --width 800 --height 600
wrote wide.ppm (800x600)

$ ls -l out.ppm
-rw-r--r--  921615  out.ppm   # 640 * 480 * 3 + 15-byte header

$ head -c 15 out.ppm | xxd
50 36 0a 36 34 30 20 34 38 30 0a 32 35 35 0a   P6.640 480.255.

Every stage, by hand

The six stages a vertex passes through.

Vertex transform src/vec.rs

Model to world to camera to clip space through a chain of hand-written 4x4 matrices: rotation, look_at, and a right-handed perspective.

Projection src/scene.rs

Divide by w to land in normalized device coordinates in [-1, 1], then map linearly to pixel coordinates and the z-buffer depth range.

Barycentric fill src/raster.rs

Walk each triangle's bounding box. Three edge functions per pixel decide inside from outside, and their signed ratios are the barycentric weights.

Z-buffer src/framebuffer.rs

One depth per pixel. set_with_depth commits a color only when the incoming depth is nearer, so overlaps resolve regardless of draw order.

Flat shading src/scene.rs

A dot product between the world-space face normal and a fixed light direction sets each triangle's brightness. One tone per face.

PPM output P6 binary

A text header then width times height times three raw RGB bytes, row-major from the top. No compression, no metadata, checkable by byte length alone.

Where Prism sits.

Prism is a teaching-grade software rasterizer, not a renderer to ship a game on. It trades speed for a pipeline you can read.

tinyrenderer, scratchapixel

The classic path for learning software rendering, usually in C++. Prism is in that spirit: a full pipeline read end to end, written from scratch in Rust with the transforms and z-buffer by hand.

A GPU pipeline (wgpu, OpenGL, Vulkan)

Runs these same steps in hardware: fast, parallel, and opaque. Prism does vertex transform, rasterization, depth test, and shading in plain CPU code so each step is visible instead of hidden behind a driver.

Prism

The readable middle. Real perspective transforms, per-pixel barycentric fill, a per-pixel z-buffer, flat directional shading, and a PPM writer, with clap as the only dependency. The same engine runs live in the browser demo above.