← All work
Selected Work · General Relativity

Ray-Tracing Black Holes

Rendering Schwarzschild and Kerr black holes from the metric up — shadow, lensing, and a Doppler-boosted disk.

Python · SciPy Null geodesics · RK45 Schwarzschild & Kerr PHYS 161 · UC San Diego
Rendered Schwarzschild black hole with a Doppler-boosted accretion disk lensed above and below the shadow, Milky Way in the background
The shadow, the disk, and the lensed sky. A non-spinning (Schwarzschild) black hole with a thin orbiting disk, rendered against a real Milky Way all-sky map. Light bending wraps the far side of the disk up and over the shadow — the same effect that made Interstellar's Gargantua look the way it does.

The idea

This was my final project for PHYS 161 (Black Holes) with Prof. George Fuller. The goal was to take the general relativity we'd been doing on paper — metrics, Christoffel symbols, the geodesic equation — and turn it into actual images of a black hole: the shadow, an accretion disk, and a distorted background sky.

I built a small ray tracer from scratch for two spacetimes — the non-spinning Schwarzschild black hole and the spinning Kerr black hole — so I could see directly how spin, frame dragging, and relativistic Doppler shifts change what a black hole looks like. The north star was Interstellar (2014): I wanted those visuals, but coming out of my own code, from the equations up.

How it works — a four-box pipeline

  1. Pick a spacetime, build the metric. Implement gμν(r,θ). For Schwarzschild I hard-code the analytic Christoffel symbols; for Kerr I take finite-difference derivatives of the metric to build them approximately.
  2. Fire a photon per pixel. Place a camera at radius R, build a local orthonormal tetrad, and for each pixel construct an initial photon four-momentum in the camera frame, then transform it into coordinates.
  3. Integrate the null geodesic. Solve the geodesic equation as a first-order system — first with a hand-written Euler step, then upgraded to RK45 (SciPy's solve_ivp) for accuracy — watching for three fates: it falls through the horizon (radius 2M for Schwarzschild, M + √(M² − a²) for Kerr), it hits the disk, or it escapes to the sky.
  4. Color the pixel. Horizon hits are black; disk hits get a base color scaled by the Doppler factor; escaping rays are mapped onto a rainbow test-sphere or the Milky Way sky by their final direction.

The two spacetimes are just two metrics feeding the same machine. Schwarzschild, in the usual coordinates:

Schwarzschild line elementds² = −(1 − 2M/r) dt² + (1 − 2M/r)⁻¹ dr² + r² dθ² + r² sin²θ dφ²

Kerr adds the spin a — and with it a cross term dt dφ that mixes time and rotation. That single term is frame dragging:

Kerr line element · Boyer–Lindquistds² = −(1 − 2Mr/Σ) dt² − (4Mar·sin²θ / Σ) dt dφ + (Σ/Δ) dr² + Σ dθ² + (r² + a² + 2Ma²r·sin²θ / Σ) sin²θ dφ² with  Σ = r² + a²cos²θ,  Δ = r² − 2Mr + a²

Light follows null geodesics — paths with gμν kᵘ kᵛ = 0 — and to color the disk I compute a relativistic redshift/Doppler factor from the four-velocities of the gas and the observer:

Doppler / redshift factorg = ν_obs / ν_emit  →  g > 1 brightens & blueshifts (gas moving toward you), g < 1 dims & reddens (moving away)

Results

To make the lensing obvious, I first rendered the black holes against a rainbow-striped sphere. The difference between no spin and spin is immediate:

Schwarzschild black hole against a horizontal rainbow background — symmetric shadow
Kerr black hole against a horizontal rainbow background — shadow shifted to one side
Left — Schwarzschild (no spin): the shadow is a perfect circle and the color bands bend symmetrically around it. Right — Kerr (spinning): the shadow is pushed to one side and the lensing loses its symmetry. That asymmetry is frame dragging — the spinning black hole dragging spacetime around with it.

Sweeping the Kerr spin parameter a from zero upward, you can watch the shadow deform and drift:

Kerr spin a=0
a = 0
Kerr spin a=0.5
a = 0.5
Kerr spin a=0.9
a = 0.9

The same sweep against a vertical rainbow bends the stripes the other way, but tells the identical story — symmetric with no spin, drifting and lopsided as the spin grows:

Kerr black hole, vertical rainbow, spin a=0
a = 0
Kerr black hole, vertical rainbow, spin a=0.5
a = 0.5
Kerr black hole, vertical rainbow, spin a=0.9
a = 0.9

Swap the rainbow for a real Milky Way sky and add the Doppler-boosted disk, and it starts to look like the real thing:

Kerr black hole with Doppler-boosted accretion disk against the Milky Way
Kerr, with the disk. One side of the ring runs bright and blue where the gas orbits toward the camera; the other side fades and reddens as it recedes — relativistic Doppler beaming, straight out of the four-velocities.

What I took from it

Working directly with the metric, the Christoffel symbols, and the geodesic equation is what finally connected the math of general relativity to something I could see. Changing from Schwarzschild to Kerr visibly changes the light paths — and therefore the shape and position of the shadow. Building the Doppler factor out of four-velocities made relativistic beaming concrete, because I could watch one side of the disk brighten and the other fade. Understanding a system until I feel part of it — this is what that means for me.

Read the full write-up (PDF) →
← All work