Built a real-time black hole renderer on top of R3F — every photon path is
integrated through an actual Kerr geodesic solver rather than faked with a
lensing sprite, and the accretion disk’s brightness/color come from the same
equations that describe a real spinning black hole
What’s actually simulated, not just styled:
- Photons integrate through the Kerr–Schild form of the Kerr metric
(Cartesian, horizon-penetrating coordinates), evolved with a symplectic
Euler step on the HamiltonianH = ½(−E² + |p|² − fΦ²)— frame dragging
falls out of the integration itself. - The disk starts at the exact prograde ISCO (Bardeen–Press–Teukolsky
formula) for spin a★ = 0.95, and follows a Novikov–Thorne temperature
profile, so it cools to black right at the inner edge instead of glowing
up to the horizon. - Color comes from a Planckian blackbody fit at the gravitationally +
Doppler-shifted temperature; brightness from δ⁴ relativistic beaming
(Liouville’s theorem) — that’s why the approaching limb outshines the
far side. - The background sky is a real three.js scene (nebula dome + a 3D star
point-cloud + a background galaxy), rendered once to an HDR cubemap via
CubeCamera. The geodesic integrator samples it with the bent ray
direction, so the nebula/stars get lensed into the halo exactly like the
disk’s far side does. - The HUD’s time dilation, local gravity, and tidal-force readouts are
computed live from the camera’s exact Boyer–Lindquist position each
frame, scaled to a real ~1e8 solar-mass hole.
Post-processing is a standard HDR pipeline: mipmap Bloom → ACES filmic
tonemap → grain/vignette.
Stack: Next.js · React Three Fiber · drei · @react-three/postprocessing
Would love feedback, especially from anyone who’s done geodesic ray-marching
in GLSL before — curious if there’s a cleaner way to handle the disk’s
razor-thin-slab sampling without the adaptive step clamping I ended up with.
