Home

Recreating the Game Boy Camera

Project:
Farsight
Year:
2026
Type:
Post-process shader
Tech:
Unity URP, HLSL
Player view next to the Game Boy camera view
The normal view on the left, the Game Boy camera on the right.

The goal

You carry a Game Boy with a camera and take photos with it throughout the game. We wanted the viewfinder and photos to look like they actually came out of one: tiny, low-res, a few shades of grey, with that dithered look.

It also had to work with our lighting. Dark rooms shouldn't go fully black, and bright windows shouldn't blow everything out to white.

How it works

The Game Boy has its own camera that renders into a small 384 by 384 texture. Right after it renders, a shader rewrites that texture. The viewfinder and the saved photos both use the same texture, so they both get the look for free.

1. Tone

First the image gets softened a little, so jaggy edges don't turn into noise once it's dithered. Then it's turned into brightness only. Lights and windows get toned down, exposure is applied, and bright areas ease off towards white instead of clipping.

Source view and its greyscale tone
The source view, then the tone-mapped brightness.

2. Dither

If you just round to five shades you get ugly bands. So before rounding, each pixel gets compared against an 8 by 8 Bayer pattern. Pixels next to each other get different thresholds, so in-between shades turn into a checker of two shades, which your eye reads as a smooth gradient.

The 8 by 8 Bayer threshold matrix
The 8x8 Bayer matrix: 64 threshold levels.
float bayer = (BAYER[(px.y & 7u) * 8u + (px.x & 7u)] + 0.5) / 64.0;
float q = saturate(floor(lum * 4.0 + bayer) / 4.0);
Five shades without dithering, then with Bayer dithering
Five shades without dithering, then with it.

3. Palette

The five shades map to five colours on the material, so we can tweak the look without touching code. Some story moments swap the palette, and they do it on a copy so the original material stays as is.

The five-shade grey palette

The step images were rendered with a Python port of the shader. The clip at the top is straight from the game.