Astrophysics Physics WebGL GLSL

Through the Wormhole: Rendering a Morris–Thorne Spacetime Bridge


Live two-universe ray tracer. Every pixel above integrates a null geodesic through a Morris–Thorne / Ellis–Bronnikov wormhole connecting two distinct celestial spheres: Universe A (cyan Milky Way + Saturn) and Universe B (crimson-gold nebula + spiral galaxy). Drag to orbit, open Controls and toggle Fly-through to cross the throat ($l = 0$) in real time, or launch the simulation full-screen.

After rendering Gargantua, the natural sequel is the other relativistic centerpiece of Interstellar: the sphere sitting quietly near Saturn. In decades of science fiction, wormholes were drawn as flat 2D puddles or swirling whirlpools in space. Kip Thorne and the Double Negative team showed what general relativity actually predicts: a three-dimensional crystal sphere whose surface is rimmed by nested Einstein rings of two different universes at once.

Mathematically, a traversable wormhole is even cleaner to ray-trace than a black hole—and much stranger when you fly inside it. Where a Schwarzschild black hole has a one-way event horizon that terminates rays, a Morris–Thorne wormhole has a smooth minimum-radius throat at proper radial coordinate $l = 0$ connecting two asymptotically flat universes: $l \to +\infty$ (Universe A) and $l \to -\infty$ (Universe B). This post builds the full real-time WebGL ray tracer from first principles and renders a 4K 120 fps seamless fly-through loop diving straight through the throat and back.


0. Why a Wormhole Looks Like a Crystal Sphere

Suppose you take a pair of 3D Euclidean spaces, cut a ball of radius $b_0$ out of each, and glue the two spherical boundaries together smoothly. Because the boundary you glued is a 2-sphere ($S^2$), an observer approaching from Universe A does not see a flat circular doorway; from every angle, the throat projects onto a disk of apparent radius $b_0$ on the sky.

Metric
Zero-tidal-force Morris–Thorne / Ellis–Bronnikov wormhole with finite cylindrical throat length $2a$ and throat radius $b_0$.
Coordinate $l \in (-\infty, +\infty)$
Proper radial distance measured through the throat: $l > 0$ is Universe A (Saturn sector), $l = 0$ is the throat equator, and $l < 0$ is Universe B (Gargantua sector).
Interior ($b < b_0$)
Rays aimed inside the critical circle have impact parameter $b < b_0$; they pass through $l = 0$ into Universe B, acting like a wide-angle fisheye lens onto the far universe.
Exterior ($b > b_0$)
Rays aimed outside the throat hit a centrifugal barrier at $r(l_{\text{turn}}) = b$, bounce back into Universe A, and show a lensed reflection of our own sky (notice the secondary lensed image of Saturn hugging the lower-right rim).
Boundary ($b = b_0$)
The throat itself is an unstable photon sphere: rays with $b \approx b_0$ wind around the throat multiple times before escaping to either $+l$ or $-l$, creating an infinite stack of alternating Einstein rings.

1. The Ellis–Bronnikov / Dneg Metric

To make a wormhole human-traversable without crushing the traveller via tidal forces, Morris and Thorne (1988) set the gravitational redshift function $\Phi(l) = 0$ everywhere. Time ticks at the exact same rate inside the throat as at infinity—there is no gravitational time dilation and no horizon. The entire spacetime curvature lives in the spatial circumferential radius $r(l)$:

Zero-tidal-force Morris–Thorne line element
$$ds^2 = -dt^2 + dl^2 + r^2(l)\,\left(d\theta^2 + \sin^2\theta\,d\varphi^2\right)$$

Here $dl$ measures true proper radial distance along a ruler poked through the throat, while $2\pi r(l)$ is the circumference of a sphere centered on the wormhole at coordinate $l$.

In the classic textbook Ellis–Bronnikov wormhole, $r(l) = \sqrt{b_0^2 + l^2}$. For Interstellar (James et al., 2015), Kip Thorne added a second dial: a cylindrical throat of half-length $a$ so the team could tune how long the Endurance spent inside the tunnel and how tightly the Einstein rings packed around the rim. Using a soft-plus transition of width $\sigma \ll a$ across $|l| = a$, the profile integrated by our shader is:

Circumferential radius profile $r(l)$ and its derivative
$$r(l) = \sqrt{b_0^2 + x(l)^2}, \qquad x(l) = \frac{(|l| - a) + \sqrt{(|l| - a)^2 + \sigma^2}}{2}, \qquad \frac{dr}{dl} = \frac{x(l)}{r(l)}\,\frac{dx}{dl}$$

Here $\sigma$ sets the collar smoothing width across $|l| = a$. For $|l| \gg a$, $r(l) \to |l|$ and both universes become flat Euclidean space; inside the throat ($|l| < a$), $r(l) \approx b_0$ and $dr/dl \approx 0$ (for $\sigma \ll a$). Setting the Throat Length slider $2a = 0$ with $\sigma \to 0$ recovers the pure Ellis wormhole.

