From 4dbfa40d779c7d1e1f7619c6e84ef0477560dff0 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 13 Dec 2020 00:48:18 +0300 Subject: [PATCH] Deferred renderer: implement cascaded shadow maps --- .../include/psemek/gfx/renderer/deferred.hpp | 1 + libs/gfx/source/renderer/deferred.cpp | 222 ++++++++++++++---- 2 files changed, 174 insertions(+), 49 deletions(-) diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp index 6df03e40..4e2bbdd7 100644 --- a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -71,6 +71,7 @@ namespace psemek::gfx geom::vector direction; bool shadowed = true; std::size_t shadow_map_size = 1024; + std::size_t cascades = 4; }; struct point_light diff --git a/libs/gfx/source/renderer/deferred.cpp b/libs/gfx/source/renderer/deferred.cpp index f2511c4d..abab8e7f 100644 --- a/libs/gfx/source/renderer/deferred.cpp +++ b/libs/gfx/source/renderer/deferred.cpp @@ -378,9 +378,12 @@ R"( uniform vec3 u_light_direction; uniform vec3 u_light_color; -uniform mat4 u_light_transform; -uniform sampler2DShadow u_shadow; +#define MAX_CASCADES 8 + +uniform mat4 u_light_transform[MAX_CASCADES]; +uniform sampler2DArrayShadow u_shadow; uniform int u_shadowed; +uniform int u_cascades; uniform vec3 u_camera_position; @@ -409,16 +412,22 @@ void main() vec3 color = l * albedo.rgb * u_light_color; - if (u_shadowed != 0) + if (u_shadowed != 0 && d > 0.0) { - vec4 shadow_space = u_light_transform * vec4(position, 1.0); - - vec3 tc = shadow_space.xyz / shadow_space.w; - tc = tc * 0.5 + vec3(0.5); - - if (d > 0.0 && tc.x >= 0.0 && tc.x <= 1.0 && tc.y >= 0.0 && tc.y <= 1.0 && tc.z >= 0.0) + for (int cascade = 0; cascade < MAX_CASCADES; ++cascade) { - color *= texture(u_shadow, tc); + if (cascade >= u_cascades) break; + + vec4 shadow_space = u_light_transform[cascade] * vec4(position, 1.0); + + vec3 tc = shadow_space.xyz / shadow_space.w; + tc = tc * 0.5 + vec3(0.5); + + if (tc.x >= 0.0 && tc.x <= 1.0 && tc.y >= 0.0 && tc.y <= 1.0 && tc.z >= 0.0) + { + color *= texture(u_shadow, vec4(tc.x, tc.y, float(cascade), tc.z)); + break; + } } } @@ -785,7 +794,7 @@ void main() std::optional> g_buffer_size; gfx::framebuffer directional_shadow_framebuffer; - gfx::texture_2d directional_shadow_texture; + gfx::texture_2d_array directional_shadow_texture; gfx::framebuffer point_shadow_framebuffer; gfx::texture_cubemap point_shadow_texture; @@ -857,13 +866,13 @@ void main() impl().directional_shadow_texture.linear_filter(); impl().directional_shadow_texture.clamp(); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); + gl::TexParameteri(impl().directional_shadow_texture.target, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); + gl::TexParameteri(impl().directional_shadow_texture.target, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); impl().point_shadow_texture.linear_filter(); impl().point_shadow_texture.clamp(); - gl::TexParameteri(gl::TEXTURE_CUBE_MAP, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); - gl::TexParameteri(gl::TEXTURE_CUBE_MAP, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); + gl::TexParameteri(impl().point_shadow_texture.target, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE); + gl::TexParameteri(impl().point_shadow_texture.target, gl::TEXTURE_COMPARE_FUNC, gl::LEQUAL); for (std::size_t i = 0; i < 3; ++i) { @@ -968,6 +977,7 @@ void main() auto const camera_transform = opts.camera->transform(); auto const camera_position = opts.camera->position(); auto const camera_direction = opts.camera->direction(); + auto const camera_clip_planes = opts.camera->clip_planes(); cg::frustum camera_frustum(camera_transform); @@ -1472,10 +1482,47 @@ void main() for (auto const & l : opts.directional_lights) { - geom::matrix light_transform; + std::vector> light_transform; if (l.shadowed) { + if (l.cascades == 0) + throw std::runtime_error("The number of shadow map cascades should be positive"); + if (l.cascades > 8) + throw std::runtime_error("More than 8 shadow map cascades are not supported"); + + light_transform.resize(l.cascades); + + // Compute cascade split points + + auto dir = geom::as_vector(camera_clip_planes[4]); + std::vector> cascade_splits(l.cascades); + float offset; + { + auto dir_length = geom::length(dir); + float near = camera_clip_planes[4][3] / dir_length; + dir /= dir_length; + offset = geom::dot(camera_position - geom::point::zero(), dir); + near += offset; + float far = -camera_clip_planes[5][3] / geom::length(geom::as_vector(camera_clip_planes[5])) - offset; + float lambda = 1.f / 24.f; + for (int i = 0; i < l.cascades; ++i) + { + cascade_splits[i].max = (1.f - lambda) * near * std::pow(far / near, i * 1.f / l.cascades) + lambda * (near + (far - near) * (i * 1.f / l.cascades)); + cascade_splits[i].min = (1.f - lambda) * near * std::pow(far / near, (i + 1) * 1.f / l.cascades) + lambda * (near + (far - near) * ((i + 1) * 1.f / l.cascades)); + cascade_splits[i] -= offset; + } + } + + std::size_t dir_max_index = 0; + if (std::abs(dir[1]) > std::abs(dir[dir_max_index])) dir_max_index = 1; + if (std::abs(dir[2]) > std::abs(dir[dir_max_index])) dir_max_index = 2; + + if (impl().directional_shadow_texture.width() != l.shadow_map_size || impl().directional_shadow_texture.depth() != l.cascades) + { + impl().directional_shadow_texture.load({l.shadow_map_size, l.shadow_map_size, l.cascades}); + } + geom::vector light_axes[3]; light_axes[2] = -geom::normalized(l.direction); @@ -1488,44 +1535,114 @@ void main() light_axes[0] = geom::cross(light_axes[2], light_axes[1]); - geom::box light_bbox; - - geom::point origin = geom::point::zero(); - - for (auto const & v : geom::vertices(casts_shadow_bbox)) + for (int cascade = 0; cascade < l.cascades; ++cascade) { - for (std::size_t i = 0; i < 3; ++i) - light_bbox[i] |= geom::dot(light_axes[i], v - origin); - } + geom::box shadowed_bbox; - light_transform = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2])); + geom::matrix cascade_transform = camera_transform; - impl().directional_shadow_framebuffer.bind(); - gl::DrawBuffer(gl::NONE); + { + float near = cascade_splits[cascade].max; + float far = cascade_splits[cascade].min; - if (impl().directional_shadow_texture.width() != l.shadow_map_size) - { - impl().directional_shadow_texture.load({l.shadow_map_size, l.shadow_map_size}); - impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture); + geom::vector b; + b[0] = -2.f * cascade_transform[3][dir_max_index]; + b[1] = -2.f * cascade_transform[3][3]; + + geom::matrix m; + m[0][0] = - dir[dir_max_index]; + m[0][1] = dir[dir_max_index]; + m[1][0] = -near; + m[1][1] = far; + + geom::gauss(m, b); + + for (std::size_t i = 0; i < 3; ++i) + cascade_transform[2][i] = dir[i] * (b[0] + b[1]) / 2.f; + cascade_transform[2][3] = (b[0] * near + b[1] * far) / 2.f; + } + + for (auto v : cg::vertices(cg::frustum{cascade_transform})) + shadowed_bbox |= v; + + shadowed_bbox &= casts_shadow_bbox; + + static auto const origin = geom::point::zero(); + + geom::box light_bbox; + + for (auto const & v : geom::vertices(shadowed_bbox)) + { + for (std::size_t i = 0; i < 3; ++i) + light_bbox[i] |= geom::dot(light_axes[i], v - origin); + } + + for (auto const & v : geom::vertices(casts_shadow_bbox)) + { +// light_bbox[2].min = std::min(light_bbox[2].min, geom::dot(light_axes[2], v - origin)); + light_bbox[2] |= geom::dot(light_axes[2], v - origin); + } + + light_transform[cascade] = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2])); + + cg::frustum light_frustum{light_transform[cascade]}; + + impl().directional_shadow_framebuffer.bind(); + gl::DrawBuffer(gl::NONE); + + impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture, cascade); impl().directional_shadow_framebuffer.assert_complete(); + + gl::Viewport(0, 0, impl().directional_shadow_texture.width(), impl().directional_shadow_texture.height()); + + gl::Clear(gl::DEPTH_BUFFER_BIT); + + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(gl::LEQUAL); + + gl::Enable(gl::CULL_FACE); + gl::CullFace(gl::FRONT); + + gl::Disable(gl::BLEND); + + impl().shadow_builder_program.bind(); + impl().shadow_builder_program["u_light_transform"] = light_transform[cascade]; + + auto & program = impl().shadow_builder_program; + + for (int bi = 0; bi < bins_total; ++bi) + { + auto const & b = bins.data()[bi]; + + if (cg::separation(light_frustum, cg::box{b.bbox}).second > 0.f) continue; + + for (auto const & p : b.buckets) + { + if (p.second.objects.empty()) continue; + + std::uint32_t mask = std::get<0>(p.first); + + if (!(mask & O_CASTS_SHADOW)) continue; + + program["u_flag_mask"] = mask; + + for (std::size_t i = 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(); + } + } + } } - - gl::Viewport(0, 0, impl().directional_shadow_texture.width(), impl().directional_shadow_texture.height()); - - gl::Clear(gl::DEPTH_BUFFER_BIT); - - gl::Enable(gl::DEPTH_TEST); - gl::DepthFunc(gl::LEQUAL); - - gl::Enable(gl::CULL_FACE); - gl::CullFace(gl::FRONT); - - gl::Disable(gl::BLEND); - - impl().shadow_builder_program.bind(); - impl().shadow_builder_program["u_light_transform"] = light_transform; - - render_all(impl().shadow_builder_program, [](auto mask){ return (mask & O_CASTS_SHADOW); }); } target.bind(); @@ -1542,7 +1659,14 @@ void main() impl().directional_light_pass_program["u_light_direction"] = geom::normalized(l.direction); impl().directional_light_pass_program["u_light_color"] = l.color; impl().directional_light_pass_program["u_shadowed"] = l.shadowed; - if (l.shadowed) impl().directional_light_pass_program["u_light_transform"] = light_transform; + if (l.shadowed) + { + impl().directional_light_pass_program["u_cascades"] = static_cast(l.cascades); + for (int i = 0; i < l.cascades; ++i) + { + impl().directional_light_pass_program[util::to_string("u_light_transform[", i, "]").data()] = light_transform[i]; + } + } impl().screen_mesh.draw(); }