153 lines
5.4 KiB
GLSL
153 lines
5.4 KiB
GLSL
#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);
|
|
}
|