| 1 | #version 300 es |
| 2 | // Retro CRT Shader - FULLY OPTIMIZED |
| 3 | // Fixes: Resolution-aware scanlines, proper vignette, edge clamping, |
| 4 | // correct aberration direction, anti-aliased scanlines |
| 5 | |
| 6 | precision highp float; |
| 7 | |
| 8 | in vec2 v_texcoord; |
| 9 | uniform sampler2D tex; |
| 10 | out vec4 fragColor; |
| 11 | |
| 12 | // --- CONFIGURATION --- |
| 13 | const float CURVATURE = 3.5; |
| 14 | const float SCANLINE_STRENGTH = 0.25; |
| 15 | // Approximate screen height in pixels (adjust to your resolution) |
| 16 | const float SCREEN_HEIGHT = 1080.0; |
| 17 | // Scanlines per screen height (lower = thicker lines) |
| 18 | const float SCANLINE_COUNT = 540.0; |
| 19 | const float ABERRATION = 0.002; |
| 20 | const float VIGNETTE_RADIUS = 1.00; |
| 21 | const float VIGNETTE_SOFTNESS = 0.45; |
| 22 | // Phosphor glow simulation |
| 23 | const float GLOW = 0.03; |
| 24 | |
| 25 | vec2 curveUV(vec2 uv) { |
| 26 | uv = uv * 2.0 - 1.0; |
| 27 | vec2 offset = abs(uv.yx) / vec2(CURVATURE); |
| 28 | uv = uv + uv * offset * offset; |
| 29 | uv = uv * 0.5 + 0.5; |
| 30 | return uv; |
| 31 | } |
| 32 | |
| 33 | void main() { |
| 34 | vec2 uv = curveUV(v_texcoord); |
| 35 | |
| 36 | // Black bezel for out-of-bounds |
| 37 | if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { |
| 38 | fragColor = vec4(0.0, 0.0, 0.0, 1.0); |
| 39 | return; |
| 40 | } |
| 41 | |
| 42 | // FIXED: Clamp aberration samples to prevent edge artifacts |
| 43 | vec2 centerDist = uv - 0.5; |
| 44 | vec2 aberrOffset = centerDist * ABERRATION; |
| 45 | |
| 46 | // FIXED: Correct direction - red focuses short, blue focuses long |
| 47 | float r = texture(tex, clamp(uv + aberrOffset, 0.0, 1.0)).r; |
| 48 | float g = texture(tex, uv).g; |
| 49 | float b = texture(tex, clamp(uv - aberrOffset, 0.0, 1.0)).b; |
| 50 | vec3 color = vec3(r, g, b); |
| 51 | |
| 52 | // FIXED: Anti-aliased scanlines that don't shimmer during scroll |
| 53 | // Using smooth triangle wave instead of harsh sine |
| 54 | float scanlinePhase = uv.y * SCANLINE_COUNT * 2.0; |
| 55 | float scanline = abs(fract(scanlinePhase) - 0.5) * 2.0; // Triangle wave 0-1 |
| 56 | scanline = smoothstep(0.0, 1.0, scanline); // Smooth it |
| 57 | float scanlineFactor = 1.0 - (SCANLINE_STRENGTH * (1.0 - scanline)); |
| 58 | color *= scanlineFactor; |
| 59 | |
| 60 | // Subtle phosphor glow (brightens slightly between scanlines) |
| 61 | color += GLOW * (1.0 - scanline); |
| 62 | |
| 63 | // FIXED: Proper circular vignette with correct smoothstep bounds |
| 64 | float dist = length(centerDist) * 2.0; // 0 at center, ~1.414 at corners |
| 65 | float vignette = 1.0 - smoothstep(VIGNETTE_RADIUS, VIGNETTE_RADIUS + VIGNETTE_SOFTNESS, dist); |
| 66 | color *= vignette; |
| 67 | |
| 68 | // Subtle brightness boost to compensate for darkening effects |
| 69 | color *= 1.1; |
| 70 | |
| 71 | fragColor = vec4(clamp(color, 0.0, 1.0), 1.0); |
| 72 | } |