diff --git a/libs/gfx/include/psemek/gfx/effect/blur.hpp b/libs/gfx/include/psemek/gfx/effect/blur.hpp new file mode 100644 index 00000000..f06c67ba --- /dev/null +++ b/libs/gfx/include/psemek/gfx/effect/blur.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::gfx +{ + + struct hblur + { + hblur(int size, float sigma); + ~hblur(); + + void invoke(texture_2d const & src, render_target const & dst); + + private: + psemek_declare_pimpl + }; + + struct vblur + { + vblur(int size, float sigma); + ~vblur(); + + void invoke(texture_2d const & src, render_target const & dst); + + private: + psemek_declare_pimpl + }; + +} diff --git a/libs/gfx/source/effect/blur.cpp b/libs/gfx/source/effect/blur.cpp new file mode 100644 index 00000000..e1b067b3 --- /dev/null +++ b/libs/gfx/source/effect/blur.cpp @@ -0,0 +1,168 @@ +#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; + +in vec2 texcoord; + +out vec4 out_color; + +void main() +{ + vec4 result = vec4(0.0); + for (int i = -BLUR_SIZE; i <= BLUR_SIZE; ++i) + { + result += texture(u_texture, texcoord + float(i) * direction * u_inv_texture_size) * coeffs[i + BLUR_SIZE]; + } + out_color = result; +} +)"; + + 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, bool horizontal) + : program(blur_vs, generate_blur_fs_source(size, sigma, horizontal)) + { + program.bind(); + program["u_texture"] = 0; + } + + void invoke(texture_2d const & src, render_target const & dst) + { + gl::Disable(gl::DEPTH_TEST); + gl::Disable(gl::CULL_FACE); + + dst.bind(); + gl::ActiveTexture(gl::TEXTURE0); + src.bind(); + array.bind(); + program.bind(); + program["u_inv_texture_size"] = geom::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) + : pimpl_{std::make_unique(size, sigma, 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) + : pimpl_{std::make_unique(size, sigma, false)} + {} + + vblur::~vblur() = default; + + void vblur::invoke(texture_2d const & src, render_target const & dst) + { + impl().invoke(src, dst); + } + +}