Deferred renderer: sort objects into a grid & use grid-based occlusion queries
This commit is contained in:
parent
fdef1d6018
commit
68cdd22083
2 changed files with 225 additions and 44 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue