Die Kunst des Dithering: Schöne Bilder mit einem Bit
Wie Dithering-Algorithmen mit nur Schwarz und Weiß Schattierungen erzeugen. Von Floyd-Steinberg bis Blue Noise, mit praktischen Implementierungen.

Lucas Popes Return of the Obra Dinn gehört zu den optisch beeindruckendsten Spielen überhaupt, und es kommt mit genau zwei Farben aus: Schwarz und Weiß. Ein Bit pro Pixel. Keine Graustufen, keine Verläufe, kein Anti-Aliasing. Jeder Pixel ist entweder an oder aus. Trotzdem rendert das Spiel eine vollständig 3D-Welt mit erkennbaren Figuren, Tiefenwirkung und atmosphärischer Beleuchtung, alles dank cleverem Dithering.
Dithering ist die Technik, schwarze und weiße Pixel so in Muster anzuordnen, dass das Auge Zwischentöne wahrnimmt. Die Methode gibt es seit den Anfängen der Computergrafik, und sie erlebt gerade ein Comeback: in der Spieleentwicklung, im Webdesign und bei dem zunehmend wichtigen Problem, Bilder auf E-Ink-Displays darzustellen. Die dahinterliegenden Algorithmen sind elegant, erstaunlich vielfältig und vielseitiger einsetzbar, als man denkt.
Warum Dithering funktioniert
Dein visuelles System verarbeitet nicht einzelne Pixel, sondern fasst Bereiche zusammen. Wenn du ein Muster aus abwechselnd schwarzen und weißen Pixeln betrachtest, siehst du keine einzelnen Punkte. Du siehst Grau. Ein Bereich, in dem 25 % der Pixel weiß sind, wirkt wie 25 % Grau. Ein Bereich mit 75 % weißen Pixeln wirkt wie 75 % Grau. Die Dichte der weißen Pixel in einer Fläche bestimmt also die wahrgenommene Helligkeit.
Dahinter steckt dasselbe Prinzip wie beim Rasterdruck in Zeitungen. Die Punkte unterschiedlicher Größe und Abstände erzeugen auf weißem Papier aus reiner schwarzer Tinte die Illusion stufenloser Töne. Dithering ist das digitale Gegenstück, nur dass hier Pixelmuster statt Punktgrößen verwendet werden.
Die Herausforderung ist nicht, Graustufen zu erzeugen, das ist simpel. Die Herausforderung ist, es ohne sichtbare Artefakte zu tun: ohne Banding (abrupte Übergänge zwischen Mustern), ohne richtungsabhängige Verzerrungen (Muster, die sichtbare Linien bilden) und ohne Moiré-Effekte (Interferenzmuster zwischen dem Dithering und dem Pixelraster des Displays).
Threshold-Dithering: Der naive Ansatz
Das einfachste Dithering: Die Helligkeit jedes Pixels wird mit einem Schwellenwert (meist 50 %) verglichen. Heller als der Schwellenwert wird weiß, dunkler wird schwarz. Das ergibt eine kontrastreiche Silhouette ganz ohne Abstufungen. Gut für Text und Strichzeichnungen, schlecht für Fotos oder 3D-Renderings.
import numpy as np
from PIL import Image
def threshold_dither(image, threshold=128):
"""Binary threshold — no dithering at all, really."""
gray = np.array(image.convert('L'), dtype=float)
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
Ordered Dithering: Muster auf einem Raster
Ordered Dithering verwendet eine Schwellenwertmatrix (auch Bayer-Matrix genannt), die den Schwellenwert über eine sich wiederholende Kachel variiert. Statt jeden Pixel mit demselben Schwellenwert zu vergleichen, wird jeder Pixel abhängig von seiner Position gegen einen anderen Wert aus der Matrix geprüft.
def ordered_dither(image, matrix_size=4):
"""Ordered dithering with Bayer matrix."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
# 4×4 Bayer matrix (values 0-15, normalized to 0-255)
bayer_4x4 = np.array([
[ 0, 8, 2, 10],
[12, 4, 14, 6],
[ 3, 11, 1, 9],
[15, 7, 13, 5]
]) * (255 / 16)
# Tile the matrix across the image
threshold = np.tile(bayer_4x4, (h // 4 + 1, w // 4 + 1))[:h, :w]
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
Ordered Dithering erzeugt ein unverwechselbares Kreuzschraffur-Muster, das man sofort erkennt. Bei niedrigen Auflösungen ist die regelmäßige Rasterstruktur sichtbar und gibt dem Bild einen mechanischen, retro anmutenden Charakter. In vielen Kontexten ist das eine bewusste ästhetische Entscheidung, denn das geordnete Muster einer 8×8-Bayer-Matrix hat einen eigenen Look, den viele Spiele und Künstler gezielt einsetzen.
Die Bayer-Matrix ist so konstruiert, dass jede Schwellenstufe neue Pixel an Positionen hinzufügt, die möglichst weit von den bereits vorhandenen entfernt liegen. Das verhindert Klumpenbildung und sorgt auf jeder Helligkeitsstufe für die gleichmäßigste Punkteverteilung. Außerdem ist das extrem schnell: ein Vergleich pro Pixel, keine Abhängigkeiten zu Nachbarn. Das macht es ideal für Echtzeit-Rendering.
Error Diffusion: Floyd-Steinberg und Co.
Error-Diffusion-Dithering geht einen völlig anderen Weg. Statt einer festen Schwellenwertmatrix werden Pixel nacheinander verarbeitet, und der 'Fehler' (die Differenz zwischen dem Originalwert des Pixels und dem quantisierten Ergebnis, also Schwarz oder Weiß) wird auf noch nicht bearbeitete Nachbarpixel verteilt.
def floyd_steinberg_dither(image):
"""Floyd-Steinberg error diffusion dithering."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
for y in range(h):
for x in range(w):
old_pixel = gray[y, x]
new_pixel = 255.0 if old_pixel > 128 else 0.0
gray[y, x] = new_pixel
error = old_pixel - new_pixel
# Distribute error to neighbors
# Floyd-Steinberg diffusion kernel:
# * 7/16
# 3/16 5/16 1/16
if x + 1 < w:
gray[y, x + 1] += error * 7 / 16
if y + 1 < h:
if x - 1 >= 0:
gray[y + 1, x - 1] += error * 3 / 16
gray[y + 1, x] += error * 5 / 16
if x + 1 < w:
gray[y + 1, x + 1] += error * 1 / 16
return Image.fromarray(gray.astype(np.uint8))
Floyd-Steinberg-Dithering liefert deutlich natürlicher wirkende Ergebnisse als Ordered Dithering. Die Pixelverteilung hat kein sichtbares Rastermuster, und sanfte Verläufe meistert es hervorragend. Die Punkte gruppieren sich organisch, was einem fotografischen Rasterdruck erstaunlich nahekommt.
Der Nachteil: Der Prozess ist sequenziell. Jeder Pixel hängt vom Fehler der zuvor verarbeiteten Pixel ab, deshalb lässt er sich nicht einfach parallelisieren. Das macht ihn langsamer als Ordered Dithering und schwerer in shaderbasierten Rendering-Pipelines umzusetzen. Für vorverarbeitete Bilder spielt das keine Rolle. Für 3D-Echtzeit-Rendering mit 60 fps ist es ein Problem.
Blue-Noise-Dithering: Das Beste aus beiden Welten
Blue-Noise-Dithering gilt bei vielen Anwendungen als aktueller Stand der Technik. Es nutzt wie Ordered Dithering eine Schwellenwertmatrix und ist daher schnell und parallelisierbar. Die Matrixwerte sind aber so angeordnet, dass eine Blue-Noise-Verteilung entsteht. Die Punkte liegen zufällig verteilt, aber mit einem minimalen Abstand zueinander. So werden sowohl die Klumpenbildung von White Noise als auch die Rastermuster des Ordered Dithering vermieden.
Der Begriff 'Blue Noise' stammt aus der Signalverarbeitung. Blue Noise hat den Großteil seiner Energie bei hohen Frequenzen, ähnlich wie blaues Licht. Räumlich betrachtet bedeutet das: Das Muster hat keine großskalige Struktur (kein sichtbares Raster, keine Richtungsabhängigkeit), die Punkte sind aber lokal gleichmäßig verteilt (keine Klumpen, keine Lücken). Es ist das Muster, das entsteht, wenn man Dartpfeile auf eine Scheibe wirft, mit der Einschränkung, dass jeder Pfeil einen gewissen Mindestabstand zu allen anderen einhalten muss.
Eine gute Blue-Noise-Schwellenwertmatrix zu erzeugen, ist überraschend schwierig. Es handelt sich um ein Optimierungsproblem, bei dem so lange iteriert werden muss, bis das Frequenzspektrum dem gewünschten Profil entspricht. Hat man die Matrix aber einmal, ist die Anwendung so schnell wie Bayer-Dithering: ein Vergleich pro Pixel.
Obra Dinns Ansatz: Sphärisch abgebildetes Dithering
Das Dithering in Return of the Obra Dinn ist besonders clever, weil es ein Problem löst, das nur beim 3D-Rendering auftritt: die zeitliche Stabilität. Wird ein Standard-Dithering-Muster im Bildschirmraum angewendet, beginnt es bei Kamerabewegungen zu 'schwimmen'. Es bleibt am Bildschirm fixiert, während sich die 3D-Geometrie darunter bewegt. Das erzeugt ein Flimmern, das extrem ablenkt.
Popes Lösung: Das Dithering-Muster wird im 3D-Raum statt im Bildschirmraum abgebildet. Das Muster wird auf eine Sphäre projiziert, die die Kamera umgibt, und für jeden Pixel anhand der Blickrichtung abgetastet. Dreht sich die Kamera, dreht sich das Muster mit. Bewegen sich Objekte im Weltraum, bleibt ihr Dithering stabil, weil es aus ihrer Weltposition abgeleitet wird und nicht aus ihrer Bildschirmposition.
Diese Technik lässt sich für jede 3D-Anwendung adaptieren, die Dithering oder Halftone-Effekte nutzt. Die Kernerkenntnis: Wenn du zeitliche Stabilität willst, sollten deine Schwellenwerte von etwas Stabilem abhängen (Weltposition, Blickrichtung) und nicht von etwas, das sich in jedem Frame ändert (Bildschirmkoordinaten).
Praktische Anwendungen jenseits der Retro-Ästhetik
Dithering ist nicht nur etwas für die Retro-Spielästhetik. Es hat auch handfeste praktische Anwendungen in der modernen Entwicklung.
- E-Ink-Displays. E-Reader und E-Ink-Schilder beherrschen nur eingeschränkt Graustufen (typischerweise 16 Stufen). Für die Darstellung von Fotos und Verläufen auf diesen Geräten ist Dithering unverzichtbar. Die Wahl des Algorithmus beeinflusst direkt Lesbarkeit und Bildqualität.
- Kleinere Dateigrößen. Ein gedithertes 1-Bit-Bild ist nur ein Achtel so groß wie ein 8-Bit-Graustufenbild. Für die Auslieferung dekorativer Bilder im Web können geditherte PNGs deutlich kleiner sein als Graustufen-Alternativen. Manche Webdesigner setzen geditherte Bilder bewusst aus ästhetischen und Performance-Gründen ein.
- Farbquantisierung. Wenn du ein Vollfarbbild auf eine begrenzte Palette reduzierst (GIF, indizierte PNGs oder palettenbeschränkte Displays), verhindert Dithering das Banding, das entsteht, wenn sanfte Verläufe auf diskrete Farbwerte abgebildet werden. Floyd-Steinberg-Dithering im Farbraum ist genau das, was GIF-Encoder dafür nutzen.
- Gradient-Banding beim Rendering. Render-Targets mit geringer Genauigkeit (8 Bit pro Kanal) können in sanften Verläufen sichtbares Banding erzeugen. Ein Screen-Space-Dithering (schon ±0,5 eines Wertes) bricht das Banding auf, ohne sichtbares Rauschen zu erzeugen. Die meisten modernen Game-Engines machen das automatisch.
- Thermo- und Belegdrucker. Thermodrucker arbeiten grundsätzlich mit 1 Bit, denn jeder Punkt wird entweder erhitzt (schwarz) oder nicht. Dithering wandelt Fotos und Grafiken in eine druckbare Form um. Der gewählte Algorithmus entscheidet, ob dein Kassenbonbild erkennbar bleibt oder zu einem Klecks wird.
Dithering in einem Shader implementieren
Für Echtzeitanwendungen lassen sich Ordered Dithering und Blue-Noise-Dithering effizient als Fragment-Shader umsetzen.
// GLSL fragment shader for Bayer 8x8 ordered dithering
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
// Bayer 8x8 matrix lookup via bit manipulation (no texture needed)
float bayer8x8(vec2 pos) {
ivec2 p = ivec2(mod(pos, 8.0));
int index = p.x + p.y * 8;
// Bit-reversal trick for Bayer matrix values
int value = 0;
int x = p.x ^ p.y;
int y = p.y;
for (int i = 0; i < 3; i++) {
value = value * 4 + (x & 1) * 2 + (y & 1);
x >>= 1;
y >>= 1;
}
return float(value) / 64.0;
}
void main() {
vec4 color = texture2D(uTexture, vTexCoord);
float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));
float threshold = bayer8x8(gl_FragCoord.xy);
float dithered = step(threshold, luminance);
gl_FragColor = vec4(vec3(dithered), 1.0);
}
Bei Blue Noise berechnest du in der Regel die Noise-Textur vorab und sampelst sie im Shader. Die Textur kann gekachelt werden, sodass eine 64×64- oder 128×128-Blue-Noise-Textur beim Wiederholen den gesamten Bildschirm abdeckt. Der Aufwand pro Pixel ist ein Textur-Sample und ein Vergleich, auf modernen GPUs praktisch kostenlos.
Den richtigen Algorithmus wählen
Jeder Dithering-Ansatz hat einen Anwendungsfall, in dem er die beste Wahl ist.
- Threshold: Text, Strichzeichnungen und kontrastreiche Grafiken, bei denen Details wichtiger sind als der Tonumfang.
- Ordered (Bayer): Echtzeit-Rendering, wenn du eine bewusste Retro- bzw. Halftone-Ästhetik willst. Schnell, parallelisierbar, GPU-freundlich.
- Floyd-Steinberg: Statische Bildverarbeitung, bei der die Qualität am wichtigsten ist. Am besten für Fotos und Bilder mit sanften Verläufen.
- Blue Noise: Echtzeit-Rendering, wenn du natürlich wirkende Ergebnisse ohne sichtbare Muster willst. Die beste universelle Wahl für moderne Anwendungen.
- Sphärisch/weltbasiert: 3D-Rendering, bei dem zeitliche Stabilität entscheidend ist. Unverzichtbar für 1-Bit-3D-Rendering wie bei Obra Dinn.
Dithering ist eines dieser Themen, bei denen das einfache Grundprinzip, Punkte so anzuordnen, dass sie wie Schattierungen aussehen, über die Tiefe der Implementierungsentscheidungen hinwegtäuscht. Jeder Algorithmus stellt einen anderen Kompromiss zwischen Qualität, Geschwindigkeit und visuellem Charakter dar. Und in einer Zeit, in der E-Ink-Displays, Verbindungen mit geringer Bandbreite und Retro-Ästhetik alle eine Rolle spielen, ist das Verständnis dieser Kompromisse praktischer denn je.


