The Return of the Obra Dinn Look, Reverse-Engineered With Settings

LOOK CHASINGDESIGNER

Obra Dinn's look is ordered dithering, not Floyd-Steinberg — it doesn't shimmer on a moving camera. The 8x8 Bayer matrix, threshold settings, and failure modes.

01/ ARTICLE

What is the Obra Dinn look, exactly?

The Obra Dinn look is a 1-bit, black-and-white ordered-dither rendering style, built by Lucas Pope for Return of the Obra Dinn to mimic early Macintosh graphics. Every pixel is fully black or fully white — no gray values at all — and shading, depth, and gradients are all faked through the density of that black-and-white pattern.

The build order was backward from how most games get made: Pope built the 1-bit rendering engine first, then designed the ship-mystery gameplay to fit it, rather than picking a visual style to skin an existing game. That's part of why the look holds up under scrutiny — it wasn't a filter applied late, it was the foundation the whole game sat on.

Designers keep coming back to reproduce it because it does something few visual styles manage: it reads instantly as "old Mac" at any size, including a thumbnail, while still holding enough detail to feel like a real rendered scene rather than a flat graphic.

What dithering algorithm does Obra Dinn actually use?

Ordered dithering vs. error diffusion, and why the game needed the former

Obra Dinn's environments use ordered dithering — specifically a Bayer-matrix approach, not Floyd-Steinberg or any other error-diffusion method. The distinction matters more than it sounds like it should.

Ordered dithering compares each pixel's brightness against a fixed threshold value pulled from a matrix, indexed purely by that pixel's screen position. Neighboring pixels don't affect each other at all. Error-diffusion dithering works differently: each pixel's rounding error gets pushed forward into pixels that haven't been processed yet, which is what gives Floyd-Steinberg its organic, non-repeating texture on a still image.

That feedback loop is exactly the problem in a first-person 3D game. Error diffusion depends on scan order and on what came before it in that specific frame. Move the camera even one pixel, and the same piece of geometry dithers slightly differently. Frame to frame, the pattern "swims" — a visible crawl across static surfaces that reads as noise rather than texture. Ordered dithering doesn't have this problem: since each pixel's outcome depends only on its fixed screen position, the same wall dithers the same way in every frame regardless of camera movement. See Floyd-Steinberg vs ordered dithering for the full algorithm-level breakdown, including why Floyd-Steinberg specifically distributes error across four neighboring pixels.

Brent Werness's face shader, the part Lucas Pope didn't build himself

The environment dither and the character-face dither in Obra Dinn are not the same shader. Character faces use a dedicated dithering shader built by forum user Brent Werness, handle Koloth, shared as a Dropbox link on a TIGSource devlog thread and later mirrored to GitHub so the code wouldn't disappear along with the original link.

The gap worth flagging: the archived repository preserves the shader code, but not a written explanation of its exact threshold math. It ships as a Processing sketch rather than a documented library, so reproducing the face-specific look means reading the shader source directly, or working from a third-party reimplementation that describes the same threshold-comparison approach. No clean writeup exists to shortcut that.

What matrix size and threshold settings reproduce it?

The starting point is the standard 8x8 Bayer threshold matrix, 64 discrete values arranged in a recursive, non-sequential order specifically chosen to avoid low-frequency banding:

 0 32  8 40  2 34 10 42
48 16 56 24 50 18 58 26
12 44  4 36 14 46  6 38
60 28 52 20 62 30 54 22
 3 35 11 43  1 33  9 41
51 19 59 27 49 17 57 25
15 47  7 39 13 45  5 37
63 31 55 23 61 29 53 21

Applying it: scale the pixel's brightness value into the 0-63 range, then compare it against the matrix cell at that pixel's (x mod 8, y mod 8) screen position. If the brightness is below the matrix value at that cell, the pixel goes black; otherwise it goes white.

Practical settings for reproducing the look in kott: matrix size 8x8, palette locked to pure black and white with no gray steps, and a small contrast boost (roughly +10%) applied to the source before the dither pass. Skip that contrast step and mid-tone areas tend to come out muddy and gray-reading even though every pixel is technically pure black or white — the dither pattern needs enough contrast in the source to separate cleanly. These are a starting point, not a fixed law; test against your own source and adjust from there.

