[KongchangAI]
· 3 min read· 1,723 words

Retro Rendering on the Web: Dithering and the Art of Retro Shading

Retro Rendering on the Web: Dithering and the Art of Retro Shading

Dithering and retro shading are being embraced by web developers as a deliberate creative language, not a nostalgic compromise.

The article systematically examines how dithering and retro shading can be implemented on the web. Dithering approximates more color gradations using a limited palette through error diffusion, originating from printing halftone processes and represented by the Floyd–Steinberg algorithm. Its web revival is driven by performance and size advantages, stylistic recognizability, controllable randomness, and the maturity of WebGL shaders. Implementation paths include WebGL post-processing shaders and Canvas 2D CPU processing, with ordered dithering favored for real-time shaders due to its full parallelizability.

When the Browser Becomes a Retro Canvas

In an era when 4K screens and high-fidelity rendering are within easy reach, a group of developers are deliberately "downgrading" their visuals—using limited palettes, visible pixels, and intentional dither noise to craft web experiences. This isn't a step backward in technology, but an aesthetic choice known as Dithering and Retro Shading.

A recent article that sparked discussion on Hacker News, The Art of Dithering and Retro Shading for the Web, systematically examines how this set of techniques can be implemented in browser environments. It touches on more than mere "nostalgia"—it addresses a long-underestimated proposition: how to produce expressive imagery under conditions where bandwidth, compute, and color depth are all constrained. And these former compromises are now being reclaimed as a deliberate creative language.

Web retro dithering rendering effect

What Exactly Is Dithering

At a technical level, dithering is an error-diffusion technique that approximates more color gradations using a limited palette. Suppose you only have black and white but want to represent gray—the traditional approach is to give up; the dithering approach is to mix black and white dots at different densities so the eye visually "synthesizes" gray.

This idea originated in the halftone processes of the printing industry and was later adopted extensively by early computer graphics to simulate gradients on low-color-depth displays. The core mechanism is: compute the error between the target color and the currently available color, then diffuse that error to neighboring pixels according to certain rules. The classic Floyd–Steinberg algorithm is one of the most widely circulated.

In the context of retro shading, dithering is often combined with palette restrictions, pixelated scaling, and scanline effects to collectively evoke the visual texture of CRT monitors or early game consoles.

The Floyd–Steinberg algorithm, proposed by Robert Floyd and Louis Steinberg in 1976, is the most classic implementation of error-diffusion dithering. It works as follows: for each pixel, first quantize it to the closest color in the current palette, compute the quantization error, then distribute that error with fixed weights to the right, lower-left, bottom, and lower-right neighboring pixels that have not yet been processed—with weights of 7/16, 3/16, 5/16, and 1/16 respectively. This serial structure—"scanline by scanline, diffusing error toward unprocessed directions"—is inherently non-parallelizable: the result of a later pixel depends on the error input of an earlier one. Its advantages are smooth transitions and fine grain; its drawbacks are a tendency to produce "worm" artifacts, and the sequential dependency that slows down the pipeline in real-time rendering. Understanding this makes it clear why shader-based scenarios prefer ordered dithering.

If you understand dithering merely as "imitating old games," you underestimate its real value. Its revival on the web has several clear drivers:

First, performance and size advantages. When the palette is compressed to dozens or even a dozen or so colors, the cost of image compression and transmission drops significantly. For web pages that need to load large amounts of visual material, this means smaller asset sizes and a faster first screen.

Second, stylistic recognizability. Amid uniform high-definition UIs, images with a distinct graininess and visible color banding are actually more memorable. Indie games, creative portfolios, and experimental websites are the main application scenarios.

Third, controllable randomness. The noise introduced by dithering is not purely a defect; it can make gradients more natural, reduce banding, and lend a handcrafted quality visually.

Fourth, the maturation of web technology itself. The proliferation of WebGL, Canvas 2D, and shaders in the browser means that real-time dithering post-processing—once requiring specialized hardware—can now be achieved with just a few lines of shader code.

Key Implementation Paths on the Web

Post-Processing Shader Approach

The most mainstream approach is to treat dithering as the final post-processing step in the rendering pipeline. The scene is rendered normally to a texture (framebuffer), and then a full-screen quad paired with a fragment shader performs palette quantization and error diffusion on each pixel. This method minimally intrudes on the original rendering logic and can be applied wholesale to the vast majority of WebGL projects.

Post-processing refers to a round of processing applied to the final image after all scene geometry and lighting have been rendered, commonly seen in effects such as anti-aliasing, bloom, tone mapping, and depth of field. Its typical structure is: first render the scene to an offscreen texture (framebuffer) or render target rather than outputting directly to the screen; then use a full-screen quad to sample and transform the texture pixel by pixel with a fragment shader, finally writing to the screen. The advantage of dithering as post-processing lies in decoupling—it doesn't care how many objects are in the scene or what materials they use; it acts uniformly on the final pixels. Therefore it can be attached wholesale to any WebGL project, with small changes and high reusability.

