127 lines
3.9 KiB
GLSL
127 lines
3.9 KiB
GLSL
#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;
|
|
} 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
|
|
vec3 colour;
|
|
if (u.bleeding > 0.0) {
|
|
float tw = float(textureSize(scene, 0).x);
|
|
vec3 ycc = rgb_to_ycc(base);
|
|
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0/tw, 0.0)).rgb);
|
|
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0/tw, 0.0)).rgb);
|
|
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0/tw, 0.0)).rgb);
|
|
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + 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), u.bleeding);
|
|
} else {
|
|
colour = base;
|
|
}
|
|
|
|
// Aberración cromática
|
|
float ca = u.chroma_strength * 0.005;
|
|
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
|
|
float texHeight = float(textureSize(scene, 0).y);
|
|
float scaleY = u.screen_height / texHeight;
|
|
float screenY = uv.y * u.screen_height;
|
|
float posInRow = mod(screenY, scaleY);
|
|
float scanLineDY = posInRow / scaleY - 0.5;
|
|
float scan = max(1.0 - scanLineDY * scanLineDY * 6.0, 0.12) * 3.5;
|
|
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
|
|
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);
|
|
}
|
|
|
|
out_color = vec4(colour, 1.0);
|
|
}
|