From 1620eef29de6dd04d9281a881f5ccfa713549780 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Wed, 9 Dec 2020 23:21:15 +0300 Subject: [PATCH] Implement blooming in deferred renderer --- .../include/psemek/gfx/renderer/deferred.hpp | 10 + libs/gfx/source/renderer/deferred.cpp | 210 +++++++++++++++++- 2 files changed, 217 insertions(+), 3 deletions(-) diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp index b267ac4b..19d69807 100644 --- a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -35,6 +35,7 @@ namespace psemek::gfx texture_2d const * texture = nullptr; bool transparent = false; bool lit = true; + bool blooming = false; float diffuse = 1.f; struct @@ -98,6 +99,15 @@ namespace psemek::gfx float max_intensity; // Equals max_intensity / 256 by default std::optional min_intensity; + + struct bloom_data + { + int size = 5; + float sigma = 3.f; + std::size_t downsample = 2; + }; + + std::optional bloom; }; void render(std::vector const & objects, render_target const & target, options const & opts); diff --git a/libs/gfx/source/renderer/deferred.cpp b/libs/gfx/source/renderer/deferred.cpp index 2744e85d..da332cc1 100644 --- a/libs/gfx/source/renderer/deferred.cpp +++ b/libs/gfx/source/renderer/deferred.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -13,7 +15,7 @@ #include -#include +#include namespace psemek::gfx { @@ -29,6 +31,7 @@ const uint O_CASTS_SHADOW = 1u << 4; const uint O_PRE_TRANSFORM = 1u << 5; const uint O_POST_TRANSFORM = 1u << 6; const uint O_INSTANCED = 1u << 7; +const uint O_BLOOMING = 1u << 8; uniform uint u_flag_mask; )"; @@ -641,6 +644,7 @@ void main() gfx::program shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_fs}; gfx::program cubemap_shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_gs, shadow_builder_fs}; gfx::program transparent_pass_program{std::string(g_buffer_pass_common) + transparent_pass_vs, std::string(g_buffer_pass_common) + transparent_pass_fs}; + gfx::program bloom_pass_program{std::string(g_buffer_pass_common) + transparent_pass_vs, std::string(g_buffer_pass_common) + transparent_pass_fs}; // G-buffer attachments: // 0 - position (rbg) @@ -663,6 +667,19 @@ void main() gfx::framebuffer point_shadow_framebuffer; gfx::texture_cubemap point_shadow_texture; + std::map, hblur> hblur_container; + std::map, vblur> vblur_container; + overlay bloom_overlay; + + std::optional> bloom_size; + std::optional bloom_downsample; + + // 0: original, unblurred bloom + // 1: horizontally blurred bloom + // 2: fully blurred bloom + gfx::framebuffer bloom_framebuffer[3]; + gfx::texture_2d bloom_texture[3]; + gfx::mesh screen_mesh; }; @@ -718,6 +735,13 @@ void main() impl().point_shadow_framebuffer.depth(impl().point_shadow_texture); impl().point_shadow_framebuffer.assert_complete(); + impl().bloom_texture[0].linear_filter(); + impl().bloom_texture[1].linear_filter(); + impl().bloom_texture[2].linear_filter(); + impl().bloom_texture[0].clamp(); + impl().bloom_texture[1].clamp(); + impl().bloom_texture[2].clamp(); + impl().screen_mesh.setup>(); } @@ -737,6 +761,7 @@ void main() static std::uint32_t const O_PRE_TRANSFORM = 1 << 5; static std::uint32_t const O_POST_TRANSFORM = 1 << 6; static std::uint32_t const O_INSTANCED = 1 << 7; + static std::uint32_t const O_BLOOMING = 1 << 8; std::uint32_t mask(deferred_renderer::object const & o) { @@ -749,6 +774,7 @@ void main() if (o.pre_transform) m |= O_PRE_TRANSFORM; if (o.post_transform) m |= O_POST_TRANSFORM; if (o.mesh->is_instanced()) m |= O_INSTANCED; + if (o.mat.blooming) m |= O_BLOOMING; return m; } @@ -777,6 +803,9 @@ void main() if (o.mat.lit && o.mat.transparent) throw std::runtime_error("Materials that are both tit & transparent are not supported"); + if (o.mat.lit && o.mat.blooming) + throw std::runtime_error("Materials that are both tit & blooming are not supported"); + if (o.casts_shadow && o.mat.transparent) throw std::runtime_error("Transparent objects cannot cast shadow"); @@ -788,7 +817,7 @@ void main() // Resize g-buffer if needed - auto buffer_size = geom::cast(target.viewport.dimensions()); + auto const buffer_size = geom::cast(target.viewport.dimensions()); bool const buffer_size_changed = !impl().g_buffer_size || *impl().g_buffer_size != buffer_size; if (buffer_size_changed || impl().position_mode_changed) { @@ -824,6 +853,46 @@ void main() impl().position_mode_changed = false; } + bool const bloom_size_changed = opts.bloom && (!impl().bloom_size || *impl().bloom_size != buffer_size || !impl().bloom_downsample || *impl().bloom_downsample != opts.bloom->downsample); + if (bloom_size_changed) + { + impl().bloom_texture[0].load(buffer_size); + impl().bloom_framebuffer[0].color(impl().bloom_texture[0]); + impl().bloom_framebuffer[0].depth(impl().g_buffer_depth); + impl().bloom_framebuffer[0].assert_complete(); + + impl().bloom_texture[1].load({buffer_size[0] / opts.bloom->downsample, buffer_size[1]}); + impl().bloom_framebuffer[1].color(impl().bloom_texture[1]); + impl().bloom_framebuffer[1].assert_complete(); + + impl().bloom_texture[2].load(buffer_size / opts.bloom->downsample); + impl().bloom_framebuffer[2].color(impl().bloom_texture[2]); + impl().bloom_framebuffer[2].assert_complete(); + + impl().bloom_size = buffer_size; + impl().bloom_downsample = opts.bloom->downsample; + } + + std::optional> hblur_params; + std::optional> vblur_params; + + if (opts.bloom) + { + hblur_params = std::pair{opts.bloom->size, opts.bloom->sigma}; + if (impl().hblur_container.count(*hblur_params) == 0) + { + auto params = std::tuple{hblur_params->first, hblur_params->second}; + impl().hblur_container.emplace(std::piecewise_construct, params, params); + } + + vblur_params = std::pair{opts.bloom->size, opts.bloom->sigma}; + if (impl().vblur_container.count(*vblur_params) == 0) + { + auto params = std::tuple{vblur_params->first, vblur_params->second}; + impl().vblur_container.emplace(std::piecewise_construct, params, params); + } + } + // Setup g-buffer impl().g_framebuffer.bind(); @@ -897,10 +966,12 @@ void main() // Render unlit transparent objects + gl::DrawBuffer(gl::COLOR_ATTACHMENT0); + impl().transparent_framebuffer.bind(); impl().transparent_pass_program.bind(); impl().transparent_pass_program["u_camera_transform"] = camera_transform; - impl().transparent_pass_program["u_max_intensity"] = opts.max_intensity; + impl().transparent_pass_program["u_max_intensity"] = 1.f; gl::Enable(gl::BLEND); gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ZERO, gl::ONE); @@ -941,7 +1012,133 @@ void main() } } + // Render bloom + + impl().bloom_framebuffer[0].bind(); + + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(gl::LEQUAL); + + gl::Disable(gl::BLEND); + + impl().bloom_pass_program.bind(); + impl().bloom_pass_program["u_camera_transform"] = camera_transform; + impl().bloom_pass_program["u_max_intensity"] = opts.max_intensity; + + for (auto const & p : objects_by_mask) + { + std::uint32_t mask = p.first; + + if (!(mask & O_BLOOMING)) + continue; + + if (p.second.empty()) continue; + + impl().bloom_pass_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) + { + auto const & o = objects[i]; + + if (mask & O_UNIFORM_COLOR) + impl().bloom_pass_program["u_color"] = *o.mat.color; + + if (mask & O_TEXTURE_COLOR) + { + gl::ActiveTexture(gl::TEXTURE0); + o.mat.texture->bind(); + } + + if (mask & O_PRE_TRANSFORM) + impl().bloom_pass_program["u_pre_transform"] = *o.pre_transform; + + if (mask & O_POST_TRANSFORM) + impl().bloom_pass_program["u_post_transform"] = *o.post_transform; + + impl().bloom_pass_program["u_material"] = geom::vector{o.mat.diffuse, o.mat.specular.intensity, o.mat.specular.shininess}; + + o.mesh->draw(); + } + } + + // Render unlit transparent objects to bloom + + impl().transparent_pass_program.bind(); + impl().transparent_pass_program["u_camera_transform"] = camera_transform; + impl().transparent_pass_program["u_max_intensity"] = 1.f; + + gl::Enable(gl::BLEND); + gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ZERO, gl::ONE); + + for (auto const & p : objects_by_mask) + { + std::uint32_t mask = p.first; + + if (!(mask & O_TRANSPARENT)) + continue; + + if (p.second.empty()) continue; + + impl().transparent_pass_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) + { + auto const & o = objects[i]; + + if (mask & O_UNIFORM_COLOR) + impl().transparent_pass_program["u_color"] = *o.mat.color; + + if (mask & O_TEXTURE_COLOR) + { + gl::ActiveTexture(gl::TEXTURE0); + o.mat.texture->bind(); + } + + if (mask & O_PRE_TRANSFORM) + impl().transparent_pass_program["u_pre_transform"] = *o.pre_transform; + + if (mask & O_POST_TRANSFORM) + impl().transparent_pass_program["u_post_transform"] = *o.post_transform; + + o.mesh->draw(); + } + } + gl::DepthMask(gl::TRUE); + gl::Disable(gl::BLEND); + + // Apply horizontal blur to bloom + + { + render_target target; + target.framebuffer = &impl().bloom_framebuffer[1]; + target.draw_buffer = gl::COLOR_ATTACHMENT0; + target.viewport = {{{0, impl().bloom_texture[1].width()}, {0, impl().bloom_texture[1].height()}}}; + + target.bind(); + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + impl().hblur_container.at(*hblur_params).invoke(impl().bloom_texture[0], target); + } + + // Apply vertical blur to bloom + + { + render_target target; + target.framebuffer = &impl().bloom_framebuffer[2]; + target.draw_buffer = gl::COLOR_ATTACHMENT0; + target.viewport = {{{0, impl().bloom_texture[2].width()}, {0, impl().bloom_texture[2].height()}}}; + + target.bind(); + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + impl().vblur_container.at(*vblur_params).invoke(impl().bloom_texture[1], target); + } // Setup destination framebuffer @@ -1275,6 +1472,13 @@ void main() impl().screen_mesh.draw(); } + // Overlay bloom + + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + + impl().bloom_overlay.invoke(impl().bloom_texture[2], target); + check_error(); }