Deferred renderer: implement cascaded shadow maps
This commit is contained in:
parent
65bab34304
commit
4dbfa40d77
2 changed files with 174 additions and 49 deletions
|
|
@ -71,6 +71,7 @@ namespace psemek::gfx
|
||||||
geom::vector<float, 3> direction;
|
geom::vector<float, 3> direction;
|
||||||
bool shadowed = true;
|
bool shadowed = true;
|
||||||
std::size_t shadow_map_size = 1024;
|
std::size_t shadow_map_size = 1024;
|
||||||
|
std::size_t cascades = 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct point_light
|
struct point_light
|
||||||
|
|
|
||||||
|
|
@ -378,9 +378,12 @@ R"(
|
||||||
uniform vec3 u_light_direction;
|
uniform vec3 u_light_direction;
|
||||||
uniform vec3 u_light_color;
|
uniform vec3 u_light_color;
|
||||||
|
|
||||||
uniform mat4 u_light_transform;
|
#define MAX_CASCADES 8
|
||||||
uniform sampler2DShadow u_shadow;
|
|
||||||
|
uniform mat4 u_light_transform[MAX_CASCADES];
|
||||||
|
uniform sampler2DArrayShadow u_shadow;
|
||||||
uniform int u_shadowed;
|
uniform int u_shadowed;
|
||||||
|
uniform int u_cascades;
|
||||||
|
|
||||||
uniform vec3 u_camera_position;
|
uniform vec3 u_camera_position;
|
||||||
|
|
||||||
|
|
@ -409,16 +412,22 @@ void main()
|
||||||
|
|
||||||
vec3 color = l * albedo.rgb * u_light_color;
|
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);
|
for (int cascade = 0; cascade < MAX_CASCADES; ++cascade)
|
||||||
|
{
|
||||||
|
if (cascade >= u_cascades) break;
|
||||||
|
|
||||||
|
vec4 shadow_space = u_light_transform[cascade] * vec4(position, 1.0);
|
||||||
|
|
||||||
vec3 tc = shadow_space.xyz / shadow_space.w;
|
vec3 tc = shadow_space.xyz / shadow_space.w;
|
||||||
tc = tc * 0.5 + vec3(0.5);
|
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)
|
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, tc);
|
color *= texture(u_shadow, vec4(tc.x, tc.y, float(cascade), tc.z));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -785,7 +794,7 @@ void main()
|
||||||
std::optional<geom::vector<std::size_t, 2>> g_buffer_size;
|
std::optional<geom::vector<std::size_t, 2>> g_buffer_size;
|
||||||
|
|
||||||
gfx::framebuffer directional_shadow_framebuffer;
|
gfx::framebuffer directional_shadow_framebuffer;
|
||||||
gfx::texture_2d directional_shadow_texture;
|
gfx::texture_2d_array directional_shadow_texture;
|
||||||
|
|
||||||
gfx::framebuffer point_shadow_framebuffer;
|
gfx::framebuffer point_shadow_framebuffer;
|
||||||
gfx::texture_cubemap point_shadow_texture;
|
gfx::texture_cubemap point_shadow_texture;
|
||||||
|
|
@ -857,13 +866,13 @@ void main()
|
||||||
|
|
||||||
impl().directional_shadow_texture.linear_filter();
|
impl().directional_shadow_texture.linear_filter();
|
||||||
impl().directional_shadow_texture.clamp();
|
impl().directional_shadow_texture.clamp();
|
||||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE);
|
gl::TexParameteri(impl().directional_shadow_texture.target, 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_FUNC, gl::LEQUAL);
|
||||||
|
|
||||||
impl().point_shadow_texture.linear_filter();
|
impl().point_shadow_texture.linear_filter();
|
||||||
impl().point_shadow_texture.clamp();
|
impl().point_shadow_texture.clamp();
|
||||||
gl::TexParameteri(gl::TEXTURE_CUBE_MAP, gl::TEXTURE_COMPARE_MODE, gl::COMPARE_REF_TO_TEXTURE);
|
gl::TexParameteri(impl().point_shadow_texture.target, 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_FUNC, gl::LEQUAL);
|
||||||
|
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
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_transform = opts.camera->transform();
|
||||||
auto const camera_position = opts.camera->position();
|
auto const camera_position = opts.camera->position();
|
||||||
auto const camera_direction = opts.camera->direction();
|
auto const camera_direction = opts.camera->direction();
|
||||||
|
auto const camera_clip_planes = opts.camera->clip_planes();
|
||||||
|
|
||||||
cg::frustum<float, 3> camera_frustum(camera_transform);
|
cg::frustum<float, 3> camera_frustum(camera_transform);
|
||||||
|
|
||||||
|
|
@ -1472,10 +1482,47 @@ void main()
|
||||||
|
|
||||||
for (auto const & l : opts.directional_lights)
|
for (auto const & l : opts.directional_lights)
|
||||||
{
|
{
|
||||||
geom::matrix<float, 4, 4> light_transform;
|
std::vector<geom::matrix<float, 4, 4>> light_transform;
|
||||||
|
|
||||||
if (l.shadowed)
|
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<geom::interval<float>> 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<float, 3>::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<depth24_pixel>({l.shadow_map_size, l.shadow_map_size, l.cascades});
|
||||||
|
}
|
||||||
|
|
||||||
geom::vector<float, 3> light_axes[3];
|
geom::vector<float, 3> light_axes[3];
|
||||||
|
|
||||||
light_axes[2] = -geom::normalized(l.direction);
|
light_axes[2] = -geom::normalized(l.direction);
|
||||||
|
|
@ -1488,27 +1535,63 @@ void main()
|
||||||
|
|
||||||
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
|
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
|
||||||
|
|
||||||
|
for (int cascade = 0; cascade < l.cascades; ++cascade)
|
||||||
|
{
|
||||||
|
geom::box<float, 3> shadowed_bbox;
|
||||||
|
|
||||||
|
geom::matrix<float, 4, 4> cascade_transform = camera_transform;
|
||||||
|
|
||||||
|
{
|
||||||
|
float near = cascade_splits[cascade].max;
|
||||||
|
float far = cascade_splits[cascade].min;
|
||||||
|
|
||||||
|
geom::vector<float, 2> b;
|
||||||
|
b[0] = -2.f * cascade_transform[3][dir_max_index];
|
||||||
|
b[1] = -2.f * cascade_transform[3][3];
|
||||||
|
|
||||||
|
geom::matrix<float, 2, 2> 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<float, 3>::zero();
|
||||||
|
|
||||||
geom::box<float, 3> light_bbox;
|
geom::box<float, 3> light_bbox;
|
||||||
|
|
||||||
geom::point<float, 3> origin = geom::point<float, 3>::zero();
|
for (auto const & v : geom::vertices(shadowed_bbox))
|
||||||
|
|
||||||
for (auto const & v : geom::vertices(casts_shadow_bbox))
|
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
|
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
light_transform = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2]));
|
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<float, 3> light_frustum{light_transform[cascade]};
|
||||||
|
|
||||||
impl().directional_shadow_framebuffer.bind();
|
impl().directional_shadow_framebuffer.bind();
|
||||||
gl::DrawBuffer(gl::NONE);
|
gl::DrawBuffer(gl::NONE);
|
||||||
|
|
||||||
if (impl().directional_shadow_texture.width() != l.shadow_map_size)
|
impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture, cascade);
|
||||||
{
|
|
||||||
impl().directional_shadow_texture.load<depth24_pixel>({l.shadow_map_size, l.shadow_map_size});
|
|
||||||
impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture);
|
|
||||||
impl().directional_shadow_framebuffer.assert_complete();
|
impl().directional_shadow_framebuffer.assert_complete();
|
||||||
}
|
|
||||||
|
|
||||||
gl::Viewport(0, 0, impl().directional_shadow_texture.width(), impl().directional_shadow_texture.height());
|
gl::Viewport(0, 0, impl().directional_shadow_texture.width(), impl().directional_shadow_texture.height());
|
||||||
|
|
||||||
|
|
@ -1523,9 +1606,43 @@ void main()
|
||||||
gl::Disable(gl::BLEND);
|
gl::Disable(gl::BLEND);
|
||||||
|
|
||||||
impl().shadow_builder_program.bind();
|
impl().shadow_builder_program.bind();
|
||||||
impl().shadow_builder_program["u_light_transform"] = light_transform;
|
impl().shadow_builder_program["u_light_transform"] = light_transform[cascade];
|
||||||
|
|
||||||
render_all(impl().shadow_builder_program, [](auto mask){ return (mask & O_CASTS_SHADOW); });
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target.bind();
|
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_direction"] = geom::normalized(l.direction);
|
||||||
impl().directional_light_pass_program["u_light_color"] = l.color;
|
impl().directional_light_pass_program["u_light_color"] = l.color;
|
||||||
impl().directional_light_pass_program["u_shadowed"] = l.shadowed;
|
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<int>(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();
|
impl().screen_mesh.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue