#version 300 es precision highp float; // highp: fullscreen UV addressing (mediump fp16 is not enough on mobile) in vec2 vTexCoord; uniform mediump sampler2D u_tex; uniform float u_threshold; uniform float u_knee; // soft-knee width below the threshold; 0 = hard cut uniform float u_chromaBias; // 0 = Rec.709 luminance, 1 = largest channel (hue-blind) out vec4 fragColor; void main() { vec3 color = texture(u_tex, vTexCoord).rgb; // Rec. 709 luminance is perceptually correct but weights blue at 7% and green at 72%, so a // saturated blue needs about ten times a green's value to pass the same threshold. On a palette // that assigns each thing its own hue, that silently excludes whole colours from the glow. // The largest channel is hue-blind: it asks "how strong is this colour", not "how bright does it // look". u_chromaBias picks between them float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); float peak = max(color.r, max(color.g, color.b)); float brightness = mix(luma, peak, u_chromaBias); // soft knee: ramps in over [threshold - knee, threshold] — a hard cut flickers on moving edges float w = smoothstep(u_threshold - max(u_knee, 0.0001), u_threshold, brightness); fragColor = vec4(color * w, 1.0); }