Skip to content

crtFragment

const crtFragment: “const vec2 curve = vec2(0.115, 0.165);\nconst float scanlineCount = 240.0;\nconst float scanlineDepth = 0.30;\nconst float triadCount = 200.0;\nconst float grille = 0.34;\nconst float bar = 0.045;\nconst float flickerDepth = 0.028;\nconst float grain = 0.022;\nconst float vignette = 0.58;\nconst float gain = 1.34;\nconst float halo = 0.10;\nconst vec3 sheen = vec3(0.55, 1.0, 0.78);\nconst vec3 room = vec3(0.012, 0.03, 0.022);\n\nfloat hash(vec2 p) {\n p = fract(p * vec2(123.34, 456.21));\n p += dot(p, p + 45.32);\n return fract(p.x * p.y);\n}\n\n// Bends the flat UVs as curved glass does: the corners pull inward.\nvec2 bend(vec2 uv) {\n uv = uv * 2.0 - 1.0;\n vec2 squared = uv.yx * uv.yx;\n uv += uv * squared * curve * uCurvature;\n return uv * 0.5 + 0.5;\n}\n\nvoid main() {\n vec2 uv = bend(vUv);\n float t = uTime;\n\n // Inside the bent picture, with a soft edge; outside is the room’s dark.\n vec2 inBounds = step(vec2(0.0), uv) * step(uv, vec2(1.0));\n float inside = inBounds.x * inBounds.y;\n vec2 edge = min(uv, 1.0 - uv);\n inside *= smoothstep(0.0, 0.020, min(edge.x, edge.y));\n\n // Colour fringing: red and blue sampled a little off, more toward the edge.\n vec2 direction = uv - 0.5;\n vec2 offset = direction * (0.0010 + 0.0075 * dot(direction, direction));\n vec3 colour;\n colour.r = texture2D(uMap, uv + offset).r;\n colour.g = texture2D(uMap, uv).g;\n colour.b = texture2D(uMap, uv - offset).b;\n\n // Halation: six wide taps, so bright glyphs bloom into the glass.\n float spread = 0.0038;\n vec3 wide = texture2D(uMap, uv + vec2(spread, 0.0)).rgb\n + texture2D(uMap, uv + vec2(-spread, 0.0)).rgb\n + texture2D(uMap, uv + vec2(0.0, spread)).rgb\n + texture2D(uMap, uv + vec2(0.0, -spread)).rgb\n + texture2D(uMap, uv + vec2(spread, spread) * 0.72).rgb\n + texture2D(uMap, uv + vec2(-spread, -spread) * 0.72).rgb;\n colour += wide * (halo / 6.0);\n\n // Scanlines: dark bands that drift with time.\n float line = sin(uv.y * 3.14159265 * scanlineCount + t * 4.0);\n colour *= mix(1.0 - scanlineDepth * uScanlines, 1.0, line * line);\n\n // Phosphor grille: red, green and blue stripes across the width.\n float stripe = vUv.x * 6.2831853 * triadCount;\n colour *= (1.0 - grille) + grille * cos(stripe + vec3(0.0, 2.094, 4.188));\n colour *= gain;\n\n // A bright bar rolls up the screen.\n float roll = fract(uv.y * 0.5 - t * 0.07);\n roll = smoothstep(0.0, 0.05, roll) * (1.0 - smoothstep(0.05, 0.18, roll));\n colour += roll * bar;\n\n // A soft reflection low on the glass.\n colour += (1.0 - smoothstep(0.0, 0.55, distance(uv, vec2(0.50, 0.15)))) * 0.030 * sheen;\n\n // Darker corners, a flicker and film grain.\n float shade = 1.0 - smoothstep(0.30, 0.98, length((uv - 0.5) * vec2(1.05, 1.0)));\n colour *= mix(1.0 - vignette, 1.0, shade);\n colour *= 1.0 - flickerDepth * uFlicker * sin(t * 8.0);\n colour += (hash(vUv + fract(t * 0.37)) - 0.5) * grain;\n\n // The room’s colour around the picture, lit by the glass’s spill.\n float spill = (1.0 - smoothstep(0.18, 0.85, length(vUv - 0.5))) * 0.05;\n colour = mix(room + sheen * spill * 0.42, colour, inside);\n colour = max(colour, room * 0.34);\n gl_FragColor = vec4(colour, 1.0);\n}\n”

Defined in: packages/engine/src/render/crt.ts:10

The CRT screen, for a Shader: uMap is the picture, uCurvature bends the glass (0 flat, 1 the terminal look), uScanlines darkens the lines (0 none, 1 the terminal look) and uFlicker scales the flicker.