From c8c5e874da6b2041d9b16431e4bb4a8d1cff709a Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 10 Dec 2020 10:41:40 +0300 Subject: [PATCH] Implement SSAO in deferred renderer --- libs/gfx/CMakeLists.txt | 2 +- .../include/psemek/gfx/renderer/deferred.hpp | 8 + libs/gfx/source/renderer/deferred.cpp | 478 +++++++++++++----- 3 files changed, 354 insertions(+), 134 deletions(-) diff --git a/libs/gfx/CMakeLists.txt b/libs/gfx/CMakeLists.txt index 72248c51..c75b3bc7 100644 --- a/libs/gfx/CMakeLists.txt +++ b/libs/gfx/CMakeLists.txt @@ -6,7 +6,7 @@ file(GLOB_RECURSE PSEMEK_GFX_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sou add_library(psemek-gfx ${PSEMEK_GFX_HEADERS} ${PSEMEK_GFX_SOURCES}) target_include_directories(psemek-gfx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(psemek-gfx PUBLIC psemek-util psemek-geom psemek-cg OpenGL::GL) +target_link_libraries(psemek-gfx PUBLIC psemek-util psemek-geom psemek-cg psemek-random OpenGL::GL) psemek_add_resources(psemek-gfx resources/font_9x12.pbm psemek/gfx/resource/font_9x12 diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp index 19d69807..e1de41e5 100644 --- a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -108,6 +108,14 @@ namespace psemek::gfx }; std::optional bloom; + + struct ssao_data + { + float radius; + std::size_t downsample = 2; + }; + + std::optional ssao; }; 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 da332cc1..8a1cb409 100644 --- a/libs/gfx/source/renderer/deferred.cpp +++ b/libs/gfx/source/renderer/deferred.cpp @@ -8,6 +8,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -15,6 +19,8 @@ #include +#include + #include namespace psemek::gfx @@ -181,66 +187,66 @@ void main() } )"; - static char const shadow_builder_vs[] = - R"( - uniform mat4 u_light_transform; - uniform mat4x3 u_pre_transform; - uniform mat4x3 u_post_transform; +static char const shadow_builder_vs[] = +R"( +uniform mat4 u_light_transform; +uniform mat4x3 u_pre_transform; +uniform mat4x3 u_post_transform; - layout (location = 0) in vec4 in_position; +layout (location = 0) in vec4 in_position; - layout (location = 4) in mat3x4 in_instance_transform; +layout (location = 4) in mat3x4 in_instance_transform; - void main() +void main() +{ + vec4 pos = in_position; + + if ((u_flag_mask & O_PRE_TRANSFORM) != 0u) { - vec4 pos = in_position; - - if ((u_flag_mask & O_PRE_TRANSFORM) != 0u) - { - pos = vec4(u_pre_transform * pos, 1.0); - } - - if ((u_flag_mask & O_INSTANCED) != 0u) - { - pos = vec4(transpose(in_instance_transform) * pos, 1.0); - } - - if ((u_flag_mask & O_POST_TRANSFORM) != 0u) - { - pos = vec4(u_post_transform * pos, 1.0); - } - - gl_Position = u_light_transform * pos; + pos = vec4(u_pre_transform * pos, 1.0); } - )"; - static char const shadow_builder_gs[] = - R"(#version 330 - - uniform mat4 u_layer_transform[6]; - - layout (triangles) in; - layout (triangle_strip, max_vertices = 18) out; - - void main() + if ((u_flag_mask & O_INSTANCED) != 0u) { - for (int face = 0; face < 6; ++face) - { - gl_Layer = face; - for (int i = 0; i < 3; ++i) - { - gl_Position = u_layer_transform[face] * gl_in[i].gl_Position; - EmitVertex(); - } - EndPrimitive(); - } + pos = vec4(transpose(in_instance_transform) * pos, 1.0); } - )"; - static char const shadow_builder_fs[] = - R"(#version 330 - void main(){} - )"; + if ((u_flag_mask & O_POST_TRANSFORM) != 0u) + { + pos = vec4(u_post_transform * pos, 1.0); + } + + gl_Position = u_light_transform * pos; +} +)"; + +static char const shadow_builder_gs[] = +R"(#version 330 + +uniform mat4 u_layer_transform[6]; + +layout (triangles) in; +layout (triangle_strip, max_vertices = 18) out; + +void main() +{ + for (int face = 0; face < 6; ++face) + { + gl_Layer = face; + for (int i = 0; i < 3; ++i) + { + gl_Position = u_layer_transform[face] * gl_in[i].gl_Position; + EmitVertex(); + } + EndPrimitive(); + } +} +)"; + +static char const shadow_builder_fs[] = +R"(#version 330 +void main(){} +)"; static char const fullscreen_vs[] = R"(#version 330 @@ -337,6 +343,8 @@ vec3 unpack_normal(uint v) R"( uniform vec3 u_ambient; +uniform sampler2D u_ssao_texture; +uniform int u_use_ssao; uniform float u_max_intensity; @@ -348,6 +356,8 @@ void main() if (albedo.a < 0.5) color = albedo.rgb; + else if (u_use_ssao == 1) + color = albedo.rgb * u_ambient * texture(u_ssao_texture, texcoord).r; else color = albedo.rgb * u_ambient; @@ -582,6 +592,58 @@ void main() out_color = vec4(albedo.rgb / u_max_intensity, albedo.a); } +)"; + + static char const ssao_pass_fs[] = +R"( + +uniform sampler2D u_ssao_rotation; +uniform vec2 u_ssao_rotation_step; + +uniform float u_ssao_radius; + +uniform vec3 u_ssao_kernel[16]; + +uniform mat4 u_view; +uniform mat4 u_transform; + +void main() +{ + vec4 albedo = texture(u_g1, texcoord); + if (albedo.a < 0.5) + { + out_color = vec4(1.0); + return; + } + + vec3 position = texture(u_g0, texcoord).xyz; + vec3 normal = unpack_normal(texture(u_g2, texcoord).r); + + vec3 tangent = texture(u_ssao_rotation, texcoord * u_ssao_rotation_step).xyz; + tangent = normalize(tangent - normal * dot(normal, tangent)); + vec3 bitangent = cross(normal, tangent); + + mat3 tbn = mat3(tangent, bitangent, normal); + + float occlusion = 0.0; + for (int i = 0; i < 16; ++i) + { + vec3 sample = position + (tbn * u_ssao_kernel[i]) * u_ssao_radius; + + vec4 clip = u_transform * vec4(sample, 1.0); + clip.xy /= clip.w; + clip.xy = clip.xy * 0.5 + vec2(0.5); + + vec3 pos = texture(u_g0, clip.xy).xyz; + + float w = length(pos - position) / u_ssao_radius; + + if (w <= 1.0 && dot(pos - position, normal) >= 0.f && (u_view * vec4(pos, 1.0)).z > (u_view * vec4(sample, 1.0)).z) + occlusion += 1.0 / 16.0; + } + + out_color = vec4(1.0 - occlusion); +} )"; static std::size_t bbox_to_screen_fan(geom::matrix const & camera_transform, geom::box const & b, geom::point * result) @@ -645,6 +707,7 @@ void main() 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}; + gfx::program ssao_pass_program{fullscreen_vs, std::string(light_common) + ssao_pass_fs}; // G-buffer attachments: // 0 - position (rbg) @@ -680,6 +743,20 @@ void main() gfx::framebuffer bloom_framebuffer[3]; gfx::texture_2d bloom_texture[3]; + gfx::texture_2d ssao_rotation_texture; + + std::optional> ssao_size; + std::optional ssao_downsample; + + // 0: original, unblurred ssao + // 1: horizontally blurred ssao + // 2: fully blurred ssao + gfx::framebuffer ssao_framebuffer[3]; + gfx::texture_2d ssao_texture[3]; + + hblur ssao_hblur{2, 2.f}; + vblur ssao_vblur{2, 2.f}; + gfx::mesh screen_mesh; }; @@ -701,6 +778,7 @@ void main() impl().ambient_pass_program["u_g1"] = 1; impl().ambient_pass_program["u_g2"] = 2; impl().ambient_pass_program["u_g3"] = 3; + impl().ambient_pass_program["u_ssao_texture"] = 4; impl().directional_light_pass_program.bind(); impl().directional_light_pass_program["u_g0"] = 0; @@ -735,12 +813,57 @@ 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(); + for (std::size_t i = 0; i < 3; ++i) + { + impl().bloom_texture[i].linear_filter(); + impl().bloom_texture[i].clamp(); + } + + { + // params are random + random::generator rng{0xaa8e8eabull, 0x44a700e7ull}; + + { + random::uniform_hemiball_vector_distribution d{geom::vector{0.f, 0.f, 1.f}}; + std::vector> ssao_kernel(16); + for (auto & v : ssao_kernel) + v = d(rng); + + impl().ssao_pass_program.bind(); + for (std::size_t i = 0; i < ssao_kernel.size(); ++i) + { + impl().ssao_pass_program[util::to_string("u_ssao_kernel[", i, "]").data()] = ssao_kernel[i]; + } + } + + { + random::uniform_sphere_vector_distribution d; + + basic_pixmap> pm({8, 8}); + for (auto & v : pm) + v = d(rng); + + impl().ssao_rotation_texture.load(pm); + impl().ssao_rotation_texture.nearest_filter(); + impl().ssao_rotation_texture.repeat(); + } + } + + for (std::size_t i = 0; i < 3; ++i) + { + impl().ssao_texture[i].linear_filter(); + impl().ssao_texture[i].clamp(); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_G, gl::RED); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_B, gl::RED); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_A, gl::RED); + } + + impl().ssao_pass_program.bind(); + impl().ssao_pass_program["u_g0"] = 0; + impl().ssao_pass_program["u_g1"] = 1; + impl().ssao_pass_program["u_g2"] = 2; + impl().ssao_pass_program["u_g3"] = 3; + impl().ssao_pass_program["u_ssao_rotation"] = 4; impl().screen_mesh.setup>(); } @@ -873,6 +996,20 @@ void main() impl().bloom_downsample = opts.bloom->downsample; } + bool const ssao_size_changed = opts.ssao && (!impl().ssao_size || *impl().ssao_size != buffer_size || !impl().ssao_downsample || *impl().ssao_downsample != opts.ssao->downsample); + if (ssao_size_changed) + { + for (std::size_t i = 0; i < 3; ++i) + { + impl().ssao_texture[i].load(buffer_size / opts.ssao->downsample); + impl().ssao_framebuffer[i].color(impl().ssao_texture[i]); + impl().ssao_framebuffer[i].assert_complete(); + } + + impl().ssao_size = buffer_size; + impl().ssao_downsample = opts.ssao->downsample; + } + std::optional> hblur_params; std::optional> vblur_params; @@ -1014,104 +1151,111 @@ 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) + if (opts.bloom) { - std::uint32_t mask = p.first; + impl().bloom_framebuffer[0].bind(); - if (!(mask & O_BLOOMING)) - continue; + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); - if (p.second.empty()) continue; + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(gl::LEQUAL); - impl().bloom_pass_program["u_flag_mask"] = mask; + gl::Disable(gl::BLEND); - for (std::size_t i : p.second) + 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) { - auto const & o = objects[i]; + std::uint32_t mask = p.first; - if (mask & O_UNIFORM_COLOR) - impl().bloom_pass_program["u_color"] = *o.mat.color; + if (!(mask & O_BLOOMING)) + continue; - if (mask & O_TEXTURE_COLOR) + if (p.second.empty()) continue; + + impl().bloom_pass_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) { - gl::ActiveTexture(gl::TEXTURE0); - o.mat.texture->bind(); + 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(); } - - 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) + if (opts.bloom) { - std::uint32_t mask = p.first; + impl().transparent_pass_program.bind(); + impl().transparent_pass_program["u_camera_transform"] = camera_transform; + impl().transparent_pass_program["u_max_intensity"] = 1.f; - if (!(mask & O_TRANSPARENT)) - continue; + gl::Enable(gl::BLEND); + gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ZERO, gl::ONE); - if (p.second.empty()) continue; - - impl().transparent_pass_program["u_flag_mask"] = mask; - - for (std::size_t i : p.second) + for (auto const & p : objects_by_mask) { - auto const & o = objects[i]; + std::uint32_t mask = p.first; - if (mask & O_UNIFORM_COLOR) - impl().transparent_pass_program["u_color"] = *o.mat.color; + if (!(mask & O_TRANSPARENT)) + continue; - if (mask & O_TEXTURE_COLOR) + if (p.second.empty()) continue; + + impl().transparent_pass_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) { - gl::ActiveTexture(gl::TEXTURE0); - o.mat.texture->bind(); + 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(); } - - 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); + gl::DepthMask(gl::TRUE); + gl::Disable(gl::BLEND); + } // Apply horizontal blur to bloom + if (opts.bloom) { render_target target; target.framebuffer = &impl().bloom_framebuffer[1]; @@ -1127,6 +1271,7 @@ void main() // Apply vertical blur to bloom + if (opts.bloom) { render_target target; target.framebuffer = &impl().bloom_framebuffer[2]; @@ -1140,12 +1285,7 @@ void main() impl().vblur_container.at(*vblur_params).invoke(impl().bloom_texture[1], target); } - // Setup destination framebuffer - - target.bind(); - - gl::Disable(gl::DEPTH_TEST); - gl::Disable(gl::BLEND); + // Bind g-buffer textures gl::ActiveTexture(gl::TEXTURE0); impl().g_buffer_texture[0].bind(); @@ -1156,6 +1296,68 @@ void main() gl::ActiveTexture(gl::TEXTURE3); impl().g_buffer_texture[3].bind(); + gl::Disable(gl::DEPTH_TEST); + gl::Disable(gl::BLEND); + + // Generate SSAO + + if (opts.ssao) + { + impl().ssao_framebuffer[0].bind(); + gl::Viewport(0, 0, impl().ssao_texture[0].width(), impl().ssao_texture[0].height()); + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + impl().ssao_pass_program.bind(); + gl::ActiveTexture(gl::TEXTURE4); + impl().ssao_rotation_texture.bind(); + impl().ssao_pass_program["u_ssao_rotation_step"] = geom::cast(impl().ssao_texture[0].size()) / (1.f * impl().ssao_rotation_texture.width()); + impl().ssao_pass_program["u_ssao_radius"] = opts.ssao->radius; + impl().ssao_pass_program["u_transform"] = camera_transform; + impl().ssao_pass_program["u_view"] = opts.camera->view(); + + gl::DrawArrays(gl::TRIANGLES, 0, 6); + } + + // Blur ssao horizontally + + if (opts.ssao) + { + render_target target; + target.framebuffer = &impl().ssao_framebuffer[1]; + target.draw_buffer = gl::COLOR_ATTACHMENT0; + target.viewport = {{{0, impl().ssao_texture[1].width()}, {0, impl().ssao_texture[1].height()}}}; + + target.bind(); + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + impl().ssao_hblur.invoke(impl().ssao_texture[0], target); + } + + // Blur ssao vertically + + if (opts.ssao) + { + render_target target; + target.framebuffer = &impl().ssao_framebuffer[2]; + target.draw_buffer = gl::COLOR_ATTACHMENT0; + target.viewport = {{{0, impl().ssao_texture[2].width()}, {0, impl().ssao_texture[2].height()}}}; + + target.bind(); + gl::ClearColor(0.f, 0.f, 0.f, 0.f); + gl::Clear(gl::COLOR_BUFFER_BIT); + + impl().ssao_vblur.invoke(impl().ssao_texture[1], target); + } + + // Setup destination framebuffer + + gl::ActiveTexture(gl::TEXTURE0); + impl().g_buffer_texture[0].bind(); + + target.bind(); + impl().screen_mesh.bind(); // Draw unlit & ambient layers @@ -1163,6 +1365,16 @@ void main() impl().ambient_pass_program.bind(); impl().ambient_pass_program["u_ambient"] = opts.ambient; impl().ambient_pass_program["u_max_intensity"] = opts.max_intensity; + if (opts.ssao) + { + gl::ActiveTexture(gl::TEXTURE4); + impl().ssao_texture[2].bind(); + impl().ambient_pass_program["u_use_ssao"] = 1; + } + else + { + impl().ambient_pass_program["u_use_ssao"] = 0; + } gl::DrawArrays(gl::TRIANGLES, 0, 6); // Directional lights