Deferred renderer: sort objects into a grid & use grid-based occlusion queries

This commit is contained in:
Nikita Lisitsa 2020-12-12 16:19:01 +03:00
parent fdef1d6018
commit 68cdd22083
2 changed files with 225 additions and 44 deletions

View file

@ -115,6 +115,8 @@ namespace psemek::gfx
};
std::optional<ssao_data> ssao;
geom::vector<std::size_t, 3> grid_size{1, 1, 1};
};
void render(std::vector<object> const & objects, render_target const & target, options const & opts);

View file

@ -5,6 +5,7 @@
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/error.hpp>
#include <psemek/gfx/query.hpp>
#include <psemek/gfx/effect/blur.hpp>
#include <psemek/gfx/effect/overlay.hpp>
@ -16,6 +17,7 @@
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/contains.hpp>
#include <psemek/cg/convex_hull_2d/graham.hpp>
@ -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<float, 4, 4> const & camera_transform, geom::box<float, 3> const & b, geom::point<float, 2> * result)
{
geom::point<float, 2> 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<object> 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<float, 3> camera_frustum_vertices[8];
for (int i = 0; i < 8; ++i)
{
geom::vector<float, 4> 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<std::tuple<std::uint32_t, material const *>, objects_bucket> buckets;
struct bin
{
std::unordered_map<std::tuple<std::uint32_t, material const *>, objects_bucket> buckets;
geom::box<float, 3> bbox;
bool contains_near_clip = false;
float camera_distance = -std::numeric_limits<float>::infinity();
};
geom::box<float, 3> all_bbox;
std::vector<float> 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<float>::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<bin, 3> bins({opts.grid_size[0], opts.grid_size[1], opts.grid_size[2]});
geom::box<float, 3> lit_bbox;
geom::box<float, 3> casts_shadow_bbox;
std::vector<float> 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<float>::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<float, 2>{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<float, 2>{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<gfx::depth24_pixel>(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();