From 68cdd22083276752661cf803eb2947bcbddefb1a Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sat, 12 Dec 2020 16:19:01 +0300 Subject: [PATCH] Deferred renderer: sort objects into a grid & use grid-based occlusion queries --- .../include/psemek/gfx/renderer/deferred.hpp | 2 + libs/gfx/source/renderer/deferred.cpp | 267 +++++++++++++++--- 2 files changed, 225 insertions(+), 44 deletions(-) diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp index 8184c65a..79e47b76 100644 --- a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -115,6 +115,8 @@ namespace psemek::gfx }; std::optional ssao; + + geom::vector grid_size{1, 1, 1}; }; 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 940ba296..204409b7 100644 --- a/libs/gfx/source/renderer/deferred.cpp +++ b/libs/gfx/source/renderer/deferred.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include @@ -648,6 +650,57 @@ void main() } )"; + char const occlusion_pass_vs[] = +R"(#version 330 + +uniform mat4 u_camera_transform; +uniform vec3 u_box_min; +uniform vec3 u_box_max; + +vec3 vertices[8] = vec3[8]( + vec3(0.0, 0.0, 0.0), + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(1.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0), + vec3(1.0, 0.0, 1.0), + vec3(0.0, 1.0, 1.0), + vec3(1.0, 1.0, 1.0) + ); + +ivec3 faces[12] = ivec3[12]( + // -Z + ivec3(0, 2, 1), + ivec3(1, 2, 3), + // +Z + ivec3(4, 5, 6), + ivec3(6, 5, 7), + // -Y + ivec3(0, 1, 4), + ivec3(4, 1, 5), + // +Y + ivec3(2, 6, 3), + ivec3(6, 7, 3), + // -X + ivec3(0, 4, 2), + ivec3(2, 4, 6), + // +X + ivec3(1, 3, 5), + ivec3(3, 7, 5) + ); + +void main() +{ + ivec3 face = faces[gl_VertexID / 3]; + int i = gl_VertexID % 3; + vec3 pos = vertices[(i == 0) ? face.x : (i == 1) ? face.y : face.z]; + + gl_Position = u_camera_transform * vec4(pos * (u_box_max - u_box_min) + u_box_min, 1.0); +} +)"; + + char const occlusion_pass_fs[] = R"( void main(){} )"; + static std::size_t bbox_to_screen_fan(geom::matrix const & camera_transform, geom::box const & b, geom::point * result) { geom::point bbox_corners_screen[8]; @@ -701,6 +754,7 @@ void main() deferred_renderer::position_mode position_mode = deferred_renderer::position_mode::float32; bool position_mode_changed = true; + gfx::program occlusion_pass_program{occlusion_pass_vs, occlusion_pass_fs}; gfx::program g_buffer_pass_program{std::string(g_buffer_pass_common) + g_buffer_pass_vs, std::string(g_buffer_pass_common) + g_buffer_pass_fs}; gfx::program ambient_pass_program{fullscreen_vs, std::string(light_common) + ambient_pass_fs}; gfx::program directional_light_pass_program{screen_vs, std::string(light_common) + directional_light_pass_fs}; @@ -718,6 +772,7 @@ void main() // 3 - material.diffuse (r), material.specular (g), material.shininess (b) gfx::framebuffer g_framebuffer; + gfx::framebuffer occlusion_framebuffer; gfx::texture_2d g_buffer_texture[4]; gfx::texture_2d g_buffer_depth; @@ -760,6 +815,9 @@ void main() vblur ssao_vblur{2, 2.f}; gfx::mesh screen_mesh; + + query_array queries; + gfx::array box_array; }; static int const shadow_map_size = 1024; @@ -905,6 +963,13 @@ void main() void deferred_renderer::render(std::vector const & objects, render_target const & target, options const & opts) { + // Allocate query objects + + std::size_t const bins_total = opts.grid_size[0] * opts.grid_size[1] * opts.grid_size[2]; + + if (impl().queries.size() < bins_total) + impl().queries.resize(bins_total); + // Get camera info assert(opts.camera); @@ -912,6 +977,21 @@ void main() auto const camera_position = opts.camera->position(); auto const camera_direction = opts.camera->direction(); + geom::point camera_frustum_vertices[8]; + + for (int i = 0; i < 8; ++i) + { + geom::vector p; + p[0] = (i & 1) ? 1.f : -1.f; + p[1] = (i & 2) ? 1.f : -1.f; + p[2] = (i & 4) ? 1.f : -1.f; + p[3] = 1.f; + + geom::gauss(camera_transform, p); + + camera_frustum_vertices[i] = geom::as_point(p); + } + // Sort objects by mask & compute bbox struct objects_bucket @@ -920,13 +1000,36 @@ void main() std::size_t first_visible; }; - std::unordered_map, objects_bucket> buckets; + struct bin + { + std::unordered_map, objects_bucket> buckets; + geom::box bbox; + bool contains_near_clip = false; + float camera_distance = -std::numeric_limits::infinity(); + }; + + geom::box all_bbox; + std::vector camera_distance(objects.size()); + + for (std::size_t i = 0; i < objects.size(); ++i) + { + auto const & o = objects[i]; + all_bbox |= o.bbox; + + float dist = -std::numeric_limits::infinity(); + for (int c = 0; c < 8; ++c) + { + dist = std::max(dist, dot(o.bbox.corner(c & 1, (c & 2) >> 1, (c & 4) >> 1) - camera_position, camera_direction)); + } + + camera_distance[i] = dist; + } + + util::array bins({opts.grid_size[0], opts.grid_size[1], opts.grid_size[2]}); geom::box lit_bbox; geom::box casts_shadow_bbox; - std::vector camera_distance(objects.size()); - for (std::size_t i = 0; i < objects.size(); ++i) { auto const & o = objects[i]; @@ -941,63 +1044,99 @@ void main() if (o.mat->casts_shadow && o.mat->transparent) throw std::runtime_error("Transparent objects cannot cast shadow"); - buckets[std::tuple{mask(o), o.mat}].objects.push_back(i); + auto c = o.bbox.center() - all_bbox.corner(0, 0, 0); + + int bx = geom::clamp(std::floor(c[0] / all_bbox[0].length() * opts.grid_size[0]), {0, opts.grid_size[0] - 1}); + int by = geom::clamp(std::floor(c[1] / all_bbox[1].length() * opts.grid_size[1]), {0, opts.grid_size[1] - 1}); + int bz = geom::clamp(std::floor(c[2] / all_bbox[2].length() * opts.grid_size[2]), {0, opts.grid_size[2] - 1}); + + bin & b = bins(bx, by, bz); + + b.bbox |= o.bbox; + b.buckets[std::tuple{mask(o), o.mat}].objects.push_back(i); if (o.mat->lit) lit_bbox |= o.bbox; if (o.mat->casts_shadow) casts_shadow_bbox |= o.bbox; + } + + for (auto & b : bins) + { + b.bbox = geom::expand(b.bbox, b.bbox.dimensions() / 64.f); float dist = -std::numeric_limits::infinity(); for (int c = 0; c < 8; ++c) { - dist = std::max(dist, dot(o.bbox.corner(c & 1, (c & 2) >> 1, (c & 4) >> 1) - camera_position, camera_direction)); + dist = std::max(dist, dot(b.bbox.corner(c & 1, (c & 2) >> 1, (c & 4) >> 1) - camera_position, camera_direction)); } + b.camera_distance = dist; - camera_distance[i] = dist; + b.contains_near_clip = true; + for (int i = 0; i < 4; ++i) + b.contains_near_clip &= geom::contains(b.bbox, camera_frustum_vertices[i]); + + for (auto & p : b.buckets) + { + std::sort(p.second.objects.begin(), p.second.objects.end(), [&](auto i, auto j){ return camera_distance[i] < camera_distance[j]; }); + p.second.first_visible = std::partition_point(p.second.objects.begin(), p.second.objects.end(), [&](auto i){ return camera_distance[i] < 0.f; }) - p.second.objects.begin(); + } } - for (auto & p : buckets) - { - std::sort(p.second.objects.begin(), p.second.objects.end(), [&](auto i, auto j){ return camera_distance[i] < camera_distance[j]; }); - p.second.first_visible = std::partition_point(p.second.objects.begin(), p.second.objects.end(), [&](auto i){ return camera_distance[i] < 0.f; }) - p.second.objects.begin(); - } + bool use_occlusion = false; auto render_all = [&](auto & program, auto && predicate, bool clip_by_camera = false) { - for (auto const & p : buckets) + for (int bi = 0; bi < bins_total; ++bi) { - if (p.second.objects.empty()) continue; + auto const & b = bins.data()[bi]; - std::uint32_t mask = std::get<0>(p.first); + if (clip_by_camera && b.camera_distance < 0.f) continue; - if (!predicate(mask)) continue; + bool const bin_use_occlusion = clip_by_camera && !b.contains_near_clip && !b.buckets.empty() && use_occlusion; - material const * mat = std::get<1>(p.first); + if (bin_use_occlusion) + gl::BeginConditionalRender(impl().queries[bi], gl::QUERY_NO_WAIT); - program["u_flag_mask"] = mask; - - if (mask & O_UNIFORM_COLOR) - program["u_color"] = *(mat->color); - - if (mask & O_TEXTURE_COLOR) + for (auto const & p : b.buckets) { - gl::ActiveTexture(gl::TEXTURE0); - mat->texture->bind(); + if (p.second.objects.empty()) continue; + + std::uint32_t mask = std::get<0>(p.first); + + if (!predicate(mask)) continue; + + material const * mat = std::get<1>(p.first); + + program["u_flag_mask"] = mask; + + if (mask & O_UNIFORM_COLOR) + program["u_color"] = *(mat->color); + + if (mask & O_TEXTURE_COLOR) + { + gl::ActiveTexture(gl::TEXTURE0); + mat->texture->bind(); + } + + program["u_material"] = geom::vector{mat->specular.intensity, mat->specular.shininess}; + + for (std::size_t i = clip_by_camera ? p.second.first_visible : 0; i < p.second.objects.size(); ++i) + { + std::size_t id = p.second.objects[i]; + + auto const & o = objects[id]; + + if (mask & O_PRE_TRANSFORM) + program["u_pre_transform"] = *o.pre_transform; + + if (mask & O_POST_TRANSFORM) + program["u_post_transform"] = *o.post_transform; + + o.mesh->draw(); + } } - program["u_material"] = geom::vector{mat->specular.intensity, mat->specular.shininess}; - - for (std::size_t i = (clip_by_camera ? p.second.first_visible : 0); i < p.second.objects.size(); ++i) - { - auto const & o = objects[p.second.objects[i]]; - - if (mask & O_PRE_TRANSFORM) - program["u_pre_transform"] = *o.pre_transform; - - if (mask & O_POST_TRANSFORM) - program["u_post_transform"] = *o.post_transform; - - o.mesh->draw(); - } + if (bin_use_occlusion) + gl::EndConditionalRender(); } }; @@ -1020,16 +1159,16 @@ void main() impl().g_buffer_depth.load(buffer_size); } - if (!impl().g_buffer_size) + for (std::size_t i = 0; i < 4; ++i) { - for (std::size_t i = 0; i < 4; ++i) - { - impl().g_framebuffer.color(impl().g_buffer_texture[i], i); - } - impl().g_framebuffer.depth(impl().g_buffer_depth); + impl().g_framebuffer.color(impl().g_buffer_texture[i], i); } + impl().g_framebuffer.depth(impl().g_buffer_depth); + impl().occlusion_framebuffer.depth(impl().g_buffer_depth); + impl().g_framebuffer.assert_complete(); + impl().occlusion_framebuffer.assert_complete(); impl().transparent_framebuffer.color(impl().g_buffer_texture[1]); impl().transparent_framebuffer.depth(impl().g_buffer_depth); @@ -1093,6 +1232,46 @@ void main() } } + // Occlusion pre-pass + + if (!buffer_size_changed) + { + use_occlusion = true; + + impl().occlusion_framebuffer.bind(); + + gl::Viewport(0, 0, target.viewport[0].length(), target.viewport[1].length()); + + gl::DepthMask(gl::FALSE); + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(gl::LEQUAL); + gl::Enable(gl::DEPTH_CLAMP); + + gl::Disable(gl::CULL_FACE); + + impl().occlusion_pass_program.bind(); + impl().occlusion_pass_program["u_camera_transform"] = camera_transform; + impl().box_array.bind(); + + for (int bi = 0; bi < bins_total; ++bi) + { + auto & b = bins.data()[bi]; + + if (b.buckets.empty()) continue; + if (b.contains_near_clip) continue; + if (b.camera_distance < 0.f) continue; + + impl().occlusion_pass_program["u_box_min"] = geom::vector{b.bbox[0].min, b.bbox[1].min, b.bbox[2].min}; + impl().occlusion_pass_program["u_box_max"] = geom::vector{b.bbox[0].max, b.bbox[1].max, b.bbox[2].max}; + + auto query_scope = impl().queries.begin(bi, gl::ANY_SAMPLES_PASSED); + gl::DrawArrays(gl::TRIANGLES, 0, 36); + } + + gl::DepthMask(gl::TRUE); + gl::Disable(gl::DEPTH_CLAMP); + } + // Setup g-buffer impl().g_framebuffer.bind();