A real-time Schwarzschild black hole + accretion disk ray tracer built with Swift. GPU-accelerated geodesic tracing through curved spacetime via a Metal compute shader, a hot accretion disk wrapping the event horizon, a live spacetime-bending grid, and an orbit camera — with the physics of the C++ original ported, corrected, and unit-tested. Pure Swift + Metal, no third-party dependencies.
- Real-time geodesic ray tracing: every pixel marches a null geodesic through the Schwarzschild metric (RK4, up to 60,000 steps) in parallel on the GPU
- Black hole shadow & photon ring: rays that fall through the event horizon render black; rays with near-critical impact parameter orbit the photon sphere at
3r_s/2and pile up into the bright photon ring - Accretion disk: rays crossing the equatorial plane between
2.2 r_sand5.2 r_sshade the disk with a temperature gradient (hot inner / cool outer), lensed over and behind the hole - Spacetime grid: the "trapdoor in spacetime" grid recomputed every frame from the Schwarzschild radius
rs = 2GM/c²(warm well-bottom / cool flat) - N-body gravity: toggleable — two suns orbit the hole under Newtonian attraction (semi-implicit Euler)
- Orbit camera: drag to orbit, scroll to zoom, G toggles gravity, O resets the camera, Q quits
.
├── src/ # Swift implementation
│ ├── Package.swift
│ ├── Sources/
│ │ ├── BlackHoleCore/ # Physics, camera, geodesic reference (CPU, unit-tested)
│ │ ├── MetalShaders/ # MSL kernels: geodesic ray tracer / display / grid
│ │ └── BlackHoleApp/ # Metal renderer + AppKit window + CLI
│ └── Tests/BlackHoleCoreTests/ # Physics / camera / geodesic / scene tests
├── black_hole/ # Original C++ version (kavan010/black_hole), git submodule
├── doc/img/cover.png # Cover image (rendered by this simulator)
├── README.md
└── LICENSE # MIT License
| Input | Action |
|---|---|
| Left-drag | Orbit camera around the black hole |
| Scroll (two-finger) | Zoom in / out |
| G | Toggle N-body gravity |
| O | Reset camera to the initial view |
| Q | Quit |
- Light follows null geodesics of the Schwarzschild metric, integrated with classic 4th-order Runge-Kutta in affine parameter λ (the original
geodesic.componly applied a single Euler substep) - The disk is detected by interpolating the equatorial-plane crossing of each integration step — the exact crossing radius beats the reference's "sign change + new-position radius" test, which can miss the thin disk
- The grid bends by the Schwarzschild radius: per vertex
dy = 2·√(rs·(d−rs)), matching the C++ bend formula - Two corrections over the original: the radial geodesic RHS carries the missing metric factor
f(so the photon sphere reappears at3r_s/2), and the seeded energy matches the null constraintE² = dr² + f·r²(dθ²+sin²θ dφ²)— both verified by unit tests; the N-body integrator uses semi-implicit Euler (v += a·dt) instead of the dimensionally inconsistentv += a
- C++ reference implementation: kavan010/black_hole, kept as a git submodule in
black_hole/
