CRT Shaders and Integer Scaling: Making Old Pixels Look Right on New Screens
A 256x224 image was never meant to be seen as a grid of hard, discrete pixels. Recreating how it actually looked on the display it was designed for is its own genuine technical problem.
Classic consoles output very low native resolutions by today’s standards — 256×224 was typical for 16-bit-era systems, against a modern 4K display’s 3840×2160. Simply stretching that image to fill a modern screen raises a question with more than one reasonable answer: what should the space between the original pixels actually look like?
The naive approach: bilinear stretching
The default behavior of most image and video scaling is bilinear interpolation — blending neighboring source pixels smoothly to fill in a larger destination image. Applied to pixel art, this reliably produces a blurry, smeared result, because pixel art was authored with hard, deliberate edges between adjacent pixels that bilinear blending directly erases.
Integer scaling: crisp, but not authentic
Integer scaling fixes the blur by restricting magnification to exact whole-number multiples (2x, 3x, 4x…) and using nearest-neighbor sampling — every source pixel becomes a perfectly crisp NxN block of identical-colored pixels, with no blending at all:
Source (1 pixel): Bilinear 4x: Integer (nearest) 4x:
■ ▓▓▒▒ ■■■■
▒▒░░ ■■■■
■■■■
■■■■
This is a popular, simple default, and a real improvement over blur — but it isn’t actually how these games were designed to be seen, either. It shows the raw pixel grid with perfect fidelity, which is not the same thing as showing the image the original artists were looking at while they made it.
What a CRT actually did to the image
Consoles of this era were designed and tested exclusively on CRT displays, which don’t render a sharp grid of discrete pixels at all. A CRT’s electron beam sweeps continuously across each scanline, and its output passes through a shadow mask or aperture grille — a physical grid of tiny holes or slits that separates red, green, and blue phosphor sub-pixels — producing visible scanline gaps between lines and genuine color blending between horizontally adjacent source pixels, along with a soft glow rather than a hard edge. Critically, artists at the time knew this and sometimes designed around it deliberately — using adjacent-pixel dithering patterns specifically because a CRT’s natural blending would merge them into an intermediate shade or a smoother gradient the console’s actual color palette couldn’t represent directly.
What a CRT shader actually simulates
A CRT shader is a real-time graphics shader that recreates these specific physical effects on a modern flat panel: darkened or fully black scanline gaps at regular intervals, a subtle horizontal blur approximating shadow-mask blending, sometimes a mild barrel-distortion curvature and vignette matching a physical tube’s geometry, and phosphor glow bleeding slightly beyond each lit pixel’s edges:
scanline_brightness(y) = base_color * (1.0 - scanline_intensity * (y % 2))
horizontal_blend(x) = mix(pixel[x], pixel[x+1], mask_blend_factor)
Done well, this doesn’t just add a nostalgic filter — it reconstructs the intended visual result the original artists were designing against and tuning their palettes and dithering for, which a bare, sharp pixel grid genuinely does not show.
Why there’s no single “correct” choice
Integer scaling and CRT shading answer different questions: integer scaling shows you exactly what data the console produced, pixel for pixel; a CRT shader shows you what a period-accurate display would have done with that same data. Neither is “wrong” — they’re reconstructing two different, both-legitimate things, and most modern frontends (RetroArch prominent among them) expose both as configurable options rather than picking one as the default for every game and every player’s taste.