Canvas 2D and the CPU Path

For scenarios that don't require real-time 3D, directly processing pixel by pixel with Canvas 2D is entirely feasible. Image data is retrieved via getImageData, dithering is computed on the CPU side, and then written back to the canvas. The cost is that performance is limited by image size, but for static images or small animated graphics it's sufficient, and the implementation barrier is lower.

Ordered Dithering vs. Error Diffusion

There is a noteworthy trade-off here. Error diffusion (such as Floyd–Steinberg) produces finer color transitions, but it is inherently a serial algorithm that is difficult to parallelize efficiently and can easily become a bottleneck in real-time rendering. Ordered Dithering, by contrast, uses a predefined Bayer matrix threshold and can be computed fully in parallel, independently per pixel, making it better suited to shader implementation. Many web retro rendering projects ultimately choose ordered dithering precisely for the balance of performance and predictability.

The Bayer matrix (also known as an ordered dither matrix) is the core of Ordered Dithering. It is an n×n threshold matrix whose element values are generated by a recursive construction rule—for example, the classic 2×2 form is [[0,2],[3,1]], and 4×4 and 8×8 are derived by recursively extending it. During rendering, the matrix elements are normalized to [0,1) and compared with the target pixel's grayscale or channel value; if greater than the threshold, take a bright color, otherwise take a dark one. Since each pixel depends only on its own coordinates and the fixed matrix, with no cross-pixel dependency, it is fully parallelizable and vectorizable, making it ideal for per-fragment GPU computation. The cost is that in flat regions it forms a visible regular grid pattern, with grain that is regular rather than random—which is precisely the kind of "halftone feel" that many retro rendering styles deliberately seek.

Stability Issues in Animation

Applying dithering to dynamic imagery runs into a thorny problem: the noise flickers from frame to frame, producing a noticeable "boiling" effect. Mitigation methods include fixing the phase of the dither matrix, quantizing time, or applying dithering only to static layers. Whether to accept this flickering often depends on the style the work aims to present.

Banding refers to a gradient that should be smooth being split into distinguishable color steps under low color depth. Its cause is the quantization of the color space: when there are only 32 or 256 levels of brightness, the span between adjacent levels is enough to be perceived by the naked eye as a hard edge. Dithering is precisely the classic means of mitigating banding—by mixing dots of different densities on either side of the boundary, the eye visually "interpolates" an intermediate tone. In addition, low-contrast gradient regions such as screen-space ambient occlusion (SSAO), volumetric fog, and large soft shadows are most prone to exposing banding, so these places are often priority targets for dithering post-processing. In the 8-bit retro style, banding is sometimes retained or even intensified as a stylistic symbol rather than a defect.

More Than a Filter: A Design Methodology

Treating dithering as "slapping on a nostalgic filter" is a common misconception. Truly well-executed retro rendering treats color depth, resolution, dither pattern, and scaling method as a set of mutually constraining design variables to be coordinated as a whole.

Whether the palette is chosen well directly determines whether dithering can express vivid gradations; the lower the resolution, the greater the expressive weight of each individual pixel; and if the dither matrix pattern is not chosen appropriately, it can form visible regular textures in certain grayscale regions. These details stack together to form the key to whether the final result "looks right."

For web creators, this methodology has an additional benefit: constraints actually reduce decision-making costs. When you have only 32 colors and a 320×240 pixel space, the trade-offs in composition and color use become more decisive.

Conclusion

The popularity of dithering and retro shading on the web appears to be a homage to the display technology of a bygone era. But more accurately, it is a form of expression that developers actively choose—trading a constrained medium for a distinctive visual tension. As the WebGL and shader toolchains mature, this set of techniques—once limited by hardware—is gaining entirely new life in the browser. If you want to try it, starting with an ordered dithering shader is the easiest way to see results.

One-line Summary

Dithering and retro shading are being embraced by web developers as a deliberate creative language, not a nostalgic compromise.

Paragraph Summary

The article systematically examines how dithering and retro shading can be implemented on the web. Dithering is a technique that approximates more color gradations using a limited palette through error diffusion; it originates from printing halftone processes and is best represented by the Floyd–Steinberg algorithm. Its web revival has four drivers: performance and size advantages, stylistic recognizability, controllable randomness, and the maturity of WebGL and shaders. Implementation paths fall mainly into two categories: WebGL post-processing shaders and Canvas 2D CPU pixel-by-pixel processing; among these, ordered dithering is better suited to real-time shaders than serial-dependent error diffusion because it can be fully parallelized. The article also points out that the noise "boiling" problem in animation must be mitigated through fixed phase or time quantization, and emphasizes that dithering should be coordinated as part of an overall design involving color depth, resolution, palette, and scaling method, rather than simply layered on as a filter.

#dithering#retro shading#WebGL#Floyd-Steinberg#ordered dithering
Share:

Related articles