// Desenfoque: gaussiana separable sobre texturas de media resolución y copia final a la ventana.
// El compuesto con máscara de rectángulo redondeado vive en `shader.wgsl` (`fs_blur`).
struct Params {
// 1 / tamaño de la textura de destino, para pasar de píxeles a uv.
inv_size: vec2<f32>,
direction: vec2<f32>,
// Desviación y separación entre muestras, en píxeles de la textura de destino.
sigma: f32,
stride: f32,
taps: f32,
padding: f32,
}
@group(0) @binding(0) var source: texture_2d<f32>;
@group(0) @binding(1) var source_sampler: sampler;
@group(1) @binding(0) var<uniform> params: Params;
struct FullscreenOut {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
// Un triángulo que cubre la pantalla: sin buffer de vértices.
@vertex
fn vs_full(@builtin(vertex_index) index: u32) -> FullscreenOut {
let uv = vec2<f32>(f32((index << 1u) & 2u), f32(index & 2u));
var out: FullscreenOut;
out.position = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0);
out.uv = uv;
return out;
}
@fragment
fn fs_blur_pass(in: FullscreenOut) -> @location(0) vec4<f32> {
let taps = i32(params.taps);
let inverse_variance = 1.0 / max(params.sigma * params.sigma, 0.0001);
var sum = vec4<f32>(0.0);
var total = 0.0;
for (var i = -taps; i <= taps; i++) {
let t = f32(i);
let weight = exp(-0.5 * t * t * inverse_variance);
let offset = params.direction * (t * params.stride) * params.inv_size;
sum += textureSampleLevel(source, source_sampler, in.uv + offset, 0.0) * weight;
total += weight;
}
return sum / total;
}
@fragment
fn fs_blit(in: FullscreenOut) -> @location(0) vec4<f32> {
return textureSampleLevel(source, source_sampler, in.uv, 0.0);
}