#include #include #include #include #include #include #include namespace psemek::gfx { static char const blur_vs[] = R"(#version 330 const vec4 vertices[6] = vec4[6]( vec4(-1.0, -1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, 1.0, 0.0, 1.0), vec4(-1.0, -1.0, 0.0, 1.0), vec4( 1.0, 1.0, 0.0, 1.0), vec4(-1.0, 1.0, 0.0, 1.0) ); out vec2 texcoord; void main() { gl_Position = vertices[gl_VertexID]; texcoord = vertices[gl_VertexID].xy * 0.5 + vec2(0.5); } )"; static char const blur_fs_template[] = R"(#version 330 #define BLUR_SIZE @BLUR_SIZE@ #define BLUR_COEFFS_COUNT 2 * BLUR_SIZE + 1 const float coeffs[BLUR_COEFFS_COUNT] = float[BLUR_COEFFS_COUNT](@BLUR_COEFFS@); const vec2 direction = vec2(@BLUR_DIRECTION@); uniform sampler2D u_texture; uniform vec2 u_inv_texture_size; uniform float u_gamma; in vec2 texcoord; out vec4 out_color; void main() { vec4 result = vec4(0.0); for (int i = -BLUR_SIZE; i <= BLUR_SIZE; ++i) { vec4 p = texture(u_texture, texcoord + float(i) * direction * u_inv_texture_size); float c = coeffs[i + BLUR_SIZE]; result.rgb += pow(p.rgb, vec3(1.0 / u_gamma)) * p.a * c; result.a += p.a * c; } if (result.a == 0.0) out_color = result; else out_color = vec4(pow(result.rgb / result.a, vec3(u_gamma)), result.a); } )"; static void replace_all(std::string & str, std::string_view old_str, std::string_view new_str) { for (size_t i = 0;;) { i = str.find(old_str, i); if (i == std::string::npos) break; str.replace(i, old_str.size(), new_str); i += new_str.size(); } } static std::string generate_blur_fs_source(int size, float sigma, bool horizontal) { std::string size_str = util::to_string(size); std::string direction_str = horizontal ? "1.0, 0.0" : "0.0, 1.0"; std::vector coeffs(2 * size + 1); { float sum = 0.f; for (int i = -size; i <= size; ++i) { float x = (i / sigma); coeffs[i + size] = std::exp(- x * x); sum += coeffs[i + size]; } for (auto & c : coeffs) c /= sum; } std::ostringstream coeffs_ss; for (std::size_t i = 0; i < coeffs.size(); ++i) { if (i > 0) coeffs_ss << ", "; coeffs_ss << std::fixed << std::setprecision(10) << coeffs[i]; } std::string result = blur_fs_template; replace_all(result, "@BLUR_SIZE@", size_str); replace_all(result, "@BLUR_COEFFS@", coeffs_ss.str()); replace_all(result, "@BLUR_DIRECTION@", direction_str); return result; } struct blur_impl { gfx::program program; gfx::array array; blur_impl(int size, float sigma, float gamma, bool horizontal) : program(blur_vs, generate_blur_fs_source(size, sigma, horizontal)) { program.bind(); program["u_texture"] = 0; program["u_gamma"] = gamma; } void invoke(texture_2d const & src, render_target const & dst) { gl::Disable(gl::DEPTH_TEST); gl::Disable(gl::CULL_FACE); gl::Disable(gl::BLEND); dst.bind(); gl::ActiveTexture(gl::TEXTURE0); src.bind(); array.bind(); program.bind(); program["u_inv_texture_size"] = math::vector{1.f / src.width(), 1.f / src.height()}; gl::DrawArrays(gl::TRIANGLES, 0, 6); } }; struct hblur::impl : blur_impl { using blur_impl::blur_impl; }; hblur::hblur(int size, float sigma, float gamma) : pimpl_{make_impl(size, sigma, gamma, true)} {} hblur::~hblur() = default; void hblur::invoke(texture_2d const & src, render_target const & dst) { impl().invoke(src, dst); } struct vblur::impl : blur_impl { using blur_impl::blur_impl; }; vblur::vblur(int size, float sigma, float gamma) : pimpl_{make_impl(size, sigma, gamma, false)} {} vblur::~vblur() = default; void vblur::invoke(texture_2d const & src, render_target const & dst) { impl().invoke(src, dst); } }