Dithering about…

Dithering is a technique used in computer graphics to create the illusion of color depth and smooth gradients in images with a limited color palette. By strategically placing pixels of different colors, dithering can simulate a wider range of tones and dramatically reduce the appearance of harsh banding in gradients.

The technique has its roots in early computing, where hardware constraints meant that displays could only show a handful of distinct colors at once. Artists and engineers alike had to get creative, turning limitation into aesthetic. Today dithering is less a workaround and more a deliberate stylistic choice — a nod to a particular moment in the history of computing that many of us still feel fondly about.

Click anywhere on the hero canvas above to cycle through the available dithering modes. Use the controls panel (press D to toggle) to tweak the shader uniforms in real time.

The Naive Threshold

The simplest possible approach: compare each pixel’s luminance to a fixed cutoff. Above it → white. Below it → black. The result makes the banding problem obvious — smooth gradients collapse into flat regions with a hard edge, and tonal information simply disappears. This is exactly what dithering sets out to fix.

Switch to Color Separation to apply a slightly different threshold per channel. Red, green and blue each cross the cutoff at a different luminance level, producing a trichromatic fringing at edges that reads somewhere between a print mis-registration error and a glitchy CRT.

White Noise, Blue Noise and True Blue Noise

Before ordered dithering took hold, the simplest approach was to compare each pixel’s luminance to a random number. This is white noise dithering: cheap, stateless, and recognisably grainy. The randomness means there is no spatial coherence: adjacent pixels have no relationship with each other, which tends to read as visual static rather than a smooth gradient. It can be pleasant in a monochromatic context, however, where the noise texture blends into the subject matter and adds a bit of character.

Blue noise dithering is the refined cousin. Real blue noise requires pre-rendered textures with careful sampling to achieve the desired distribution. A cheaper stateless alternative is Interleaved Gradient Noise (IGN), a dot-product hash that suppresses low spatial frequencies. It gives a blue-noise-like spectrum without any texture read, but it also comes with a visually unpleasant diagonal pattern.

A real blue noise texture contains randomness that has been carefully sculpted so that low frequencies are suppressed and neighbouring sample points stay spread apart. The result feels less repetitive than ordered dithering and less chaotic than white noise: a pleasant middle ground, especially for organic or naturalistic subjects.

The implementations were also tuned so that you can control the noise upper threshold - this can help smooth out the highlights to some extend. Press D top open settings and adjust the uNoiseThreshold uniform to see the effect.

CRT & Scanlines

The same principles of dithering can be used in more stylized ways, such as evoking the look of vintage CRT displays. These styles can be combined with other effects such as distortion, bloom and chromatic aberration to create a convincing retro aesthetic. The key is to understand the underlying characteristics of CRT displays and how to replicate them effectively in a digital context.

To mimic the look of a CRT, you can start with a simple scanline effect which effectively darkens or sets the color of every other horizontal line of pixels. The key here is that you need to apply this effect in the desired resolution of the final output, not the original input.

To further enhance the CRT effect, you can try to mimick the actual RGB channels of the display by simply only selectively allowing the red, green or blue channels to be visible on subsequent pixels.

The demo lets you compare the two effects in isolation, scanlines alone versus the full CRT treatment with the phosphor mask on top.

More to come...

There is plenty to explore beyond the modes covered here: bayer dithering, error diffusion, custom pattern dithering and many more ideas. Stay tuned for updates as I continue to expand the demo with new techniques and variations.


Implementation Notes

All of the effects here are implemented as custom post-processing effects using @react-three/postprocessing. Each mode is defined as a GLSL fragment shader that runs in a mainImage function, receiving the rendered scene as an inputBuffer texture and writing to outputColor.

Keeping each mode as a standalone shader object in shaders.js makes it straightforward to add new variants: define the source, declare any uniforms you need, and the Leva control panel picks them up automatically.

The blue noise texture is loaded once and injected as a sampler2D uniform. It uses NearestFilter and NoColorSpace to preserve the raw threshold values — bilinear interpolation or sRGB decoding would corrupt the frequency distribution and undo the whole point of using a blue noise texture in the first place.

Press ~ or D for controls