Files
orni-attack/shaders/bloom.frag.glsl
T

72 lines
2.7 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#version 450
// Fragment shader del bloom: una passada 1D de blur gaussià separable, amb
// high-pass opcional. Es crida dues vegades per frame:
//
// Pass H: extract=1.0, direction=(1,0). Llegeix l'escena offscreen i
// emet a bloom_texture_a aplicant high-pass + gaussiana horitzontal.
// Pass V: extract=0.0, direction=(0,1). Llegeix bloom_texture_a i emet
// a bloom_texture_b amb la gaussiana vertical (sense high-pass).
//
// Resultat: equivalent matemàtic d'una convolució 2D de 15×15 mostres denses,
// però només costa 2×15 = 30 mostres per píxel. Sense moiré (samples a
// distància 1 texel, així que la gaussiana és contínua a l'escala del píxel).
//
// El paràmetre `sigma` (en texels) controla l'amplada del halo. Per a sigma=4,
// el halo cobreix ~12 texels al voltant de cada línia. Pujar sigma engreixa
// el halo; cal mantenir-lo ≤ ~5-6 perquè el rang de mostreig (±7 taps) cobreixi
// el 99% del gaussià.
//
// Recursos:
// set=2, binding=0 → sampler2D (input)
// set=3, binding=0 → uniform buffer (paràmetres)
layout(set = 2, binding = 0) uniform sampler2D src;
layout(set = 3, binding = 0) uniform BloomUBO {
vec2 texel_size; // 1.0 / texture_size
vec2 direction; // (1,0) per pass H, (0,1) per pass V
float threshold; // luminància mínima per al high-pass
float extract; // 1.0 = aplica high-pass (pass H), 0.0 = blur pur (pass V)
float sigma; // sigma de la gaussiana en texels
float _pad;
} ubo;
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 frag;
void main() {
vec3 sum = vec3(0.0);
float total_weight = 0.0;
// 15 taps: -7..+7, espaiats 1 texel. Cobreix ±7 texels = ±~2σ per σ=3.5.
// Per σ més grans, el cua es retalla una mica però el peso del tap 7 ja és
// molt baix; visualment no es nota.
const int RADIUS = 7;
const float TWO_SIGMA_SQ_FACTOR = 2.0; // multiplicador per a 2σ² al denominador
for (int i = -RADIUS; i <= RADIUS; ++i) {
vec2 offset = ubo.direction * float(i) * ubo.texel_size;
vec3 c = texture(src, v_uv + offset).rgb;
// High-pass només a la primera passada: a la segona, c ja és el
// resultat de la H i no l'hem de tornar a filtrar.
if (ubo.extract > 0.5) {
float luma = max(c.r, max(c.g, c.b));
float high_pass = max(0.0, luma - ubo.threshold);
c *= high_pass;
}
float fi = float(i);
float w = exp(-(fi * fi) / (TWO_SIGMA_SQ_FACTOR * ubo.sigma * ubo.sigma));
sum += c * w;
total_weight += w;
}
if (total_weight > 0.0) {
sum /= total_weight;
}
frag = vec4(sum, 1.0);
}