LIVE RENDER Everything runs in your browser, with no saved frames, and the physics shortcuts are listed. Physics limits, stated plainly
How it works
Every pixel fires a ray from the camera and steps it along the Schwarzschild geodesic equations in spherical coordinates, written as first-order equations in the coordinates and their derivatives. Each step checks for the event horizon, the two reference spheres, the jet cones and a crossing of the disk plane. The equations, disk and jet colouring, tone mapping and feature presets come straight from shaders/geodesic.comp and src/black_hole.cpp. Everything runs live in your browser, with no saved frames.
What changed in the port
Each difference is marked in the comments of geodesic.frag.
COMPUTE → FRAGMENT
Compute to fragment
WebGL2 has no compute shaders, so the ray tracer runs in a fragment shader over a full-screen triangle.
INTEGRATOR
True fourth-order Runge-Kutta
The C++ function named
rk4Steptakes one forward Euler step of 107 m (about 0.0008 rs) per iteration, with a budget of 32,000 to 256,000 iterations per ray. The port integrates the same right-hand sides with true fourth-order Runge-Kutta, at a step proportional to r that shrinks near rs, in 90 to 450 steps depending on quality.PER-RAY FRAME
A polar axis for every ray
Each ray gets a polar axis perpendicular to its starting plane. The equations are unchanged by the rotation, and it keeps long steps away from the singularity at the coordinate pole.
JET SAMPLING
Brightness scaled by step length
Jet brightness is scaled by step length so it adds up the same along a path. One time-varying jet term moved about 1000 radians per C++ step, which averages to a constant, so the port uses that constant.
ADDED FOR THE WEB
Starfield
Added here so the bending of light is visible. The C++ leaves escaped rays black.
LEFT OUT
Two features not ported
The test-object gravity mode (right mouse button) and the spacetime grid mesh.
How the port was checked
A script (demo/test/geodesic.mjs) runs the original Euler integration in double precision and compares it with the port's integrator, for rays that start 50 rs from the hole and run until they fall in or pass 60 rs on the way out:
| Quantity | C++ integration | Port |
|---|---|---|
| Shadow edge, impact parameter | 1.7454 rs, about 66,600 steps for a ray there | 1.7437 rs, 84 steps |
| Deflection at b = 3 rs | 0.3557 rad in 140,257 steps | 0.3554 rad in 58 steps |
| Deflection at b = 10 rs | 0.0727 rad in 139,038 steps | 0.0727 rad in 38 steps |
Physics limits, stated plainly
- The radial equation. The C++ radial equation ends in
r * (dθ² + sin²θ dφ²). The Schwarzschild Christoffel symbol gives(r − rs) * (...). As written, light bends about a third as much as general relativity predicts: 0.0340 rad against 0.1063 rad with the corrected term at b = 20 rs over the same path, and a shadow edge near 1.74 rs rather than the analytic 2.60 rs (the corrected term gives 2.58 rs from this starting distance). The page opens with the corrected term. Untick the checkbox to compare with the equations as written in the C++. - Kerr mode is not a full Kerr solution. It keeps the Schwarzschild equations, uses the Kerr formula for the horizon radius, and adds a small Lense-Thirring style term to the azimuthal acceleration about the z axis, while the disk lies in the y = 0 plane. It also makes the disk emission asymmetric. The Kerr metric functions in the GLSL are not used by the integration. With the C++ equations, the shadow changes little with spin: its edge moves from 1.75 rs to 1.71 rs at a/M = 0.8.
- The Doppler term is nearly invisible. It is ported as written. Its velocity works out near β ≈ 10−5, and it follows a fixed world direction rather than the viewer, so toggling it shows almost no change.
- Colours are artistic gradients. The disk uses colour ramps with redshift-style scaling, not radiative transfer, and the disk is a flat plane that stops a ray on contact. The temperature value only selects a ramp.
- TON618 mode is a feature preset. It changes the disk and jet settings. The mass and rs stay those of Sgr A*, as in the C++.
Credits
This project builds on kavan010/black_hole. Its README also acknowledges rossning92/Blackhole and the 20k Schwarzschild tutorial.