Surma, a web developer, writes about WebAssembly, WebGPU, Rust, image dithering, and performance on the web.
HN commenters recommend his dithering article as a great explanation, including blue noise, and his WebGPU tutorial as an easier way into GPU programming.
The major dithering algorithm that's missing from this list is blue-noise dithering. This is very similar to "ordered dithering"; you can think of ordered dithering as either thresholding the pixel values with a different threshold value on each pixel, following a regular pattern, or as adding a different offset value to each pixel, following a regular pattern, and thresholding the result with a constant threshold. Blue-noise dithering replaces the regular pattern with a random pattern that's been high-pass filtered. This has all the advantages of ordered dithering, in particular avoiding "crawling" patterns during animation, but avoids the repetitive patterns and line artifacts it introduces.
https://nelari.us/post/quick_and_dirty_dithering/ is the best quick introduction to the technique that I've seen. There's a more comprehensive introduction at https://momentsingraphics.de/BlueNoise.html. https://bartwronski.com/2016/10/30/dithering-part-three-real-world-2d-quantization-dithering/ also demonstrates it, comparing it to other dithering algorithms.
Ulichney introduced blue noise to dithering in 01988 as a refinement of "white-noise dithering", also known as "random dithering", where you just add white noise before thresholding: https://cv.ulichney.com/papers/1988-blue-noise.pdf. Ulichney's paper is also a pretty comprehensive overview of dithering algorithms at the time, and he also makes some interesting observations about high-pass prefiltering ("sharpening", for…
"Collision detection is usually a tree search"
Yes, because of the very limited numbers of CPU cores. With a GPU you can just assign one core to one particle.
Here is a simple approach to do it with WebGPU:
https://surma.dev/things/webgpu/
It uses the very simple approach, of testing every particle with EVERY other particle. Still very performant (the simulation, the choosen rendering with canvas is very slow)
I currently try to do something like this, but optimised. With the naive approache here and Pixi instead of canvas, I get to 20000 particles 120 fps on an old laptop. I am curious how far I get with an optimized version. But yes, the danger is in calculating and rendering blocking each other. So I have to use the CPU in a smart way, to limit the data being pushed to the GPU. And while I prepare the data on the CPU, the GPU can do the graphic rendering. Like I said, it is way harder to do right this way. When the simulation behaves weird, debugging is pain.
Oh man, there is an absolute beauty with 1-bit graphics that I think is deeply underappreciated. When the right dithering algorithm is used, the graphics look like what characters in cyberpunk novels dream of. [1]
I secretly wish the dominant aesthetic in computing was one where everything was mac-like 1-bit dithered graphics, and we only used color when absolutely necessary - when the black and white graphics just couldn't convey what was needed. Like if instead of throwing it away for color, we had just built on top of it.
Side note, I'm also often struck how 1-bit-ish graphics sometimes were used to great effect on the ZXSpectrum, another minimally graphically capable computer of the era. When the game developers were trying to use color the games often looked worse than if they had just stuck with a simpler scheme. Chase H.Q., Bombjack, Pang!, etc. all look amazing to my eye.
1 - https://surma.dev/things/ditherpunk/
Interesting, but very heavy read. And this is about the low level access towards it via system libaries.
A more simple way to get into GPU programming, would be WebGPU.
I can recommend this tutorial as an introduction:
https://surma.dev/things/webgpu/
GPU programming is really a different way of programming, even though the code looks mostly familiar. And if you are used to single threaded, it will give you a massive headache thinking about, how to do everything in parallel, so that everything is not blocking everything.
The raw power is tempting, so much that I evaluate now how to do allmost everything on the GPU, but many things simply are not suitable to do there.
Basically, you want to avoid conditions and loops. Cannot do recursion. Debugging is a pain and non obvious things can make everything very slow, until you roughly understand, what is going on inside.
Cool, yeah Floyd Sternberg is definitely period appropriate!
This article on dithering made the rounds a few months ago, and was a fascinating read; the hilbert curve method at the end (Riemersma) might be a good fit and get rid of some of the line effects (if that's of interest). And the Atkinson error diffusion sounds like it even has some history in old macs.
https://surma.dev/things/ditherpunk/
There's a really nice blog post on video stable dithering in Return to the Obra Dinn, but in that case it's a 3d game, and they've got direct access to the scene geometry. I'm curious if there's a nice way to use video encoding pixel tracking vectors to reduce swimminess...
“Dithering” is the key — except this seems to have been done by hand.
When I was a kid, I owned a monochrome display that could only display at CGA resolutions “640x400” 1-bit (and 320x200). Many games and art and didn’t support that showed up garbled.
Then I got hold of Deluxe Paint that would load pictures in color and dither them with an algo called Floyd Steinberg. And the pictures that I saw on my friends VGA monitors suddenly looked beautiful on my monochrome screen.
See examples https://surma.dev/things/ditherpunk/
Games like Monkey Island were also ditherered for monochrome displays and they looked great.