CRT Monitor / Retro TV Effect
Wrote a CRT shader that I'm pretty happy with. It does scanlines, screen curvature, vignette, and a bit of chromatic aberration to sell the old-TV look. I'm using it as a post-process pass for a pixel art game and it adds a lot of character without being too heavy.
The barrel distortion is the part that took the most tuning. Too much and it looks like a fisheye lens, too little and you can't tell it's curved. The 0.15 value I landed on feels right for a 4:3 aspect ratio CRT.
precision mediump float;
uniform vec2 iResolution;
uniform float iTime;
// Barrel distortion to simulate curved CRT glass
vec2 crtCurve(vec2 uv, float amount) {
uv = uv * 2.0 - 1.0;
vec2 offset = abs(uv.yx) / vec2(6.0, 4.0);
uv = uv + uv * offset * offset * amount;
uv = uv * 0.5 + 0.5;
return uv;
}
void main() {
vec2 uv = gl_FragCoord.xy / iResolution.xy;
// Apply barrel distortion
vec2 crtUV = crtCurve(uv, 0.15);
// Kill pixels outside the curved screen area
if (crtUV.x < 0.0 || crtUV.x > 1.0 || crtUV.y < 0.0 || crtUV.y > 1.0) {
gl_FragColor = vec4(0.0);
return;
}
// Chromatic aberration: offset R and B channels slightly
float aberration = 0.003;
float r = 0.5 + 0.5 * sin(crtUV.x * 12.0 + iTime * 0.7);
float g = 0.5 + 0.5 * sin(crtUV.y * 10.0 - iTime * 0.5);
float b = 0.5 + 0.5 * sin((crtUV.x + crtUV.y) * 8.0 + iTime * 1.1);
// Shift channels for chromatic split
vec2 uvR = crtCurve(uv + vec2(aberration, 0.0), 0.15);
vec2 uvB = crtCurve(uv - vec2(aberration, 0.0), 0.15);
float rr = 0.5 + 0.5 * sin(uvR.x * 12.0 + iTime * 0.7);
float bb = 0.5 + 0.5 * sin((uvB.x + uvB.y) * 8.0 + iTime * 1.1);
vec3 col = vec3(rr, g, bb);
// Scanlines
float scanline = sin(crtUV.y * iResolution.y * 1.5) * 0.5 + 0.5;
scanline = pow(scanline, 1.8) * 0.3 + 0.7;
col *= scanline;
// Horizontal sync wobble (subtle)
float flicker = 1.0 + 0.015 * sin(iTime * 60.0 + crtUV.y * 40.0);
col *= flicker;
// Vignette darkening at edges
vec2 vig = crtUV * (1.0 - crtUV);
float vigAmount = vig.x * vig.y * 15.0;
vigAmount = pow(vigAmount, 0.25);
col *= vigAmount;
// Phosphor glow: faint RGB subpixel pattern
float subpx = mod(gl_FragCoord.x, 3.0);
vec3 phosphor = vec3(
step(0.5, subpx) * step(subpx, 1.5),
step(1.5, subpx) * step(subpx, 2.5),
step(subpx, 0.5) + step(2.5, subpx)
);
col = mix(col, col * phosphor * 1.5, 0.08);
// Slight warm tint like an old monitor
col *= vec3(1.05, 1.0, 0.92);
gl_FragColor = vec4(col, 1.0);
}The phosphor subpixel effect is optional but if you zoom in it gives you that RGB stripe pattern you see on old CRT screens. On a pixel art game running at low res it's a nice touch.
One thing I haven't figured out is how to do the rolling horizontal bar artifact that old TVs get when the v-hold is drifting. Anyone tried that?