primera implementacio de postfx

This commit is contained in:
2026-03-23 13:18:36 +01:00
parent 99cc803f21
commit 2f3161d701
23 changed files with 13306 additions and 771 deletions

144
data/shaders/postfx.frag Normal file
View File

@@ -0,0 +1,144 @@
#version 450
// Vulkan GLSL fragment shader — PostFX effects
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
// Compile: glslc postfx.frag -o postfx.frag.spv
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
//
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
// (8 floats, 32 bytes, std140/scalar layout).
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D scene;
layout(set = 3, binding = 0) uniform PostFXUniforms {
float vignette_strength;
float chroma_strength;
float scanline_strength;
float screen_height;
float mask_strength;
float gamma_strength;
float curvature;
float bleeding;
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
} u;
// YCbCr helpers for NTSC bleeding
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);
}
void main() {
vec2 uv = v_uv;
// Curvatura barrel CRT
if (u.curvature > 0.0) {
vec2 c = uv - 0.5;
float rsq = dot(c, c);
vec2 dist = vec2(0.05, 0.1) * u.curvature;
vec2 barrelScale = vec2(1.0) - 0.23 * dist;
c += c * (dist * rsq);
c *= barrelScale;
if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
out_color = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
uv = c + 0.5;
}
// Muestra base
vec3 base = texture(scene, uv).rgb;
// Sangrado NTSC — difuminado horizontal de crominancia.
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
vec3 colour;
if (u.bleeding > 0.0) {
float tw = float(textureSize(scene, 0).x);
float step = u.oversample / tw; // 1 pixel lógico en UV
vec3 ycc = rgb_to_ycc(base);
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 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), u.bleeding);
} else {
colour = base;
}
// Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
// Corrección gamma (linealizar antes de scanlines, codificar después)
if (u.gamma_strength > 0.0) {
vec3 lin = pow(colour, vec3(2.4));
colour = mix(colour, lin, u.gamma_strength);
}
// Scanlines — 1 pixel físico oscuro por fila lógica.
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
// calcular la posición dentro de la fila en coordenadas físicas.
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
if (u.scanline_strength > 0.0) {
float ps = max(1.0, round(u.pixel_scale));
float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float is_dark = step(ps - 1.0, row_pos);
float scan = mix(3.5, 0.42, is_dark);
colour *= mix(1.0, scan, u.scanline_strength);
}
if (u.gamma_strength > 0.0) {
vec3 enc = pow(colour, vec3(1.0 / 2.2));
colour = mix(colour, enc, u.gamma_strength);
}
// Viñeta
vec2 d = uv - 0.5;
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
colour *= clamp(vignette, 0.0, 1.0);
// Máscara de fósforo RGB — después de scanlines (orden original):
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
if (u.mask_strength > 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, u.mask_strength);
}
// Parpadeo de fósforo CRT (~50 Hz)
if (u.flicker > 0.0) {
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
}
out_color = vec4(colour, 1.0);
}

24
data/shaders/postfx.vert Normal file
View File

@@ -0,0 +1,24 @@
#version 450
// Vulkan GLSL vertex shader — postfx full-screen triangle
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
// Compile: glslc postfx.vert -o postfx.vert.spv
// xxd -i postfx.vert.spv > ../../source/core/rendering/sdl3gpu/postfx_vert_spv.h
layout(location = 0) out vec2 v_uv;
void main() {
// Full-screen triangle (no vertex buffer needed)
const vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0)
);
const vec2 uvs[3] = vec2[3](
vec2(0.0, 1.0),
vec2(2.0, 1.0),
vec2(0.0,-1.0)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
v_uv = uvs[gl_VertexIndex];
}