Gargantua — a ray-traced black hole in my portfolio (WebGL, one draw call)

Hi everyone! I built my portfolio around a black hole inspired by Interstellar’s Gargantua, and I wanted to share it here.

It isn’t a model or a texture: a fragment shader traces the light around a Schwarzschild black hole for every pixel (velocity-Verlet on the photon orbit equation, up to 190 steps per pixel, 340 on the high tier). The photon ring, the secondary image and the lensed starfield come out of the physics. The disk is Keplerian fbm noise with two cross-faded epochs so the shear never winds it into sub-pixel rings, plus relativistic Doppler beaming.

The three.js side is deliberately small: the black hole is a single draw call (a full-screen quad and one ShaderMaterial), accumulated over eight jittered frames to smooth the edges, then UnrealBloomPass. Bloom was lighting up the shadow, so a SavePass keeps the clean image and puts it back inside the shadow. When nothing moves, it stops drawing.

Live demo (switch to Study to change views and toggle Doppler, the secondary images and the lens one by one):

Write-up with the shader code: How I Built a Black Hole in WebGL with Three.js · Jonás Javier

Feedback very welcome, especially on performance on phones.

1 Like

It’s amazing what AI can create these days!

1 Like

Impressive shaders! But a bit disappointing that there’s no orbital camera controller like the other examples.

1 Like

Thanks! Leaving it out was a deliberate choice, not an oversight. The image is accumulated over eight jittered frames (that’s what gives the disk its clean finish), and dragging would reset that on every frame, so it would look visibly grainier while you move. The other reason is composition: I spent a lot of passes framing those four views, and a free camera guarantees someone ends up seeing it from an angle nobody framed.

The other specimens in the same observatory Endurance, Miller, Edmunds, Ranger, Tesseract
do have drag-to-orbit, since they’re regular meshes. For Gargantua it isn’t ruled out: the plan would be to lower the accumulation while dragging and refine again on release. If it holds up from every angle, I’ll add it and post an update here.

Thanks! AI was definitely part of the toolkit and helped a lot with the coding. But the visual decisions were very manual — the four views, framing, Doppler intensity, and lensing all went through many rounds of screenshot comparisons and tuning by eye. I wrote up the physics and implementation too, if you’re curious.