Ordered dithering vs. error diffusion for real-time scenes

Ordered (Bayer)Error diffusion (Floyd-Steinberg)
Pattern stability in motionFixed to screen position, stays stable frame to frameDepends on scan order; shifts and shimmers as the camera moves
Per-pixel independenceFully independentDepends on neighboring pixels' rounding error
Visual characterStructured cross-hatch patternOrganic, non-repeating texture
Best use caseReal-time 3D, animation, anything that movesStatic images, print, anything that doesn't move frame to frame
Matches the Obra Dinn lookYes — this is what the game's environment shader usesNo — visually close on a single still, wrong once the camera moves

For anything that moves, ordered wins outright. On a single static image, though, Floyd-Steinberg often looks better up close — its organic texture avoids the visible cross-hatch structure a Bayer matrix leaves behind.

What breaks the look? Common failure modes

Using error diffusion on animated or 3D content. This is the mistake the game's own engine was built to avoid: apply Floyd-Steinberg per-frame to a moving camera and the dither pattern shimmers and crawls across static geometry, because the diffusion depends on scan order and every frame starts that calculation over.

Wrong matrix size. A 16x16 Bayer matrix has 256 threshold levels instead of 64, which reads as smoother and closer to photographic gradation rather than the game's chunky, high-contrast pattern. Go too small in the other direction (2x2 or 4x4) and the result reads as noise rather than a deliberate texture. This is also where people reach for the wrong algorithm entirely — Atkinson dithering is a different error-diffusion variant, tuned for a lighter, higher-contrast result than Floyd-Steinberg, but it's still error diffusion under the hood and inherits the same motion-shimmer problem on a moving camera.

Skipping the motion-blur pass. Pope's own account is a useful data point here: the 1-bit rendering worked fine in a windowed view, but at full-screen resolution players reported motion sickness, and the fix wasn't abandoning the dither — it was adding the equivalent of motion blur to the rendering routine. A "pure" dither-only render, mathematically correct matrix and all, can still feel wrong at speed without that smoothing pass.

Reaching for a CRT scanline pass. Pope reportedly considered a CRT-style render effect and dropped it. Adding scanlines and phosphor glow on top of the dither pulls the look toward generic retro-CRT rather than the specific 1-bit Mac aesthetic Obra Dinn is known for — the two read as different eras of hardware.

Frequently asked questions

Is Obra Dinn's dither style just a Bayer matrix?

Mostly, yes, for the environment — but the character faces use a separate shader Brent Werness (Koloth) built specifically for the game, shared on the TIGSource forums and later archived on GitHub. The two aren't identical: the face shader was tuned by hand for skin and expression readability at 1-bit, while the environment uses a more standard ordered-dither pass. If you're chasing the look for a full scene, start with an 8x8 Bayer matrix and expect faces to need extra threshold tuning.

Why does ordered dithering look more like Obra Dinn than Floyd-Steinberg?

Because Floyd-Steinberg is error diffusion — each pixel's rounding error gets pushed into pixels that haven't been processed yet, which looks fine on a still image but swims frame to frame once the camera moves, since the diffusion pattern depends on scan order. Ordered dithering compares each pixel only to a fixed matrix value at that screen position, so the same pixel gets the same treatment every frame regardless of what's around it. No shimmer, which is exactly what a first-person 3D game needs.

Should I use an 8x8 or 16x16 Bayer matrix to match the game?

8x8. It has 64 threshold levels instead of 256, which reads as a chunkier, higher-contrast dot pattern at low render resolution, closer to what shipped in Obra Dinn than the smoother, denser 16x16 table. If your reproduction looks too smooth or too grey, that's usually the matrix size, not the threshold.

Do I need special software to reproduce this, or can I do it live?

Either. The shader approach (GLSL or Processing, using the threshold-comparison method above) works in a live/real-time pipeline, which is how the game renders it every frame. For a single image, any tool that supports ordered dithering with a selectable matrix size gets you most of the way. kott's dither effect exposes matrix size and threshold directly, so you can preview the exact settings before committing.

Try the 8x8 Bayer settings live in kott, preloaded to matrix size 8x8, pure black-and-white palette, and the contrast boost described above.

02/ OUT

Try it in the studio

Every setting described above is a real control. Open your own image and sweep it.

All journal entries