Because the metric is spherically symmetric on both sheets, every light ray stays in a single 2D plane spanned by the observer's radial unit vector $\hat n_0$ and the ray's initial tangent vector $\hat t_0$ on $S^2$. Parameterizing the null geodesic by proper spatial arc length $s$ (so $(dl/ds)^2 + r^2(l)(d\varphi/ds)^2 = 1$), Euler–Lagrange conservation of angular momentum gives a conserved impact parameter $b$:

Coupled 1st/2nd-order null geodesic ODEs integrated per pixel
$$b = r(l_0)\,\sin\psi_0, \qquad \frac{d^2 l}{ds^2} = \frac{b^2}{r^3(l)}\,\frac{dr}{dl}, \qquad \frac{d\varphi}{ds} = \frac{b}{r^2(l)}$$

Compare this to Schwarzschild: there is no singularity at $r = 0$ because $r(l) \ge b_0 > 0$ everywhere! Even better, the second-order equation for $l(s)$ has no coordinate turning-point singularity when a ray bounces off the centrifugal barrier.

Why integrate $\ddot l$ instead of the first-order equation $\dot l = \pm\sqrt{1 - b^2/r^2(l)}$? In the first-order form, every exterior ray ($b > b_0$) hits a turning point where $r(l) = b$ and the square root hits zero, requiring manual sign-flipping and branch logic. In the second-order form $\ddot l = (b^2/r^3)\,dr/dl$, the outward "centrifugal force" smoothly decelerates $v_l = \dot l$ through zero and reverses it automatically.
💻 GLSL: Velocity-Verlet Wormhole Geodesic IntegratorGLSL
// Decompose pixel ray rd into radial (e_l = uCamDir, outward radial unit vector) and tangential components on S^2
vec3 e_l     = uCamDir;
float dl_ds  = dot(rd, e_l);                  // initial radial velocity cos(psi)
vec3 tang    = rd - dl_ds * e_l;
float sinPsi = length(tang);
vec3 n0      = uCamDir;
vec3 t0      = (sinPsi > 1e-6) ? (tang / sinPsi) : normalize(cross(n0, vec3(0.0, 1.0, 0.001)));

float r0, dr0;
wormholeMetric(uCamL, r0, dr0);
float b = r0 * sinPsi * uLensing;             // conserved impact parameter

float l   = uCamL;
float vl  = dl_ds;
float phi = 0.0;

for (int i = 0; i < MAX_STEPS; i++){
  float r, drdl;
  wormholeMetric(l, r, drdl);
  if (abs(l) > escapeL && l * vl > 0.0) break; // escaped to Universe A (+l) or B (-l)

  float ds = clamp(0.045 * r + 0.035 * abs(l), 0.025, 0.95);

  // Second-order midpoint / Velocity-Verlet step on (l, vl) and phi
  float acc1   = (b * b) / (r * r * r) * drdl;
  float vl_mid = vl + 0.5 * acc1 * ds;
  float l_mid  = l + 0.5 * vl * ds;
  float r_mid, dr_mid;
  wormholeMetric(l_mid, r_mid, dr_mid);

  float acc_mid = (b * b) / (r_mid * r_mid * r_mid) * dr_mid;
  l   += vl_mid * ds;
  vl  += acc_mid * ds;
  phi += (b / (r_mid * r_mid)) * ds;
}

// Reconstruct asymptotic 3D look direction on the target celestial sphere
vec3 nFinal = cos(phi) * n0 + sin(phi) * t0;
vec3 tFinal = -sin(phi) * n0 + cos(phi) * t0;
float rEnd, drEnd;
wormholeMetric(l, rEnd, drEnd);
float vTang = clamp(b / rEnd, 0.0, 1.0);
float vRad  = sign(vl) * sqrt(max(0.0, 1.0 - vTang*vTang));
vec3 skyDir = normalize(vRad * nFinal + vTang * tFinal);

vec3 col = (l >= 0.0) ? skyUniverseA(skyDir) : skyUniverseB(skyDir);

2. The Throat as a Photon Sphere: Bifurcating Einstein Rings

Look closely at the boundary of the wormhole sphere in the simulation above. Where a black hole has a photon sphere at $r = 1.5\,r_s$ bordering a pitch-black shadow, the wormhole's photon sphere sits right at the throat $l = 0$ ($r = b_0$) bordering a second universe.

Why does that matter? Because rays with impact parameter $b$ just slightly larger than $b_0$ ($b = b_0 + \epsilon$) dive toward the throat, wind around the equator $N$ times as $dr/dl \to 0$, and eventually bounce back into Universe A. Rays with $b$ just slightly smaller than $b_0$ ($b = b_0 - \epsilon$) wind around the exact same throat $N$ times and then slip across $l = 0$ into Universe B:

