// // CRT-Pi Fragment Shader - Metal Shading Language // Portado desde GLSL a MSL para macOS // #include using namespace metal; // Configuración (equivalente a los #define en GLSL) constant bool SCANLINES = true; constant bool MULTISAMPLE = true; constant bool GAMMA = true; constant bool FAKE_GAMMA = false; constant bool CURVATURE = false; constant bool SHARPER = false; constant int MASK_TYPE = 2; // 0=none, 1=green/magenta, 2=trinitron constant float CURVATURE_X = 0.05; constant float CURVATURE_Y = 0.1; constant float MASK_BRIGHTNESS = 0.80; constant float SCANLINE_WEIGHT = 6.0; constant float SCANLINE_GAP_BRIGHTNESS = 0.12; constant float BLOOM_FACTOR = 3.5; constant float INPUT_GAMMA = 2.4; constant float OUTPUT_GAMMA = 2.2; // Estructura de entrada (salida del vertex shader) struct VertexOut { float4 position [[position]]; float2 texCoord; float filterWidth; // float2 screenScale; // Solo si CURVATURE está activo }; // Uniforms struct Uniforms { float2 textureSize; }; // Función para calcular el peso de la scanline float CalcScanLineWeight(float dist) { return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS); } // Función para calcular scanline con multisampling float CalcScanLine(float dy, float filterWidth) { float scanLineWeight = CalcScanLineWeight(dy); if (MULTISAMPLE) { scanLineWeight += CalcScanLineWeight(dy - filterWidth); scanLineWeight += CalcScanLineWeight(dy + filterWidth); scanLineWeight *= 0.3333333; } return scanLineWeight; } // Entry point del fragment shader fragment float4 fragment_main(VertexOut in [[stage_in]], texture2d colorTexture [[texture(0)]], constant Uniforms& uniforms [[buffer(1)]], sampler textureSampler [[sampler(0)]]) { float2 texcoord = in.texCoord; // Si CURVATURE estuviera activo, aquí iría la función Distort() float2 texcoordInPixels = texcoord * uniforms.textureSize; float2 tc; float scanLineWeight; if (!SHARPER) { // Modo normal (no SHARPER) float tempY = floor(texcoordInPixels.y) + 0.5; float yCoord = tempY / uniforms.textureSize.y; float dy = texcoordInPixels.y - tempY; scanLineWeight = CalcScanLine(dy, in.filterWidth); float signY = sign(dy); dy = dy * dy; dy = dy * dy; dy *= 8.0; dy /= uniforms.textureSize.y; dy *= signY; tc = float2(texcoord.x, yCoord + dy); } else { // Modo SHARPER float2 tempCoord = floor(texcoordInPixels) + 0.5; float2 coord = tempCoord / uniforms.textureSize; float2 deltas = texcoordInPixels - tempCoord; scanLineWeight = CalcScanLine(deltas.y, in.filterWidth); float2 signs = sign(deltas); deltas.x *= 2.0; deltas = deltas * deltas; deltas.y = deltas.y * deltas.y; deltas.x *= 0.5; deltas.y *= 8.0; deltas /= uniforms.textureSize; deltas *= signs; tc = coord + deltas; } // Muestrear textura (texture() en GLSL = sample() en MSL) float3 colour = colorTexture.sample(textureSampler, tc).rgb; if (SCANLINES) { if (GAMMA) { if (FAKE_GAMMA) { colour = colour * colour; } else { colour = pow(colour, float3(INPUT_GAMMA)); } } scanLineWeight *= BLOOM_FACTOR; colour *= scanLineWeight; if (GAMMA) { if (FAKE_GAMMA) { colour = sqrt(colour); } else { colour = pow(colour, float3(1.0 / OUTPUT_GAMMA)); } } } // Aplicar máscara CRT if (MASK_TYPE == 0) { return float4(colour, 1.0); } else if (MASK_TYPE == 1) { // Máscara verde/magenta float whichMask = fract(in.position.x * 0.5); float3 mask; if (whichMask < 0.5) { mask = float3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS); } else { mask = float3(1.0, MASK_BRIGHTNESS, 1.0); } return float4(colour * mask, 1.0); } else if (MASK_TYPE == 2) { // Máscara trinitron float whichMask = fract(in.position.x * 0.3333333); float3 mask = float3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS); if (whichMask < 0.3333333) { mask.x = 1.0; } else if (whichMask < 0.6666666) { mask.y = 1.0; } else { mask.z = 1.0; } return float4(colour * mask, 1.0); } return float4(colour, 1.0); }