pub const BLOOM_FRAG: &str = "// bloom.frag \u{2014} separable Gaussian bloom with a soft-knee threshold and an\r\n// anamorphic option.\r\n//\r\n// Three things were missing.\r\n//\r\n// There was no threshold: the whole emission buffer was blurred and added\r\n// back, so dim matter glowed exactly as readily as bright matter and the\r\n// result was a general haze rather than light coming off the things that are\r\n// actually lit. A soft knee is what makes bloom look like an optical effect\r\n// instead of a blur layer.\r\n//\r\n// The kernel was thirteen taps at a fixed radius with no use of bilinear\r\n// filtering, which is the standard trick: sampling *between* two texels gets\r\n// two taps for one fetch, so the same nine fetches cover a much wider kernel.\r\n//\r\n// And it was isotropic. Real anamorphic glass streaks light horizontally, and\r\n// a horizontal pass with a stretched radius costs exactly what the ordinary\r\n// horizontal pass costs.\r\n#version 330 core\r\n\r\nin vec2 f_uv;\r\nuniform sampler2D u_texture;\r\nuniform bool u_horizontal;\r\nuniform float u_radius;\r\nuniform float u_threshold;\r\nuniform float u_knee;\r\nuniform bool u_prefilter;\r\nuniform float u_stretch;\r\n\r\nout vec4 o_color;\r\n\r\n// Linear-sampled 9-tap: offsets fall between texels so the hardware\'s\r\n// bilinear filter does half the work.\r\nconst float weight[3] = float[](0.2270270270, 0.3162162162, 0.0702702703);\r\nconst float offset[3] = float[](0.0, 1.3846153846, 3.2307692308);\r\n\r\n// Soft knee: nothing under the threshold blooms, everything well over it\r\n// blooms fully, and the region between is a quadratic rather than a cliff.\r\n// A hard cut-off makes bright edges crawl as the frame changes.\r\nvec3 prefilter(vec3 c) {\r\n float lum = max(c.r, max(c.g, c.b));\r\n float knee = max(u_knee, 1e-4);\r\n float soft = clamp(lum - u_threshold + knee, 0.0, 2.0 * knee);\r\n soft = soft * soft / (4.0 * knee);\r\n float contribution = max(soft, lum - u_threshold) / max(lum, 1e-4);\r\n return c * contribution;\r\n}\r\n\r\nvoid main() {\r\n vec2 texel = 1.0 / vec2(textureSize(u_texture, 0));\r\n vec3 result = texture(u_texture, f_uv).rgb * weight[0];\r\n\r\n if (u_horizontal) {\r\n float r = u_radius * u_stretch;\r\n for (int i = 1; i < 3; ++i) {\r\n vec2 o = vec2(texel.x * offset[i] * r, 0.0);\r\n result += texture(u_texture, f_uv + o).rgb * weight[i];\r\n result += texture(u_texture, f_uv - o).rgb * weight[i];\r\n }\r\n } else {\r\n for (int i = 1; i < 3; ++i) {\r\n vec2 o = vec2(0.0, texel.y * offset[i] * u_radius);\r\n result += texture(u_texture, f_uv + o).rgb * weight[i];\r\n result += texture(u_texture, f_uv - o).rgb * weight[i];\r\n }\r\n }\r\n\r\n // Only the first pass thresholds. Doing it on every pass would eat the\r\n // blur it just produced.\r\n if (u_prefilter) {\r\n result = prefilter(result);\r\n }\r\n o_color = vec4(result, 1.0);\r\n}\r\n";