card-raytracer/README.md
Ole-Morten Duesund fc752cfa0c README: precise lineage — Whitted with stochastic sampling, not path tracing
'Path tracing' in graphics literature means Monte Carlo solution of the
rendering equation, with random hemisphere sampling at every diffuse
surface and indirect light transport. card.cc has none of that — the
floor branch in S() returns immediately with a flat ambient term and
casts no new rays. What it actually does is Whitted-style recursive
specular reflection (mirrors only), with distribution-style stochastic
sampling layered on top for soft shadows, depth of field, and AA.

Group the stochastic features (soft shadows, DOF, AA) under that
qualifier; keep mirror reflections, checker floor, and sky gradient
as separate non-stochastic features.
2026-05-28 14:06:55 +02:00

3.7 KiB

card-raytracer

A study of Andrew Kensler's "business card raytracer" — a complete Whitted-style raytracer with stochastic sampling for soft shadows, depth of field, and anti-aliasing, plus mirror reflections, a checker floor, and a sky gradient, all crammed into a single C++ source file small enough to print on the back of a business card.

card.cc is exactly 1337 bytes. Yes, leet. Verify it yourself:

$ wc -c card.cc
1337 card.cc

That is suspiciously round — hand-golfed C++ rarely lands on a famous leet number by accident. The compression tricks all show their work: typedefs (i for int, f for float), operator overloads on struct v that turn + / % / ^ / ! into vector math, and not a wasted space anywhere.

The original code is preserved verbatim as card.cc. A de-obfuscated, heavily-annotated rewrite lives in card_explained.cc. Both produce the same image:

Rendered output: the letters "aek" as reflective spheres on a red and white checker floor under a blue-purple sky, with soft shadows and depth-of-field blur

The spheres spell a · e · k, Andrew Kensler's initials, encoded as a 9-column bitmap in the G[] array of the original source.

Quick start

Requires a C++14 compiler, CMake ≥ 3.16, and (optionally) netpbm for PPM → PNG conversion.

cmake -S . -B build
cmake --build build

# Render with the original (PPM, 786 KiB, ~6 s on a modern laptop)
./build/card > aek.ppm

# Or via the convenience target:
cmake --build build --target image            # → build/aek.ppm
cmake --build build --target image_explained  # → build/aek_explained.ppm

# View it (PPM is supported by most image viewers; or convert):
pnmtopng aek.ppm > aek.png

What's interesting about it

The whole renderer is built on operator overloading: a single struct v provides + (add), * (scale), % (dot product), ^ (cross product), and unary ! (normalise). Once those are in place, the entire rendering pipeline — sphere intersections, reflection vectors, Phong shading, checker-pattern tile lookup, camera basis construction — collapses into just a handful of expressions.

Other tricks worth admiring:

  • Soft shadows for free: the light position is jittered each call, so averaging 64 samples per pixel produces a real penumbra without any area-light formalism.
  • Depth of field for free: the camera origin is also jittered across a 99-unit "lens" each sample, so points off the focal plane go blurry.
  • Anti-aliasing for free: sub-pixel jitter on the ray direction.

The same 64-sample loop pays for all three at once.

A line-by-line walkthrough of the algorithm is the long-form comments inside card_explained.cc.

Layout

.
├── card.cc            # original, untouched — do not edit
├── card_explained.cc  # de-obfuscated, documented rewrite
├── CMakeLists.txt
├── docs/
│   └── aek.png        # published render
├── README.md
└── CLAUDE.md          # working notes for AI assistance

Credits

Licence

The original card.cc is reproduced as-is from publicly published material for educational purposes; credit and copyright belong to Andrew Kensler. The supporting files in this repository (CMake, README, de-obfuscated rewrite) are placed in the public domain by the repository author.