#version 450 // Vulkan GLSL fragment shader — CRT-Pi PostFX // Algoritmo de scanlines continuas con pesos gaussianos, bloom y máscara de fósforo. // Basado en el shader CRT-Pi original (GLSL 3.3), portado a GLSL 4.50 con parámetros uniformes. // // Compile: glslc -fshader-stage=frag --target-env=vulkan1.0 crtpi_frag.glsl -o crtpi_frag.spv // xxd -i crtpi_frag.spv > ../../source/core/rendering/sdl3gpu/crtpi_frag_spv.h layout(location = 0) in vec2 v_uv; layout(location = 0) out vec4 out_color; layout(set = 2, binding = 0) uniform sampler2D Texture; layout(set = 3, binding = 0) uniform CrtPiBlock { // vec4 #0 float scanline_weight; // Ajuste gaussiano de scanlines (default 6.0) float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12) float bloom_factor; // Factor de brillo en zonas iluminadas (default 3.5) float input_gamma; // Gamma de entrada — linealización (default 2.4) // vec4 #1 float output_gamma; // Gamma de salida — codificación (default 2.2) float mask_brightness; // Brillo sub-píxeles de la máscara (default 0.80) float curvature_x; // Distorsión barrel eje X (default 0.05) float curvature_y; // Distorsión barrel eje Y (default 0.10) // vec4 #2 int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo int enable_scanlines; // 0 = off, 1 = on int enable_multisample; // 0 = off, 1 = on (antialiasing analítico de scanlines) int enable_gamma; // 0 = off, 1 = on // vec4 #3 int enable_curvature; // 0 = off, 1 = on int enable_sharper; // 0 = off, 1 = on float texture_width; // Ancho del canvas lógico en píxeles float texture_height; // Alto del canvas lógico en píxeles } u; // Distorsión barrel CRT vec2 distort(vec2 coord, vec2 screen_scale) { vec2 curvature = vec2(u.curvature_x, u.curvature_y); vec2 barrel_scale = 1.0 - (0.23 * curvature); coord *= screen_scale; coord -= vec2(0.5); float rsq = coord.x * coord.x + coord.y * coord.y; coord += coord * (curvature * rsq); coord *= barrel_scale; if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5) { return vec2(-1.0); // fuera de pantalla } coord += vec2(0.5); coord /= screen_scale; return coord; } float calcScanLineWeight(float dist) { return max(1.0 - dist * dist * u.scanline_weight, u.scanline_gap_brightness); } float calcScanLine(float dy, float filter_width) { float weight = calcScanLineWeight(dy); if (u.enable_multisample != 0) { weight += calcScanLineWeight(dy - filter_width); weight += calcScanLineWeight(dy + filter_width); weight *= 0.3333333; } return weight; } void main() { vec2 tex_size = vec2(u.texture_width, u.texture_height); // filterWidth: equivalente al original (768.0 / TextureSize.y) / 3.0 float filter_width = (768.0 / u.texture_height) / 3.0; vec2 texcoord = v_uv; // Curvatura barrel opcional if (u.enable_curvature != 0) { texcoord = distort(texcoord, vec2(1.0, 1.0)); if (texcoord.x < 0.0) { out_color = vec4(0.0, 0.0, 0.0, 1.0); return; } } vec2 texcoord_in_pixels = texcoord * tex_size; vec2 tc; float scan_line_weight; if (u.enable_sharper != 0) { // Modo SHARPER: filtrado bicúbico-like con subpixel sharpen vec2 temp_coord = floor(texcoord_in_pixels) + 0.5; tc = temp_coord / tex_size; vec2 deltas = texcoord_in_pixels - temp_coord; scan_line_weight = calcScanLine(deltas.y, filter_width); vec2 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 /= tex_size; deltas *= signs; tc = tc + deltas; } else { // Modo estándar float temp_y = floor(texcoord_in_pixels.y) + 0.5; float y_coord = temp_y / tex_size.y; float dy = texcoord_in_pixels.y - temp_y; scan_line_weight = calcScanLine(dy, filter_width); float sign_y = sign(dy); dy = dy * dy; dy = dy * dy; dy *= 8.0; dy /= tex_size.y; dy *= sign_y; tc = vec2(texcoord.x, y_coord + dy); } vec3 colour = texture(Texture, tc).rgb; if (u.enable_scanlines != 0) { if (u.enable_gamma != 0) { colour = pow(colour, vec3(u.input_gamma)); } colour *= scan_line_weight * u.bloom_factor; if (u.enable_gamma != 0) { colour = pow(colour, vec3(1.0 / u.output_gamma)); } } // Máscara de fósforo if (u.mask_type == 1) { float which_mask = fract(gl_FragCoord.x * 0.5); vec3 mask = (which_mask < 0.5) ? vec3(u.mask_brightness, 1.0, u.mask_brightness) : vec3(1.0, u.mask_brightness, 1.0); colour *= mask; } else if (u.mask_type == 2) { float which_mask = fract(gl_FragCoord.x * 0.3333333); vec3 mask = vec3(u.mask_brightness); if (which_mask < 0.3333333) mask.x = 1.0; else if (which_mask < 0.6666666) mask.y = 1.0; else mask.z = 1.0; colour *= mask; } out_color = vec4(colour, 1.0); }