#version 330 core // Configuración #define SCANLINES #define MULTISAMPLE #define SCANLINE_WEIGHT 6.0 #define SCANLINE_GAP_BRIGHTNESS 0.12 #define BLOOM_FACTOR 3.5 // Inputs desde vertex shader in vec2 vTexCoord; in float vFilterWidth; // Output out vec4 FragColor; // Uniforms existentes uniform sampler2D Texture; uniform vec2 TextureSize; uniform float uVignette; // 0 = sin viñeta, 1 = máxima uniform float uScanlines; // 0 = desactivadas, 1 = plenas uniform float uChroma; // 0 = sin aberración, 1 = máxima uniform float uOutputHeight; // altura del viewport en pixels de pantalla // Nuevos uniforms uniform float uMask; // 0 = sin máscara de fósforo, 1 = máxima uniform float uGamma; // 0 = sin corrección gamma, 1 = gamma 2.4/2.2 plena uniform float uCurvature; // 0 = plana, 1 = curvatura barrel máxima uniform float uBleeding; // 0 = sin sangrado NTSC, 1 = máximo // Conversores YCbCr para efecto NTSC vec3 rgb_to_ycc(vec3 rgb) { return vec3( 0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b, -0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5, 0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5 ); } vec3 ycc_to_rgb(vec3 ycc) { float y = ycc.x; float cb = ycc.y - 0.5; float cr = ycc.z - 0.5; return clamp(vec3( y + 1.402*cr, y - 0.344*cb - 0.714*cr, y + 1.772*cb ), 0.0, 1.0); } float CalcScanLineWeight(float dist) { return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS); } void main() { vec2 texcoord = vTexCoord; // Curvatura barrel CRT (distorsión en UV antes de muestrear) if (uCurvature > 0.0) { vec2 c = texcoord - 0.5; float rsq = dot(c, c); vec2 dist = vec2(0.05, 0.1) * uCurvature; vec2 barrelScale = 1.0 - 0.23 * dist; c += c * (dist * rsq); c *= barrelScale; if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) { FragColor = vec4(0.0); return; } texcoord = c + 0.5; } vec2 texcoordInPixels = texcoord * TextureSize; float tempY = floor(texcoordInPixels.y) + 0.5; float yCoord = tempY / TextureSize.y; // Scanline en espacio de pantalla (subpíxel) float scaleY = uOutputHeight / TextureSize.y; float screenY = texcoord.y * uOutputHeight; float posInRow = mod(screenY, scaleY); float scanLineDY = posInRow / scaleY - 0.5; float localFilterWidth = 1.0 / scaleY; float scanLineWeight = CalcScanLineWeight(scanLineDY); scanLineWeight += CalcScanLineWeight(scanLineDY - localFilterWidth); scanLineWeight += CalcScanLineWeight(scanLineDY + localFilterWidth); scanLineWeight *= 0.3333333; // Phosphor blur en espacio textura float dy = texcoordInPixels.y - tempY; float signY = sign(dy); dy = dy * dy; dy = dy * dy; dy *= 8.0; dy /= TextureSize.y; dy *= signY; vec2 tc = vec2(texcoord.x, yCoord + dy); // Muestra base en centro vec3 base = texture(Texture, tc).rgb; // Sangrado NTSC — difuminado horizontal de crominancia vec3 colour; if (uBleeding > 0.0) { float tw = TextureSize.x; vec3 ycc = rgb_to_ycc(base); vec3 ycc_l2 = rgb_to_ycc(texture(Texture, tc - vec2(2.0/tw, 0.0)).rgb); vec3 ycc_l1 = rgb_to_ycc(texture(Texture, tc - vec2(1.0/tw, 0.0)).rgb); vec3 ycc_r1 = rgb_to_ycc(texture(Texture, tc + vec2(1.0/tw, 0.0)).rgb); vec3 ycc_r2 = rgb_to_ycc(texture(Texture, tc + vec2(2.0/tw, 0.0)).rgb); ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0; colour = mix(base, ycc_to_rgb(ycc), uBleeding); } else { colour = base; } // Aberración cromática float ca = uChroma * 0.005; colour.r = texture(Texture, tc + vec2(ca, 0.0)).r; colour.b = texture(Texture, tc - vec2(ca, 0.0)).b; // Corrección gamma (linealizar antes de scanlines, codificar después) if (uGamma > 0.0) { vec3 lin = pow(colour, vec3(2.4)); colour = mix(colour, lin, uGamma); } // Scanlines scanLineWeight *= BLOOM_FACTOR; colour *= mix(1.0, scanLineWeight, uScanlines); if (uGamma > 0.0) { vec3 enc = pow(colour, vec3(1.0 / 2.2)); colour = mix(colour, enc, uGamma); } // Viñeta if (uVignette > 0.0) { vec2 uv = texcoord - vec2(0.5); float vig = 1.0 - dot(uv, uv) * uVignette * 4.0; colour *= clamp(vig, 0.0, 1.0); } // Máscara de fósforo RGB if (uMask > 0.0) { float whichMask = fract(gl_FragCoord.x * 0.3333333); vec3 mask = vec3(0.80); if (whichMask < 0.3333333) mask.x = 1.0; else if (whichMask < 0.6666666) mask.y = 1.0; else mask.z = 1.0; colour = mix(colour, colour * mask, uMask); } FragColor = vec4(colour, 1.0); }