2026
TL;DR
A physically accurate spinning diamond icosahedron, rendered in one GLSL shader, in real time. It traces two wavelengths of light per pixel while maintaining 65,536 distinct wavelength combinations across the image, and it never averages anything: not across frames, not across neighboring pixels.
Link: https://www.shadertoy.com/view/sXjXDd
A diamond is a fun thing to render because almost none of the light you see went straight through it. Light bends on the way in, bounces around inside off the facets a bunch of times, then bends again on the way out, and every color bends by a slightly different amount. That last part is dispersion, which is why a diamond throws rainbows. Doing it properly usually means tracing a ton of rays and letting the noise average out, which is slow.
One sample per pixel
Real dispersion means every wavelength takes a slightly different path through the stone, so to get the color right you’d trace red, then orange, then yellow, hundreds of wavelengths, and average them. A path tracer approximates that by picking a random wavelength each sample and averaging thousands of samples over time. It works. It’s also noisy until it converges, and it can’t run clean at one sample per pixel. You sit and wait for it to settle.
So I stopped picking wavelengths at random and started picking them on a schedule.
Every pixel pulls a value from a 16 by 16 Bayer dither pattern, so there are 256 different values tiled across the screen, and that value picks which wavelength the pixel traces. One pixel traces deep red, the pixel next to it orange, the next one green, all laid out in the ordered Bayer pattern. Each pixel is a single pure color. Step back, and your eye blends the neighborhood into white. The averaging still happens. It happens on your monitor and in your eye instead of in an accumulation buffer.
A normal spectral renderer converges over time. This one is already converged in space, because the dither guarantees every little neighborhood covers the spectrum evenly. It runs at one sample per pixel, deterministic, no waiting, with 256 wavelengths spread across the image.
It looks better at two, which is what the demo uses. The second sample isn’t a second wavelength drawn from the same set. It’s offset by the same 256-value dither, so the two samples in a pixel land on a different pair of wavelengths than the two in the pixel next door. Across the screen that’s 256x256, or 65,536 distinct wavelengths, with each pixel only ever tracing two. The whites clean right up. The hard part isn’t being computed, it’s being arranged.
The shape is just planes
It started life as a raymarcher, which is where the “ray” in the name comes from, and the name is now slightly wrong. The shape is an icosahedron, and an icosahedron is nothing but 20 flat faces, which makes it the overlap of 20 flat cuts through space. When your shape is just flat planes, you don’t have to march toward it one careful step at a time. You solve for exactly where a ray crosses each plane.
The 20 faces come out of the golden ratio. The directions the faces point are all sign flips of four base directions built from the golden ratio, which is the number that shows up whenever something has fivefold symmetry, and an icosahedron is about as fivefold-symmetric as a shape gets. So the code carries four directions, flips their signs eight ways each to get all the faces, and to find where a ray enters and exits it intersects the ray with every face and keeps the right ones. Entry is the last plane you cross going in, exit is the first one going out. No marching, no stepping, no missing the surface at a glancing angle.
You get the surface normal for free too. The face a point sits on is whichever face direction it lines up with most, so there’s no need to sample the shape in six directions to guess which way the surface faces, the way a raymarcher has to.
Light that never gets averaged
Inside the stone, every time a ray hits a facet it splits. Some light bounces back in, some passes through and leaves, and how much goes each way is set by Fresnel’s equations and depends on the angle. A path tracer flips a weighted coin at each bounce, follows one path, and averages a thousand of those coin flips to land on the real ratio.
This one doesn’t flip a coin. At each facet it does both. It takes the fraction of light that escapes right then, sends it out to sample whatever’s around it, and adds that in. Then it keeps the fraction that stayed inside, follows only that reflected ray onward, and does the same thing at the next facet. It carries a running number for how much light is still bouncing around, and every bounce peels off the part that leaks out.
The reason this works in one ray instead of a branching tree is that the escaping light doesn’t need to be traced. It leaves and reads whatever’s out there. Only the reflected part keeps going, and that’s a single chain. So you get the exact energy split at every bounce with no noise and no coin flips to average out, using the real dielectric Fresnel equations rather than the cheap approximation most shaders reach for.
What makes it fast
None of the individual tricks are exotic. Stacked up, they’re the difference between a render that crawls and one that runs live in a browser tab.
The unit of work is a plane test. Finding where a ray hits the stone means checking it against all 20 faces, which the code does as four base directions flipped eight ways, so 32 tests per intersection. Every bounce inside is another 32. Left unchecked, 16 bounces times two wavelengths is over a thousand plane tests per pixel, and at a few million pixels that stops being real time in a hurry.
So it doesn’t leave it unchecked. Each bounce keeps a running tally of how much light is still trapped inside, peels off the part that escapes, and the moment the trapped fraction drops below about four percent it quits. A diamond dumps most of its light out fast, so on a real frame most rays finish in a handful of bounces rather than sixteen. There’s a hard cap at 16 as a backstop, but the energy cutoff is what actually keeps the loop short.
It also never reaches for memory it doesn’t have. No accumulation buffer, no history from last frame, no denoise pass peeking at neighboring pixels. Every pixel is a self-contained calculation that starts and finishes inside one shader invocation, which is what a GPU wants. Millions of them run at once with nothing to coordinate.
The two knobs are wavelengths per pixel and that bounce cutoff. Drop to one wavelength and you halve the work. The whites get a touch noisier and nothing else changes. Building quality in across space instead of over time means you can trade sharpness for speed and the picture degrades gracefully instead of dissolving into static.
Real prism colors
Turning a wavelength into a color on screen is its own rabbit hole, and getting it wrong tints the whole gem. A single wavelength, say 500 nanometers, has a real color to a human eye, and that mapping is a measured, standardized thing called the CIE color matching functions. The shader uses a compact math fit of those curves to go from wavelength to the eye’s raw response, then a matrix to turn that into the red, green, and blue your monitor speaks.
White was the part I had to get right. If you sample a bunch of wavelengths evenly and add up their colors you’d hope for white, and you don’t get it, because the curves aren’t balanced evenly across the three channels. So I pre-scale the matrix so an even spread of all wavelengths lands exactly on white. That one adjustment is why a flat-lit facet reads as clean white instead of a muddy yellow, and it holds no matter how many samples per pixel you use. The white point doesn’t drift when you change the quality setting.
Why the faces are flat
I tried to make it fancier and it taught me why the flat shape was right all along.
The one-sample-per-pixel approach depends on flat faces, and I didn’t see that until I broke it. I tried a torus, a rounded box, a squircle, all smooth curved shapes. They all did the same thing: flat parts stayed clean, curved parts exploded into colored static. I chased that noise for a while assuming it was a bug in my intersection math. It wasn’t.
A curved surface at diamond’s index of refraction acts like a lens. It focuses rays hard, so two rays that started one pixel apart can end up somewhere completely different after a few bounces. Neighboring pixels genuinely have wildly different brightness, and a single sample per pixel can’t capture something that changes that fast, so it shows up as speckle. Dropping the index of refraction down toward plain glass calmed it right down, which is what confirmed the diagnosis. It’s real physics that needs a lot of samples to resolve, which is exactly what this whole approach is built to avoid.
Flat faces don’t focus light. Parallel rays going in stay roughly parallel, so a pixel and its neighbor stay similar, so one sample is enough. The icosahedron wasn’t only a nice-looking choice. The flatness is load-bearing. I’d like to say I knew that going in.
The small stuff
A few things that didn’t need their own section. The camera sits far back with a long lens, which is how people actually photograph jewelry, because it flattens the perspective and keeps the whole stone in a tight frame. The background is pure black, which is also the jewelry-photo move.
The gem spins on three axes at once, at speeds set by powers of the golden ratio so the rates never line up, which means the tumble never repeats. And you can grab it. Click and drag rotates it like a turntable, left-right spins it, up-down tips it toward you, and it goes back to spinning on its own when you let go.
All the tracing happens in the gem’s own frame. Instead of rotating the stone, the shader rotates the camera ray and the light into the stone’s coordinates once, up front, and traces against a gem that’s sitting still. Small thing, but it means the spin costs almost nothing.
Looking back
This one turned into a long argument with myself about noise. Every feature I wanted, dispersion and internal reflections and curved shapes, was really a question of where the noise was allowed to live. If you refuse to average over time or space, the noise has to go somewhere, so the craft is arranging things so it lands where you can’t see it. The Bayer dither hides it in a pattern your eye blends away. The deterministic Fresnel takes it out of the light bounces entirely. And the flat faces sidestep the one place, curved focusing, where it can’t be hidden at all.
I came in wanting a pretty diamond. The thing I’d actually defend is the shape choice, which I made for the wrong reasons and only later understood.