diff --git a/examples/deferred.cpp b/examples/deferred.cpp index a77b76d2..6de9f66a 100644 --- a/examples/deferred.cpp +++ b/examples/deferred.cpp @@ -337,6 +337,7 @@ void deferred_app::render() obj.mesh = &plane; obj.pre_transform = geom::scale(10.f).affine_matrix(); obj.bbox = {{{-10.f, 10.f}, {-10.f, 10.f}, {0.f, 0.f}}}; + obj.casts_shadow = false; objects.push_back(obj); } @@ -387,16 +388,19 @@ void deferred_app::render() options.point_lights[0].color = {20.f, 0.f, 0.f}; options.point_lights[0].position = {2.f * std::cos(time / 2.f), 2.f * std::sin(time / 2.f), 2.f}; options.point_lights[0].attenuation = {1.f, 0.1f, 0.05f}; + options.point_lights[0].min_shadow_distance = 0.1f; options.point_lights.emplace_back(); options.point_lights[1].color = {0.f, 0.f, 20.f}; - options.point_lights[1].position = {-6.f * std::cos(time / 2.f), -6.f * std::sin(time / 2.f), 2.f}; + options.point_lights[1].position = {-6.f * std::cos(time / 1.f), -6.f * std::sin(time / 1.f), 2.f}; options.point_lights[1].attenuation = {1.f, 0.1f, 0.05f}; + options.point_lights[1].min_shadow_distance = 0.1f; options.point_lights.emplace_back(); options.point_lights[2].color = {0.f, 20.f, 0.f}; options.point_lights[2].position = {0.f, 0.f, 4.f}; options.point_lights[2].attenuation = {1.f, 0.1f, 0.05f}; + options.point_lights[2].min_shadow_distance = 0.1f; for (int i = 0; i < 24; ++i) { @@ -406,6 +410,8 @@ void deferred_app::render() l.color = {15.f, 15.f, 15.f}; l.position = {std::cos(a) * 9.f, std::sin(a) * 9.f, 0.5f}; l.attenuation = {0.f, 0.f, 10.f}; + l.shadowed = false; + l.min_shadow_distance = 0.1f; } for (auto const & l : options.point_lights) diff --git a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp index 0155ed33..15bd4125 100644 --- a/libs/gfx/include/psemek/gfx/renderer/deferred.hpp +++ b/libs/gfx/include/psemek/gfx/renderer/deferred.hpp @@ -72,6 +72,7 @@ namespace psemek::gfx } attenuation; geom::point position; bool shadowed = true; + float min_shadow_distance; }; struct options diff --git a/libs/gfx/source/renderer/deferred.cpp b/libs/gfx/source/renderer/deferred.cpp index ad133102..a4448df7 100644 --- a/libs/gfx/source/renderer/deferred.cpp +++ b/libs/gfx/source/renderer/deferred.cpp @@ -7,9 +7,14 @@ #include #include +#include +#include +#include #include +#include + namespace psemek::gfx { @@ -173,6 +178,67 @@ void main() } )"; + static char const shadow_builder_vs[] = + R"( + uniform mat4 u_light_transform; + uniform mat4x3 u_pre_transform; + uniform mat4x3 u_post_transform; + + layout (location = 0) in vec4 in_position; + + layout (location = 4) in mat3x4 in_instance_transform; + + void main() + { + vec4 pos = in_position; + + if ((u_flag_mask & O_PRE_TRANSFORM) != 0u) + { + pos = vec4(u_pre_transform * pos, 1.0); + } + + if ((u_flag_mask & O_INSTANCED) != 0u) + { + pos = vec4(transpose(in_instance_transform) * pos, 1.0); + } + + if ((u_flag_mask & O_POST_TRANSFORM) != 0u) + { + pos = vec4(u_post_transform * pos, 1.0); + } + + gl_Position = u_light_transform * pos; + } + )"; + + static char const shadow_builder_gs[] = + R"(#version 330 + + uniform mat4 u_layer_transform[6]; + + layout (triangles) in; + layout (triangle_strip, max_vertices = 18) out; + + void main() + { + for (int face = 0; face < 6; ++face) + { + gl_Layer = face; + for (int i = 0; i < 3; ++i) + { + gl_Position = u_layer_transform[face] * gl_in[i].gl_Position; + EmitVertex(); + } + EndPrimitive(); + } + } + )"; + + static char const shadow_builder_fs[] = + R"(#version 330 + void main(){} + )"; + static char const fullscreen_vs[] = R"(#version 330 @@ -292,14 +358,24 @@ R"( uniform vec3 u_light_direction; uniform vec3 u_light_color; +uniform mat4 u_light_transform; +uniform sampler2DShadow u_shadow; +uniform int u_shadowed; + uniform vec3 u_camera_position; uniform float u_max_intensity; void main() { - vec3 position = texture(u_g0, texcoord).xyz; vec4 albedo = texture(u_g1, texcoord); + if (albedo.a < 0.5) + { + out_color = vec4(0.0); + return; + } + + vec3 position = texture(u_g0, texcoord).xyz; vec3 normal = unpack_normal(texture(u_g2, texcoord).r); vec3 material = texture(u_g3, texcoord).xyz; @@ -311,7 +387,20 @@ void main() float l = max(0.0, d) * material.x + pow(max(0.0, dot(view, refl)), material.z) * material.y; - vec3 color = l * albedo.rgb * u_light_color * albedo.a; + vec3 color = l * albedo.rgb * u_light_color; + + if (u_shadowed != 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) + { + color *= texture(u_shadow, tc); + } + } out_color = vec4(color, 1.0); } @@ -324,14 +413,26 @@ uniform vec3 u_light_position; uniform vec3 u_light_color; uniform vec3 u_light_attenuation; +uniform samplerCubeShadow u_shadow; +uniform int u_shadowed; +uniform vec3 u_shadow_far_negative; +uniform vec3 u_shadow_far_positive; +uniform float u_shadow_near; + uniform vec3 u_camera_position; uniform float u_max_intensity; void main() { - vec3 position = texture(u_g0, texcoord).xyz; vec4 albedo = texture(u_g1, texcoord); + if (albedo.a < 0.5) + { + out_color = vec4(0.0); + return; + } + + vec3 position = texture(u_g0, texcoord).xyz; vec3 normal = unpack_normal(texture(u_g2, texcoord).r); vec3 material = texture(u_g3, texcoord).xyz; @@ -340,15 +441,59 @@ void main() vec3 light = u_light_position - position; float r = length(light); - light /= r; - float d = dot(light, normal); + vec3 light_n = light / r; - vec3 refl = 2.0 * normal * d - light; + float d = dot(light_n, normal); + + vec3 refl = 2.0 * normal * d - light_n; float l = max(0.0, d) * material.x + pow(max(0.0, dot(view, refl)), material.z) * material.y; - vec3 color = l * albedo.rgb * u_light_color * albedo.a / (u_light_attenuation.x + r * (u_light_attenuation.y + r * u_light_attenuation.z)); + vec3 color = l * albedo.rgb * u_light_color / (u_light_attenuation.x + r * (u_light_attenuation.y + r * u_light_attenuation.z)); + + if (u_shadowed != 0 && d > 0.0) + { + vec3 dir = -light; + + float far; + float near = u_shadow_near; + + vec3 adir = abs(dir); + + float d = max(adir.x, max(adir.y, adir.z)); + + if (d == adir.x) + { + if (dir.x > 0.0) + far = u_shadow_far_positive.x; + else + far = u_shadow_far_negative.x; + } + else if (d == adir.y) + { + if (dir.y > 0.0) + far = u_shadow_far_positive.y; + else + far = u_shadow_far_negative.y; + } + else + { + if (dir.z > 0.0) + far = u_shadow_far_positive.z; + else + far = u_shadow_far_negative.z; + } + + float A = (far + near) / (far - near); + float B = - 2.0 * far * near / (far - near); + + d = (A * d + B) / d; + + float v = texture(u_shadow, vec4(dir, d * 0.5 + 0.5)); + + color *= v; + } out_color = vec4(color, 1.0); } @@ -409,6 +554,8 @@ void main() 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}; gfx::program point_light_pass_program{screen_vs, std::string(light_common) + point_light_pass_fs}; + gfx::program shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_fs}; + gfx::program cubemap_shadow_builder_program{std::string(g_buffer_pass_common) + shadow_builder_vs, shadow_builder_gs, shadow_builder_fs}; // G-buffer attachments: // 0 - position (rbg) @@ -422,9 +569,17 @@ void main() std::optional> g_buffer_size; + gfx::framebuffer directional_shadow_framebuffer; + gfx::texture_2d directional_shadow_texture; + + gfx::framebuffer point_shadow_framebuffer; + gfx::texture_cubemap point_shadow_texture; + gfx::mesh screen_mesh; }; + static int const shadow_map_size = 1024; + deferred_renderer::deferred_renderer() : pimpl_{std::make_unique()} { @@ -447,12 +602,33 @@ void main() impl().directional_light_pass_program["u_g1"] = 1; impl().directional_light_pass_program["u_g2"] = 2; impl().directional_light_pass_program["u_g3"] = 3; + impl().directional_light_pass_program["u_shadow"] = 4; impl().point_light_pass_program.bind(); impl().point_light_pass_program["u_g0"] = 0; impl().point_light_pass_program["u_g1"] = 1; impl().point_light_pass_program["u_g2"] = 2; impl().point_light_pass_program["u_g3"] = 3; + impl().point_light_pass_program["u_shadow"] = 4; + + impl().directional_shadow_texture.load({shadow_map_size, shadow_map_size}); + 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); + impl().directional_shadow_framebuffer.depth(impl().directional_shadow_texture); + impl().directional_shadow_framebuffer.assert_complete(); + + 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); + for (int f = 0; f < 6; ++f) + { + impl().point_shadow_texture.load(f, {shadow_map_size, shadow_map_size}); + } + impl().point_shadow_framebuffer.depth(impl().point_shadow_texture); + impl().point_shadow_framebuffer.assert_complete(); impl().screen_mesh.setup>(); } @@ -482,6 +658,8 @@ void main() return m; } + static std::optional test_mesh; + void deferred_renderer::render(std::vector const & objects, render_target const & target, options const & opts) { // Get camera info @@ -495,6 +673,7 @@ void main() std::unordered_map> objects_by_mask; geom::box lit_bbox; + geom::box casts_shadow_bbox; for (std::size_t i = 0; i < objects.size(); ++i) { @@ -506,6 +685,7 @@ void main() objects_by_mask[mask(objects[i])].push_back(i); if (o.mat.lit) lit_bbox |= o.bbox; + if (o.casts_shadow) casts_shadow_bbox |= o.bbox; } // TODO: frustum culling @@ -632,38 +812,304 @@ void main() impl().ambient_pass_program["u_max_intensity"] = opts.max_intensity; gl::DrawArrays(gl::TRIANGLES, 0, 6); - gl::Enable(gl::BLEND); - gl::BlendFunc(gl::ONE, gl::ONE); - // Directional lights geom::point bbox_hull_screen[8]; auto lit_bbox_hull_size = bbox_to_screen_fan(camera_transform, lit_bbox, bbox_hull_screen); impl().screen_mesh.load(bbox_hull_screen, lit_bbox_hull_size, gl::TRIANGLE_FAN); - impl().directional_light_pass_program.bind(); - impl().directional_light_pass_program["u_camera_position"] = camera_position; - impl().directional_light_pass_program["u_max_intensity"] = opts.max_intensity; + gl::ActiveTexture(gl::TEXTURE4); + impl().directional_shadow_texture.bind(); for (auto const & l : opts.directional_lights) { + geom::matrix light_transform; + + if (l.shadowed) + { + geom::vector light_axes[3]; + + light_axes[2] = -geom::normalized(l.direction); + light_axes[1] = {0.f, 0.f, 1.f}; + + if (light_axes[2][2] > 0.5f) + light_axes[1] = {0.f, 1.f, 0.f}; + + geom::gram_schmidt(light_axes[2], light_axes[1]); + + 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 (std::size_t i = 0; i < 3; ++i) + 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])); + + impl().directional_shadow_framebuffer.bind(); + gl::DrawBuffer(gl::NONE); + 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; + + for (auto const & p : objects_by_mask) + { + if (p.second.empty()) continue; + + std::uint32_t mask = p.first; + + if (!(mask & O_CASTS_SHADOW)) continue; + + impl().shadow_builder_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) + { + auto const & o = objects[i]; + + if (mask & O_PRE_TRANSFORM) + impl().shadow_builder_program["u_pre_transform"] = *o.pre_transform; + + if (mask & O_POST_TRANSFORM) + impl().shadow_builder_program["u_post_transform"] = *o.post_transform; + + o.mesh->draw(); + } + } + } + + target.bind(); + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::ONE, gl::ONE); + + gl::Disable(gl::DEPTH_TEST); + gl::CullFace(gl::BACK); + + impl().directional_light_pass_program.bind(); + impl().directional_light_pass_program["u_camera_position"] = camera_position; + impl().directional_light_pass_program["u_max_intensity"] = opts.max_intensity; + 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; impl().screen_mesh.draw(); } // Point lights - float min_intensity = opts.min_intensity.value_or(opts.max_intensity / 256.f); + gl::Enable(gl::TEXTURE_CUBE_MAP_SEAMLESS); + gl::ActiveTexture(gl::TEXTURE4); + impl().point_shadow_texture.bind(); - impl().point_light_pass_program.bind(); - impl().point_light_pass_program["u_camera_position"] = camera_position; - impl().point_light_pass_program["u_max_intensity"] = opts.max_intensity; + float min_intensity = opts.min_intensity.value_or(opts.max_intensity / 256.f); for (auto const & l : opts.point_lights) { float I = std::max({l.color[0], l.color[1], l.color[2]}); auto r = geom::solve_quadratic(l.attenuation.c2, l.attenuation.c1, l.attenuation.c0 - I / min_intensity); + + float near = l.min_shadow_distance; + geom::vector far_positive; + geom::vector far_negative; + + for (std::size_t i = 0; i < 3; ++i) + { + far_positive[i] = casts_shadow_bbox[i].max - l.position[i]; + far_negative[i] = l.position[i] - casts_shadow_bbox[i].min; + + if (r) + { + far_positive[i] = std::min(far_positive[i], r->second); + far_negative[i] = std::min(far_negative[i], r->second); + } + + far_positive[i] = std::max(far_positive[i], near); + far_negative[i] = std::max(far_negative[i], near); + } + + if (l.shadowed) + { + geom::matrix transform[6]; + + // +X + { + float far = far_positive[0]; + transform[0] = geom::matrix::zero(); + + transform[0][0][2] = -1.f; + transform[0][1][1] = -1.f; + + transform[0][2][0] = (far + near) / (far - near); + transform[0][2][3] = - 2.f * far * near / (far - near); + transform[0][3][0] = 1.f; + } + + // -X + { + float far = far_negative[0]; + transform[1] = geom::matrix::zero(); + + transform[1][0][2] = 1.f; + transform[1][1][1] = -1.f; + + transform[1][2][0] = - (far + near) / (far - near); + transform[1][2][3] = - 2.f * far * near / (far - near); + transform[1][3][0] = - 1.f; + } + + // +Y + { + float far = far_positive[1]; + transform[2] = geom::matrix::zero(); + + transform[2][0][0] = 1.f; + transform[2][1][2] = 1.f; + + transform[2][2][1] = (far + near) / (far - near); + transform[2][2][3] = - 2.f * far * near / (far - near); + transform[2][3][1] = 1.f; + } + + // -Y + { + float far = far_negative[1]; + transform[3] = geom::matrix::zero(); + + transform[3][0][0] = 1.f; + transform[3][1][2] = -1.f; + + transform[3][2][1] = - (far + near) / (far - near); + transform[3][2][3] = - 2.f * far * near / (far - near); + transform[3][3][1] = - 1.f; + } + + // +Z + { + float far = far_positive[2]; + transform[4] = geom::matrix::zero(); + + transform[4][0][0] = 1.f; + transform[4][1][1] = -1.f; + + transform[4][2][2] = (far + near) / (far - near); + transform[4][2][3] = - 2.f * far * near / (far - near); + transform[4][3][2] = 1.f; + } + + // -Z + { + float far = far_negative[2]; + transform[5] = geom::matrix::zero(); + + transform[5][0][0] = -1.f; + transform[5][1][1] = -1.f; + + transform[5][2][2] = - (far + near) / (far - near); + transform[5][2][3] = - 2.f * far * near / (far - near); + transform[5][3][2] = - 1.f; + } + + geom::matrix const translate_by_light = geom::translation(geom::point::zero() - l.position).homogeneous_matrix(); + + impl().point_shadow_framebuffer.bind(); + gl::DrawBuffer(gl::NONE); + gl::Viewport(0, 0, impl().point_shadow_texture.width(), impl().point_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().cubemap_shadow_builder_program.bind(); + impl().cubemap_shadow_builder_program["u_light_transform"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[0]"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[1]"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[2]"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[3]"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[4]"] = geom::matrix::identity(); +// impl().cubemap_shadow_builder_program["u_layer_transform[5]"] = geom::matrix::identity(); + + impl().cubemap_shadow_builder_program["u_layer_transform[0]"] = transform[0] * translate_by_light; + impl().cubemap_shadow_builder_program["u_layer_transform[1]"] = transform[1] * translate_by_light; + impl().cubemap_shadow_builder_program["u_layer_transform[2]"] = transform[2] * translate_by_light; + impl().cubemap_shadow_builder_program["u_layer_transform[3]"] = transform[3] * translate_by_light; + impl().cubemap_shadow_builder_program["u_layer_transform[4]"] = transform[4] * translate_by_light; + impl().cubemap_shadow_builder_program["u_layer_transform[5]"] = transform[5] * translate_by_light; + + for (auto const & p : objects_by_mask) + { + if (p.second.empty()) continue; + + std::uint32_t mask = p.first; + + if (!(mask & O_CASTS_SHADOW)) continue; + + impl().cubemap_shadow_builder_program["u_flag_mask"] = mask; + + for (std::size_t i : p.second) + { + auto const & o = objects[i]; + + if (mask & O_PRE_TRANSFORM) + impl().cubemap_shadow_builder_program["u_pre_transform"] = *o.pre_transform; + + if (mask & O_POST_TRANSFORM) + impl().cubemap_shadow_builder_program["u_post_transform"] = *o.post_transform; + + o.mesh->draw(); + } + } +/* + gfx::pixmap_float pixmap({impl().point_shadow_texture.width(), impl().point_shadow_texture.height()}); + impl().point_shadow_texture.pixels(1, gl::DEPTH_COMPONENT, gl::FLOAT, pixmap.data()); + + auto pixels = util::map([](float x){ return std::uint8_t(x * 255); }, pixmap); + + std::ofstream out{"/home/lisyarus/depth.pgm"}; + gfx::write_pgm(pixels, out); +//*/ + } + + target.bind(); + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::ONE, gl::ONE); + + gl::Disable(gl::DEPTH_TEST); + gl::CullFace(gl::BACK); + + impl().point_light_pass_program.bind(); + impl().point_light_pass_program["u_camera_position"] = camera_position; + impl().point_light_pass_program["u_max_intensity"] = opts.max_intensity; + impl().point_light_pass_program["u_shadowed"] = l.shadowed; + + if (l.shadowed) + { + impl().point_light_pass_program["u_shadow_near"] = near; + impl().point_light_pass_program["u_shadow_far_positive"] = far_positive; + impl().point_light_pass_program["u_shadow_far_negative"] = far_negative; + } + geom::box light_bbox; if (r) {