#version 450 // Fragment shader del pase de postprocesado. // Lee la textura offscreen (escena vectorial sobre fondo negro) y produce // el fragmento final aplicando: // 1. Bloom kernel 5×5 con high-pass (solo los brillos por encima de // threshold contribuyen). // 2. Flicker: multiplicador global de brillo modulado por tiempo // (sustituye al oscilador CPU del legacy). // 3. Background pulse: color de fondo que oscila entre min y max y se // suma a la imagen (las líneas brillan por encima). // // Resource sets (SDL_gpu): // set=2, binding=0 → sampler2D (escena offscreen) // set=3, binding=0 → uniform buffer (parámetros del postpro) layout(set = 2, binding = 0) uniform sampler2D scene; layout(set = 3, binding = 0) uniform PostFxUBO { float time; float bloom_intensity; float bloom_threshold; float bloom_radius_px; float flicker_amplitude; float flicker_frequency_hz; float background_pulse_freq_hz; float _pad_a; vec4 background_min; // RGB en [0..1], A=1 vec4 background_max; // RGB en [0..1], A=1 vec2 texel_size; // 1.0 / texture_size vec2 _pad_b; } ubo; layout(location = 0) in vec2 v_uv; layout(location = 0) out vec4 frag; const float TAU = 6.28318530718; void main() { // === BLOOM === // Kernel 5×5 con muestreo radial y high-pass por luminancia (max RGB). // Pesos gaussianos: w = exp(-(dx²+dy²) / 4). vec3 src = texture(scene, v_uv).rgb; vec3 bloom = vec3(0.0); float total_weight = 0.0; for (int dy = -2; dy <= 2; ++dy) { for (int dx = -2; dx <= 2; ++dx) { vec2 offset = vec2(float(dx), float(dy)) * ubo.texel_size * ubo.bloom_radius_px; vec3 c = texture(scene, v_uv + offset).rgb; float luma = max(c.r, max(c.g, c.b)); float high_pass = max(0.0, luma - ubo.bloom_threshold); float w = exp(-(float(dx * dx + dy * dy)) / 4.0); bloom += c * high_pass * w; total_weight += w; } } if (total_weight > 0.0) { bloom /= total_weight; } bloom *= ubo.bloom_intensity; // === FLICKER === // Multiplicador global de brillo. Oscila entre (1.0 - amplitude) y 1.0. // amplitude=0 → sin flicker; amplitude=1 → pulsa entre apagado y máximo. float pulse = (sin(ubo.time * ubo.flicker_frequency_hz * TAU) * 0.5) + 0.5; float flicker = 1.0 - (ubo.flicker_amplitude * (1.0 - pulse)); // === BACKGROUND PULSE === // Suma de color de fondo oscilante. min..max se interpolan con sin(t). float bg_pulse = (sin(ubo.time * ubo.background_pulse_freq_hz * TAU) * 0.5) + 0.5; vec3 background = mix(ubo.background_min.rgb, ubo.background_max.rgb, bg_pulse); // === COMPOSICIÓN === // El offscreen viene con clear=black, por lo que solo las líneas y el // bloom aportan luz. Sumamos el fondo y luego multiplicamos por flicker // para que el pulso afecte a todo (líneas + bloom + bg). vec3 lines_and_glow = (src + bloom) * flicker; frag = vec4(background + lines_and_glow, 1.0); }