DEV Community

Michael Rivera
Michael Rivera

Posted on AI-assisted

CPU path tracer in Pure Python — No GPU, No Dependencies, Physically Based

I Wrote a Path Tracer in Pure Python — No GPU, No Dependencies, Physically Based

PureTrace is a CPU path tracer written entirely in Python's standard library. No NumPy, no Pillow, no native extensions, no GPU. Everything — geometry, BVH construction, PBR materials, PNG encoding, OpenEXR encoding, multiprocessing, and checkpointing — is standard library.

This post is about the engineering decisions behind it and what it was like to build a production-quality rendering pipeline under that constraint.


Why the Constraint?

"No dependencies" sounds like a gimmick. It's not.

The real goal was to understand what rendering pipelines actually do — not what they look like through a library's API. When you can't import NumPy, you write your own vector math. When you can't use Pillow, you write your own PNG encoder. You end up understanding every piece you would otherwise have taken for granted.

The secondary benefit: zero install friction. Clone and render. No pip install chain, no native extension compilation, no version conflicts.


The Rendering Pipeline

Scene primitives → SAH BVH → Camera samples → Path tracing integrator → MIS → Tile workers → Merge → PNG / OpenEXR
Enter fullscreen mode Exit fullscreen mode

Monte Carlo path tracing with multiple-importance sampling (MIS). Each pixel fires rays into the scene, bouncing off surfaces until they hit a light or exceed max depth. MIS combines direct light sampling and BSDF sampling using the power heuristic — this is what makes glass and metal converge without the firefly artifacts you get from naive path tracing.

Physically based materials (GGX). Diffuse, metallic, and dielectric glass — all using the GGX microfacet distribution. Roughness controls the shape of specular highlights and reflections in a physically correct way.

Volumetric rendering. Homogeneous participating medium with free-flight sampling. Fog, anisotropic scattering, visible light transport.


The BVH

Ray-scene intersection is the performance bottleneck in any path tracer. Without a spatial acceleration structure, every ray tests every primitive — O(n) per ray, which makes complex scenes completely intractable.

PureTrace uses a binned surface-area-heuristic BVH (bounding volume hierarchy). The SAH is a cost model that estimates how expensive a split will be based on the surface areas of the resulting child nodes. Binning approximates the optimal split point efficiently rather than testing every possible position.

The result: ray traversal is O(log n) in the average case. A scene with thousands of triangles becomes tractable.

Writing the BVH in pure Python — no SIMD, no Cython — is where the constraint hurts most. It's real. PureTrace is not fast. Use all your cores, use sensible image sizes, and use progressive sampling. But it's correct and it's readable.


Multiprocessing and Checkpointing

The render is split into tiles. Each tile runs in a worker process (Python's multiprocessing). The main process merges tiles as they complete and writes progressive output so you can see the image forming.

Every render writes an atomic .ptrchk checkpoint file. Interrupt mid-render with Ctrl-C and the completed tiles are saved. Resume with the same settings:

# Start
puretrace render glass-chess -W 900 -H 675 -s 1200 -j 8 --samples-per-pass 4 -o chess.png

# Interrupt, resume later
puretrace render glass-chess -W 900 -H 675 -s 1200 -j 8 --samples-per-pass 4 -o chess.png --resume
Enter fullscreen mode Exit fullscreen mode

Glass caustics at 1200 samples per pixel takes a long time on a CPU. Checkpointing makes that practical.


Output Formats

  • PNG — 8-bit sRGB with ACES, Reinhard, or linear tone mapping. The encoder is hand-written: deflate compression via zlib (stdlib), PNG chunk structure by hand.
  • OpenEXR — uncompressed scanline RGB in linear scene space, half-float. Also hand-written. No openexr package.

Writing a PNG encoder is one of those things you do once and never forget. The format is simple enough to implement from the spec in an afternoon.


Built-in Scenes

puretrace render cornell -W 512 -H 512 -s 256 -j 8 -o cornell.png
puretrace render spheres -W 640 -H 400 -s 128 -o spheres.png
puretrace render glass-chess -W 900 -H 675 -s 1200 -j 8 -o chess.png
puretrace render fog -W 800 -H 600 -s 512 -j 8 -o fog.png
Enter fullscreen mode Exit fullscreen mode
Scene What It Tests
cornell Diffuse walls, rough metal, glass, soft ceiling light
spheres Chrome, copper, glass, depth of field, motion blur
glass-chess Lathed glass pieces on reflective checkerboard
caustics Glass and polished metal under compact area light
fog Anisotropic participating media, visible light transport

A first render worth looking at is 256 samples per pixel. Glass caustics converge slowly — 1000+ is normal.


JSON Scene Description

{
  "camera": {
    "look_from": [4, 2, 6],
    "look_at": [0, 1, 0],
    "vertical_fov": 40,
    "aperture": 0.05
  },
  "materials": {
    "glass": {
      "base_color": [0.95, 0.99, 1.0],
      "roughness": 0.01,
      "transmission": 1.0,
      "ior": 1.52
    }
  },
  "objects": [
    { "type": "obj", "file": "room.obj", "scale": 1.0 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

OBJ/MTL import, HDR environment lighting, all material parameters exposed. Enough to build real scenes.


Running It

git clone https://github.com/BleedingCodes/PureTrace.git
cd PureTrace
pip install -e .
puretrace render cornell -W 512 -H 512 -s 256 -j 8 -o cornell.png
Enter fullscreen mode Exit fullscreen mode

Python 3.11+. Zero pip dependencies. MIT license.


What This Project Is For

PureTrace is a deliberately readable renderer. Every algorithm is in plain Python, every decision is traceable to the source. If you want to understand how path tracers work — how MIS reduces noise, how BVH traversal works, how closures get closed upvalues — this is a codebase you can actually read.

It is not fast. It is correct, deterministic, and hackable.

Repo: github.com/BleedingCodes/PureTrace


Built by MainbyteLabs — Python tooling for electronics labs, hardware shops, and Linux-based tech teams.

github.com/MR-MainbyteLabs

Top comments (0)