#include // Output surface size in pixels. uniform vec2 uResolution; // One source-texel step in UV space: (1/width, 1/height). uniform vec2 uTexel; // 1.0 enables CRT post-process effects, 0.0 keeps only base AA. uniform float uEffectsEnabled; // Engine time in seconds used to animate scanline travel. uniform float uTime; // Source frame produced by the software renderer. uniform sampler2D uTexture; out vec4 fragColor; // Perceptual brightness approximation used for edge detection. float luma(vec3 color) { return dot(color, vec3(0.299, 0.587, 0.114)); } void main() { // Convert fragment coordinates to normalized UV coordinates. vec2 uv = FlutterFragCoord().xy / uResolution; if (uEffectsEnabled > 0.5) { // Barrel-like warp to emulate curved CRT glass. vec2 centered = uv * 2.0 - 1.0; float radius2 = dot(centered, centered); centered *= 1.0 + radius2 * 0.045; uv = centered * 0.5 + 0.5; // Fill outside warped bounds with a subtle gray plastic TV bezel. if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { vec2 clampedUv = clamp(uv, 0.0, 1.0); vec2 edgeDelta = abs(uv - clampedUv); float overflow = max(edgeDelta.x, edgeDelta.y); float verticalShade = 0.96 + 0.06 * (1.0 - (FlutterFragCoord().y / uResolution.y)); float depthShade = 1.0 - smoothstep(0.0, 0.06, overflow) * 0.18; float grain = sin(FlutterFragCoord().x * 0.21 + FlutterFragCoord().y * 0.11) * 0.01; vec3 bezelColor = vec3(0.46, 0.46, 0.44) * verticalShade * depthShade + vec3(grain); fragColor = vec4(bezelColor, 1.0); return; } } // Read the base color from the source frame. vec4 centerSample = texture(uTexture, uv); // Sample 4-neighborhood (N/S/E/W) around the current pixel. vec3 sampleN = texture(uTexture, uv + vec2(0.0, -uTexel.y)).rgb; vec3 sampleS = texture(uTexture, uv + vec2(0.0, uTexel.y)).rgb; vec3 sampleE = texture(uTexture, uv + vec2(uTexel.x, 0.0)).rgb; vec3 sampleW = texture(uTexture, uv + vec2(-uTexel.x, 0.0)).rgb; // Compute local luma range; wider range means a stronger edge. float lumaCenter = luma(centerSample.rgb); float lumaMin = min( lumaCenter, min(min(luma(sampleN), luma(sampleS)), min(luma(sampleE), luma(sampleW))) ); float lumaMax = max( lumaCenter, max(max(luma(sampleN), luma(sampleS)), max(luma(sampleE), luma(sampleW))) ); float edgeSpan = max(lumaMax - lumaMin, 0.0001); // Convert raw edge strength into a smooth 0..1 blending amount. float edgeAmount = smoothstep(0.03, 0.18, edgeSpan); // Average neighbors and blend toward that average only near edges. // This acts like a lightweight edge-aware anti-aliasing pass. vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25; vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45); // Preserve source alpha and output the anti-aliased color. vec3 outColor = aaColor; if (uEffectsEnabled > 0.5) { // Horizontal scanline modulation. float scanlineBand = 0.88 + 0.12 * sin(uv.y * uResolution.y * 3.14159265); // Slow bright line crawling down the screen. float sweepPos = fract(uTime * 0.08); float sweepBand = 1.0 + 0.16 * exp(-pow((uv.y - sweepPos) * 120.0, 2.0)); // Slight center brightening and edge falloff (CRT phosphor + lens feel). vec2 centeredUv = uv * 2.0 - 1.0; float vignette = smoothstep(1.15, 0.25, length(centeredUv)); float centerLift = 1.0 + 0.08 * (1.0 - length(centeredUv)); outColor *= scanlineBand * sweepBand * centerLift; outColor *= mix(0.62, 1.0, vignette); } fragColor = vec4(outColor, centerSample.a); }