Winding angle near the critical impact parameter $b \to b_0$
$$\Delta\varphi(b) = \int \frac{b\,dl}{r(l)\sqrt{r^2(l) - b^2}} \;\underset{b \to b_0}{\sim}\; \ln\!\left(\frac{b_0}{|b - b_0|}\right) \;+\; \Theta(b_0 - b)\,\frac{2ab}{b_0\sqrt{b_0^2 - b^2}}$$

The flared mouths ($|l| > a$) contribute a logarithmic divergence on both sides of $b_0$, while interior rays ($b < b_0$) that enter the cylindrical throat ($r(l) \approx b_0$ on $|l| \le a$, regularized by $\sigma$) pick up an additional spiral traversal angle proportional to the throat length $2a$.

This creates a striking visual signature you can test interactively right now:

  • Outside the critical circle ($b > b_0$): Saturn and the blue-cyan Milky Way of Universe A are compressed into thin concentric arcs hugging the outer rim—including an inverted secondary image of Saturn on the opposite side of the sphere.
  • Inside the critical circle ($b < b_0$): The crimson-gold nebula and two-arm spiral galaxy of Universe B fill the crystal ball, with their own compressed Einstein rings stacked just inside the critical curve.
  • Inside the throat ($l = 0$): Click the Throat (l=0) preset button in Controls. When the camera sits at $l = 0$, the wormhole no longer looks like a sphere in front of you—it wraps around the entire $360^\circ$ sky as an equatorial band! The forward hemisphere shows Universe B, the backward hemisphere shows Universe A, and the perpendicular equator ($\psi_0 = 90^\circ$) is trapped on the photon sphere.

3. Dual-Universe Render Pipeline

Because both celestial spheres are evaluated procedurally in GLSL, the entire two-universe spacetime requires zero external texture fetches:

Observer (l₀, n̂₀) b = r(l₀)·sin ψ₀ Verlet l(s) integrator l̈ = (b²/r³)·dr/dl Sheet branch sign(l_final) → A or B Dual sky shaders Saturn + Spiral galaxy ACES + Bloom
Figure 1: Each pixel integrates $(l(s), \varphi(s))$ on the great circle defined by $(\hat n_0, \hat t_0)$, then branches on the sign of $l_{\text{final}}$ to sample either Universe A or Universe B.

4. Designing a Seamless Two-Universe Fly-Through Loop

For the offline 4K 120 fps render, a static orbit in Universe A would miss the entire point of a traversable wormhole. Instead, the camera executes a closed $C^1$-continuous harmonic plunge across the throat: starting at $l = +8.8\,b_0$ in Universe A, diving straight through the Einstein ring at $l = 0$, emerging at $l = -5.6\,b_0$ inside the crimson-gold nebula of Universe B, and smoothly gliding back through the throat to its exact starting pose at $t = L = 8.0\text{ s}$:

Closed harmonic traversal trajectory ($t \in [0, L]$)
$$l(t) = l_{\text{mid}} + A_l \cos\!\left(\frac{2\pi t}{L}\right), \qquad \varphi_{\text{cam}}(t) = \varphi_0 + A_\varphi \sin\!\left(\frac{2\pi t}{L}\right), \qquad \theta_{\text{cam}}(t) = \theta_0 + A_\theta \frac{1 - \cos(2\pi t / L)}{2}$$

With $l_{\text{mid}} = 1.6\,b_0$ and $A_l = 7.2\,b_0$, the camera crosses $l = 0$ twice per loop while its radial velocity $\dot l(t) \propto -\sin(2\pi t/L)$ vanishes smoothly at both turnaround points ($t = 0$ and $t = L/2$).

To keep Universe B's rotating spiral galaxy seamless across the loop boundary without a crossfade, the shader exploits the galaxy's two-fold rotational symmetry ($\cos(2\alpha - 3.6\ln r)$): advancing the galactic azimuth $\alpha$ by exactly $\pi$ radians over the 8-second period maps Arm 1 onto Arm 2 at $t = L$, making the background evolution analytically periodic.


5. The 4K 120 fps Fly-Through Video

Rendered offline via the same stateless slice architecture as Gargantua—960 frames at 3840×2160, 120 fps—here is the complete traversal from the Saturn sector, through the wormhole throat, into the Gargantua sector, and back:

Morris–Thorne Wormhole · seamless fly-through loop. 3840×2160 · 120 fps · 8.000 s · 24.2 MB. Watch the outer Einstein ring expand to fill the 360° sky as the camera passes $l = 0$, then collapse behind us into Universe B.

Toggle the Sky grid pill in the live simulation at the top of the page and drag the Radial pos · l slider slowly through zero—watching a coordinate grid invert across two universes in real time is the closest intuition you can get for curved 3-manifolds without leaving your browser.