diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ca183..73e1a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.25) + option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_AUTHOR "Application author" "C. J. Howard") diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index 43e36cb..f570151 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -394,11 +394,13 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t { directional_shadow_maps.resize(directional_shadow_count); directional_shadow_splits.resize(directional_shadow_count); + directional_shadow_fade_ranges.resize(directional_shadow_count); directional_shadow_matrices.resize(directional_shadow_count); } directional_shadow_maps[index] = static_cast(directional_light.get_shadow_framebuffer()->get_depth_attachment()); directional_shadow_splits[index] = directional_light.get_shadow_cascade_distances(); + directional_shadow_fade_ranges[index] = directional_light.get_shadow_fade_range(); directional_shadow_matrices[index] = directional_light.get_shadow_cascade_matrices(); } break; @@ -681,22 +683,24 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(std::span{directional_shadow_maps.data(), directional_shadow_count}); std::size_t offset = 0; for (std::size_t i = 0; i < directional_shadow_count; ++i) { - directional_shadow_splits_var->update(directional_shadow_splits[i], offset * 4); + directional_shadow_splits_var->update(directional_shadow_splits[i], i); + directional_shadow_fade_ranges_var->update(directional_shadow_fade_ranges[i], i); directional_shadow_matrices_var->update(directional_shadow_matrices[i], offset); - offset += directional_shadow_splits[i].size(); + offset += directional_shadow_matrices[i].size(); } } ); diff --git a/src/engine/render/passes/material-pass.hpp b/src/engine/render/passes/material-pass.hpp index b9abeba..41bfe28 100644 --- a/src/engine/render/passes/material-pass.hpp +++ b/src/engine/render/passes/material-pass.hpp @@ -115,7 +115,8 @@ private: // Directional shadows std::vector directional_shadow_maps; - std::vector> directional_shadow_splits; + std::vector directional_shadow_splits; + std::vector directional_shadow_fade_ranges; std::vector> directional_shadow_matrices; std::size_t directional_shadow_count; diff --git a/src/engine/render/renderer.cpp b/src/engine/render/renderer.cpp index 8f69341..920dc0a 100644 --- a/src/engine/render/renderer.cpp +++ b/src/engine/render/renderer.cpp @@ -41,6 +41,7 @@ namespace render { renderer::renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager) { m_light_probe_stage = std::make_unique(rasterizer, resource_manager); + m_cascaded_shadow_map_stage = std::make_unique(rasterizer, resource_manager); m_culling_stage = std::make_unique(); m_queue_stage = std::make_unique(); } @@ -78,6 +79,9 @@ void renderer::render(float t, float dt, float alpha, scene::collection& collect m_ctx.objects.clear(); m_ctx.operations.clear(); + // Execute cascaded shadow map stage + m_cascaded_shadow_map_stage->execute(m_ctx); + // Execute culling stage m_culling_stage->execute(m_ctx); diff --git a/src/engine/render/renderer.hpp b/src/engine/render/renderer.hpp index 2eafff2..68cc496 100644 --- a/src/engine/render/renderer.hpp +++ b/src/engine/render/renderer.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ public: private: render::context m_ctx; std::unique_ptr m_light_probe_stage; + std::unique_ptr m_cascaded_shadow_map_stage; std::unique_ptr m_culling_stage; std::unique_ptr m_queue_stage; }; diff --git a/src/engine/render/passes/cascaded-shadow-map-pass.cpp b/src/engine/render/stages/cascaded-shadow-map-stage.cpp similarity index 59% rename from src/engine/render/passes/cascaded-shadow-map-pass.cpp rename to src/engine/render/stages/cascaded-shadow-map-stage.cpp index f508f3b..12d8a5e 100644 --- a/src/engine/render/passes/cascaded-shadow-map-pass.cpp +++ b/src/engine/render/stages/cascaded-shadow-map-stage.cpp @@ -17,7 +17,7 @@ * along with Antkeeper source code. If not, see . */ -#include +#include #include #include #include @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -36,16 +35,19 @@ #include #include #include +#include #include #include +#include #include +#include namespace render { static bool operation_compare(const render::operation* a, const render::operation* b); -cascaded_shadow_map_pass::cascaded_shadow_map_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager): - pass(rasterizer, nullptr) +cascaded_shadow_map_stage::cascaded_shadow_map_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager): + m_rasterizer(&rasterizer) { // Init shader template definitions m_shader_template_definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute::position); @@ -61,7 +63,7 @@ cascaded_shadow_map_pass::cascaded_shadow_map_pass(gl::rasterizer* rasterizer, r // Static mesh shader { // Load static mesh shader template - m_static_mesh_shader_template = resource_manager->load("shadow-cascade-static-mesh.glsl"); + m_static_mesh_shader_template = resource_manager.load("shadow-cascade-static-mesh.glsl"); // Build static mesh shader program rebuild_static_mesh_shader_program(); @@ -70,14 +72,14 @@ cascaded_shadow_map_pass::cascaded_shadow_map_pass(gl::rasterizer* rasterizer, r // Skeletal mesh shader { // Load skeletal mesh shader template - m_skeletal_mesh_shader_template = resource_manager->load("shadow-cascade-skeletal-mesh.glsl"); + m_skeletal_mesh_shader_template = resource_manager.load("shadow-cascade-skeletal-mesh.glsl"); // Build static mesh shader program rebuild_skeletal_mesh_shader_program(); } } -void cascaded_shadow_map_pass::render(render::context& ctx) +void cascaded_shadow_map_stage::execute(render::context& ctx) { // For each light const auto& lights = ctx.collection->get_objects(scene::light::object_type_id); @@ -110,11 +112,13 @@ void cascaded_shadow_map_pass::render(render::context& ctx) } // Render shadow atlas - render_atlas(directional_light, ctx); + render_shadow_atlas(ctx, directional_light); } + + ctx.operations.clear(); } -void cascaded_shadow_map_pass::set_max_bone_count(std::size_t bone_count) +void cascaded_shadow_map_stage::set_max_bone_count(std::size_t bone_count) { if (m_max_bone_count != bone_count) { @@ -128,7 +132,69 @@ void cascaded_shadow_map_pass::set_max_bone_count(std::size_t bone_count) } } -void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, render::context& ctx) +void cascaded_shadow_map_stage::queue(render::context& ctx, scene::directional_light& light, const math::fmat4& light_view_projection) +{ + // Clear pre-existing render operations + ctx.operations.clear(); + + // Combine camera and light layer masks + const auto camera_light_layer_mask = ctx.camera->get_layer_mask() & light.get_layer_mask(); + + // Build light view frustum from light view projection matrix + const geom::view_frustum light_view_frustum(light_view_projection); + + // Tests whether a box is completely outside a plane + auto box_outside_plane = [](const geom::box& box, const geom::plane& plane) -> bool + { + const math::fvec3 p = + { + (plane.normal.x() > 0.0f) ? box.max.x() : box.min.x(), + (plane.normal.y() > 0.0f) ? box.max.y() : box.min.y(), + (plane.normal.z() > 0.0f) ? box.max.z() : box.min.z() + }; + + return plane.distance(p) < 0.0f; + }; + + // For each object in the scene collection + const auto& objects = ctx.collection->get_objects(); + std::for_each + ( + std::execution::seq, + std::begin(objects), + std::end(objects), + [&](scene::object_base* object) + { + // Cull object if it doesn't share a common layer with the camera and light + if (!(object->get_layer_mask() & camera_light_layer_mask)) + { + return; + } + + // Ignore cameras and lights + if (object->get_object_type_id() == scene::camera::object_type_id || object->get_object_type_id() == scene::light::object_type_id) + { + return; + } + + // Cull object if it's outside of the light view frustum (excluding near plane [reverse-z, so far=near]) + const auto& object_bounds = object->get_bounds(); + if (box_outside_plane(object_bounds, light_view_frustum.left()) || + box_outside_plane(object_bounds, light_view_frustum.right()) || + box_outside_plane(object_bounds, light_view_frustum.bottom()) || + box_outside_plane(object_bounds, light_view_frustum.top()) || + box_outside_plane(object_bounds, light_view_frustum.near())) + { + return; + } + + // Add object render operations to render context + object->render(ctx); + } + ); +} + +void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene::directional_light& light) { // Disable blending glDisable(GL_BLEND); @@ -138,114 +204,98 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren glDepthFunc(GL_GREATER); glDepthMask(GL_TRUE); + // Disable depth clipping (enable "pancaking") + glEnable(GL_DEPTH_CLAMP); + // Enable back-face culling glEnable(GL_CULL_FACE); glCullFace(GL_BACK); bool two_sided = false; // Bind and clear shadow atlas framebuffer - rasterizer->use_framebuffer(*light.get_shadow_framebuffer()); - rasterizer->clear_framebuffer(false, true, false); - - // Get light layer mask - const auto light_layer_mask = light.get_layer_mask(); + m_rasterizer->use_framebuffer(*light.get_shadow_framebuffer()); + m_rasterizer->clear_framebuffer(false, true, false); // Get camera const scene::camera& camera = *ctx.camera; - // Calculate distance to shadow cascade depth clipping planes - const auto shadow_clip_near = camera.get_clip_near(); - const auto shadow_clip_far = camera.get_clip_near() + light.get_shadow_distance(); - // Get light shadow cascade distances and matrices const auto cascade_count = light.get_shadow_cascade_count(); - const auto cascade_distances = light.get_shadow_cascade_distances(); + auto& cascade_distances = light.get_shadow_cascade_distances(); const auto cascade_matrices = light.get_shadow_cascade_matrices(); // Calculate cascade far clipping plane distances - cascade_distances[cascade_count - 1] = shadow_clip_far; + cascade_distances[cascade_count - 1] = light.get_shadow_max_distance(); for (unsigned int i = 0; i < cascade_count - 1; ++i) { const float weight = static_cast(i + 1) / static_cast(cascade_count); // Calculate linear and logarithmic split distances - const float linear_distance = math::lerp(shadow_clip_near, shadow_clip_far, weight); - const float log_distance = math::log_lerp(shadow_clip_near, shadow_clip_far, weight); + const float linear_distance = math::lerp(camera.get_clip_near(), camera.get_clip_near() + light.get_shadow_max_distance(), weight); + const float log_distance = math::log_lerp(camera.get_clip_near(), camera.get_clip_near() + light.get_shadow_max_distance(), weight); // Interpolate between linear and logarithmic split distances cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution()); } - // Calculate viewports for each shadow map - const int shadow_map_resolution = static_cast(light.get_shadow_framebuffer()->get_depth_attachment()->get_width()); - const int cascade_resolution = shadow_map_resolution >> 1; - math::ivec4 shadow_map_viewports[4]; - for (int i = 0; i < 4; ++i) - { - int x = i % 2; - int y = i / 2; - - math::ivec4& viewport = shadow_map_viewports[i]; - viewport[0] = x * cascade_resolution; - viewport[1] = y * cascade_resolution; - viewport[2] = cascade_resolution; - viewport[3] = cascade_resolution; - } + // Determine resolution of shadow atlas and cascades + const auto atlas_resolution = static_cast(light.get_shadow_framebuffer()->get_depth_attachment()->get_width()); + const auto cascade_resolution = atlas_resolution >> 1; // Sort render operations std::sort(std::execution::par_unseq, ctx.operations.begin(), ctx.operations.end(), operation_compare); gl::shader_program* active_shader_program = nullptr; - // Precalculate frustum minimal bounding sphere terms - const auto k = std::sqrt(1.0f + camera.get_aspect_ratio() * camera.get_aspect_ratio()) * std::tan(camera.get_vertical_fov() * 0.5f); - const auto k2 = k * k; - const auto k4 = k2 * k2; - for (unsigned int i = 0; i < cascade_count; ++i) { - // Set viewport for this shadow map - const math::ivec4& viewport = shadow_map_viewports[i]; - rasterizer->set_viewport(viewport[0], viewport[1], viewport[2], viewport[3]); + // Get distances to near and far clipping planes of camera subfrustum + const auto subfrustum_near = i ? cascade_distances[i - 1] : camera.get_clip_near(); + const auto subfrustum_far = cascade_distances[i]; - // Find minimal bounding sphere of subfrustum in view-space - // @see https://lxjk.github.io/2017/04/15/Calculate-Minimal-Bounding-Sphere-of-Frustum.html - geom::sphere subfrustum_bounds; - { - // Get subfrustum near and far distances - const auto n = (i) ? cascade_distances[i - 1] : camera.get_clip_near(); - const auto f = cascade_distances[i]; - - if (k2 >= (f - n) / (f + n)) - { - subfrustum_bounds.center = {0, 0, -f}; - subfrustum_bounds.radius = f * k; - } - else - { - subfrustum_bounds.center = {0, 0, -0.5f * (f + n) * (1.0f + k2)}; - subfrustum_bounds.radius = 0.5f * std::sqrt((k4 + 2.0f * k2 + 1.0f) * (f * f + n * n) + 2.0f * f * (k4 - 1.0f) * n); - } - } + // Find centroid of camera subfrustum + const auto subfrustum_centroid = camera.get_translation() + camera.get_forward() * ((subfrustum_near + subfrustum_far) * 0.5f); - // Transform subfrustum bounds into world-space - subfrustum_bounds.center = camera.get_translation() + camera.get_rotation() * subfrustum_bounds.center; + // Construct light view matrix + const auto light_view = math::look_at_rh(subfrustum_centroid, subfrustum_centroid + light.get_direction(), light.get_rotation() * math::fvec3{0, 1, 0}); - // Discretize view-space subfrustum bounds - const auto texel_scale = static_cast(cascade_resolution) / (subfrustum_bounds.radius * 2.0f); - subfrustum_bounds.center = math::conjugate(light.get_rotation()) * subfrustum_bounds.center; - subfrustum_bounds.center = math::floor(subfrustum_bounds.center * texel_scale) / texel_scale; - subfrustum_bounds.center = light.get_rotation() * subfrustum_bounds.center; + // Construct subfrustum inverse view-projection matrix + const auto [subfrustum_projection, subfrustum_inv_projection] = math::perspective_half_z_inv(camera.get_vertical_fov(), camera.get_aspect_ratio(), subfrustum_far, subfrustum_near); + const auto subfrustum_inv_view_projection = camera.get_inv_view() * subfrustum_inv_projection; - // Construct light view matrix - const auto light_view = math::look_at_rh(subfrustum_bounds.center, subfrustum_bounds.center + light.get_direction(), light.get_rotation() * math::fvec3{0, 1, 0}); + // Construct matrix which transforms clip space coordinates to light view space + const auto ndc_to_light_view = light_view * subfrustum_inv_view_projection; - // Construct light projection matrix (reversed depth) + // Construct AABB containing subfrustum corners in light view space + geom::box light_projection_bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; + for (std::size_t j = 0; j < 8; ++j) + { + // Reverse half z clip-space coordinates of a cube + constexpr math::fvec4 ndc_cube[8] = + { + {-1, -1, 1, 1}, // NBL + { 1, -1, 1, 1}, // NBR + {-1, 1, 1, 1}, // NTL + { 1, 1, 1, 1}, // NTR + {-1, -1, 0, 1}, // FBL + { 1, -1, 0, 1}, // FBR + {-1, 1, 0, 1}, // FTL + { 1, 1, 0, 1} // FTR + }; + + // Find light view space coordinates of subfrustum corner + const auto corner = ndc_to_light_view * ndc_cube[j]; + + // Expand light projection bounds to contain corner + light_projection_bounds.extend(math::fvec3(corner) / corner[3]); + } + + // Construct light projection matrix const auto light_projection = math::ortho_half_z ( - -subfrustum_bounds.radius, subfrustum_bounds.radius, - -subfrustum_bounds.radius, subfrustum_bounds.radius, - subfrustum_bounds.radius, -subfrustum_bounds.radius + light_projection_bounds.min.x(), light_projection_bounds.max.x(), + light_projection_bounds.min.y(), light_projection_bounds.max.y(), + -light_projection_bounds.min.z(), -light_projection_bounds.max.z() ); // Construct light view-projection matrix @@ -254,14 +304,21 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren // Update world-space to cascade texture-space transformation matrix cascade_matrices[i] = light.get_shadow_scale_bias_matrices()[i] * light_view_projection; + // Queue render operations + queue(ctx, light, light_view_projection); + if (ctx.operations.empty()) + { + continue; + } + + // Set viewport for this cascade + const auto viewport_x = static_cast(i % 2) * cascade_resolution; + const auto viewport_y = static_cast(i >> 1) * cascade_resolution; + m_rasterizer->set_viewport(viewport_x, viewport_y, cascade_resolution, cascade_resolution); + + // Render geometry for (const render::operation* operation: ctx.operations) { - // Skip operations which don't share any layers with the shadow-casting light - if (!(operation->layer_mask & light_layer_mask)) - { - continue; - } - const render::material* material = operation->material.get(); if (material) { @@ -291,11 +348,11 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren if (active_shader_program != shader_program) { active_shader_program = shader_program; - rasterizer->use_program(*active_shader_program); + m_rasterizer->use_program(*active_shader_program); } // Calculate model-view-projection matrix - math::fmat4 model_view_projection = light_view_projection * operation->transform; + const auto model_view_projection = light_view_projection * operation->transform; // Upload operation-dependent parameters to shader program if (active_shader_program == m_static_mesh_shader_program.get()) @@ -309,12 +366,15 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren } // Draw geometry - rasterizer->draw_arrays(*operation->vertex_array, operation->drawing_mode, operation->start_index, operation->index_count); + m_rasterizer->draw_arrays(*operation->vertex_array, operation->drawing_mode, operation->start_index, operation->index_count); } } + + // Re-enable depth clipping (disable "pancaking") + glDisable(GL_DEPTH_CLAMP); } -void cascaded_shadow_map_pass::rebuild_static_mesh_shader_program() +void cascaded_shadow_map_stage::rebuild_static_mesh_shader_program() { m_static_mesh_shader_program = m_static_mesh_shader_template->build(m_shader_template_definitions); if (!m_static_mesh_shader_program->linked()) @@ -330,7 +390,7 @@ void cascaded_shadow_map_pass::rebuild_static_mesh_shader_program() } } -void cascaded_shadow_map_pass::rebuild_skeletal_mesh_shader_program() +void cascaded_shadow_map_stage::rebuild_skeletal_mesh_shader_program() { m_skeletal_mesh_shader_program = m_skeletal_mesh_shader_template->build(m_shader_template_definitions); if (!m_skeletal_mesh_shader_program->linked()) diff --git a/src/engine/render/passes/cascaded-shadow-map-pass.hpp b/src/engine/render/stages/cascaded-shadow-map-stage.hpp similarity index 71% rename from src/engine/render/passes/cascaded-shadow-map-pass.hpp rename to src/engine/render/stages/cascaded-shadow-map-stage.hpp index 5fcc63d..38deeca 100644 --- a/src/engine/render/passes/cascaded-shadow-map-pass.hpp +++ b/src/engine/render/stages/cascaded-shadow-map-stage.hpp @@ -17,44 +17,37 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_PASS_HPP -#define ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_PASS_HPP +#ifndef ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_STAGE_HPP +#define ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_STAGE_HPP -#include -#include -#include +#include +#include #include #include +#include +#include +#include #include #include #include -class resource_manager; - namespace render { /** * Renders cascaded shadow maps for directional lights. */ -class cascaded_shadow_map_pass: public pass +class cascaded_shadow_map_stage: public stage { public: /** - * Constructs a shadow map pass. + * Constructs a cascaded shadow map stage. * - * @param rasterizer Rasterizer. - * @param framebuffer Shadow map framebuffer. - * @param resource_manage Resource manager. + * @param rasterizer GL rasterizer. + * @param resource_manager Resource manager for loading shader templates. */ - cascaded_shadow_map_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager); + cascaded_shadow_map_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); - /** - * Renders shadow maps for a single camera. - * - * @param ctx Render context. - * @param queue Render queue. - */ - void render(render::context& ctx) override; + void execute(render::context& ctx) override; /** * Sets the maximum bone count for shadow-casting skeletal meshes. @@ -72,13 +65,18 @@ public: } private: + /** + * Queues render operations of objects that may cast shadows visible to the current camera. + */ + void queue(render::context& ctx, scene::directional_light& light, const math::fmat4& light_view_projection); + /** * Renders an atlas of cascaded shadow maps for a single directional light. * * @param light Shadow-casting directional light. * @param ctx Render context. */ - void render_atlas(scene::directional_light& light, render::context& ctx); + void render_shadow_atlas(render::context& ctx, scene::directional_light& light); /// Rebuilds the shader program for static meshes. void rebuild_static_mesh_shader_program(); @@ -86,6 +84,8 @@ private: /// Rebuilds the shader program for skeletal meshes. void rebuild_skeletal_mesh_shader_program(); + gl::rasterizer* m_rasterizer; + std::size_t m_max_bone_count{64}; std::unordered_map m_shader_template_definitions; @@ -102,4 +102,4 @@ private: } // namespace render -#endif // ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_PASS_HPP +#endif // ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_STAGE_HPP diff --git a/src/engine/scene/camera.cpp b/src/engine/scene/camera.cpp index a5d051d..3576653 100644 --- a/src/engine/scene/camera.cpp +++ b/src/engine/scene/camera.cpp @@ -27,8 +27,8 @@ geom::ray camera::pick(const math::fvec2& ndc) const { const auto near = m_inv_view_projection * math::fvec4{ndc[0], ndc[1], 1.0f, 1.0f}; const auto far = m_inv_view_projection * math::fvec4{ndc[0], ndc[1], 0.0f, 1.0f}; - const auto origin = math::fvec3{near[0], near[1], near[2]} / near[3]; - const auto direction = math::normalize(math::fvec3{far[0], far[1], far[2]} / far[3] - origin); + const auto origin = math::fvec3(near) / near[3]; + const auto direction = math::normalize(math::fvec3(far) / far[3] - origin); return {origin, direction}; } @@ -36,12 +36,10 @@ geom::ray camera::pick(const math::fvec2& ndc) const math::fvec3 camera::project(const math::fvec3& object, const math::fvec4& viewport) const { math::fvec4 result = m_view_projection * math::fvec4{object[0], object[1], object[2], 1.0f}; - result[0] = (result[0] / result[3]) * 0.5f + 0.5f; - result[1] = (result[1] / result[3]) * 0.5f + 0.5f; - result[2] = (result[2] / result[3]) * 0.5f + 0.5f; + result /= result[3]; - result[0] = result[0] * viewport[2] + viewport[0]; - result[1] = result[1] * viewport[3] + viewport[1]; + result.x() = result.x() * viewport[2] + viewport[0]; + result.y() = result.y() * viewport[3] + viewport[1]; return math::fvec3(result); } diff --git a/src/engine/scene/directional-light.cpp b/src/engine/scene/directional-light.cpp index 77744e1..668225c 100644 --- a/src/engine/scene/directional-light.cpp +++ b/src/engine/scene/directional-light.cpp @@ -21,10 +21,7 @@ namespace scene { -directional_light::directional_light(): - m_shadow_cascade_distances(m_shadow_cascade_count), - m_shadow_cascade_matrices(m_shadow_cascade_count), - m_shadow_scale_bias_matrices(m_shadow_cascade_count) +directional_light::directional_light() { set_shadow_bias(m_shadow_bias); update_shadow_cascade_distances(); @@ -54,19 +51,21 @@ void directional_light::set_shadow_bias(float bias) noexcept void directional_light::set_shadow_cascade_count(unsigned int count) noexcept { m_shadow_cascade_count = std::min(std::max(count, 1u), 4u); - m_shadow_cascade_distances.resize(m_shadow_cascade_count); - m_shadow_cascade_matrices.resize(m_shadow_cascade_count); - m_shadow_scale_bias_matrices.resize(m_shadow_cascade_count); update_shadow_scale_bias_matrices(); update_shadow_cascade_distances(); } -void directional_light::set_shadow_distance(float distance) noexcept +void directional_light::set_shadow_max_distance(float distance) noexcept { - m_shadow_distance = distance; + m_shadow_max_distance = distance; update_shadow_cascade_distances(); } +void directional_light::set_shadow_fade_range(float range) noexcept +{ + m_shadow_fade_range = range; +} + void directional_light::set_shadow_cascade_distribution(float weight) noexcept { m_shadow_cascade_distribution = weight; @@ -90,10 +89,7 @@ void directional_light::illuminance_updated() void directional_light::update_shadow_scale_bias_matrices() { - // Construct shadow scale-bias matrix (depth range `[-1, 1]`) - // auto m = math::translate(math::fvec3{0.5f, 0.5f, 0.5f + m_shadow_bias}) * math::scale(math::fvec3{0.5f, 0.5f, 0.5f}); - - // Construct shadow scale-bias matrix (depth range `[0, 1]`) + // Transform coordinate range from `[-1, 1]` to `[0, 1]` and apply shadow bias auto m = math::translate(math::fvec3{0.5f, 0.5f, m_shadow_bias}) * math::scale(math::fvec3{0.5f, 0.5f, 1.0f}); // Apply cascade scale @@ -113,14 +109,14 @@ void directional_light::update_shadow_cascade_distances() return; } - m_shadow_cascade_distances[m_shadow_cascade_count - 1] = m_shadow_distance; + m_shadow_cascade_distances[m_shadow_cascade_count - 1] = m_shadow_max_distance; for (unsigned int i = 0; i < m_shadow_cascade_count - 1; ++i) { const auto weight = static_cast(i + 1) / static_cast(m_shadow_cascade_count); // Calculate linear and logarithmic distribution distances - const auto linear_distance = m_shadow_distance * weight; - // const auto log_distance = math::log_lerp(0.0f, m_shadow_distance, weight); + const auto linear_distance = m_shadow_max_distance * weight; + // const auto log_distance = math::log_lerp(0.0f, m_shadow_max_distance, weight); // Interpolate between linear and logarithmic distribution distances // cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution()); diff --git a/src/engine/scene/directional-light.hpp b/src/engine/scene/directional-light.hpp index 45bdacc..fa8c982 100644 --- a/src/engine/scene/directional-light.hpp +++ b/src/engine/scene/directional-light.hpp @@ -24,7 +24,6 @@ #include #include #include -#include #include namespace scene { @@ -136,11 +135,18 @@ public: void set_shadow_cascade_count(unsigned int count) noexcept; /** - * Sets the distance from the camera up to which shadows are visible. + * Sets the maximum distance from a camera's near clipping plane up to which shadows are visible. * - * @param distance Shadow distance. + * @param distance Maximum shadow distance. */ - void set_shadow_distance(float distance) noexcept; + void set_shadow_max_distance(float distance) noexcept; + + /** + * Sets the distance from the maximum shadow distance at which shadows will begin to fade out. + * + * @param range Shadow fade range. + */ + void set_shadow_fade_range(float range) noexcept; /** * Sets the shadow cascade distribution. @@ -173,10 +179,16 @@ public: return m_shadow_cascade_count; } - /// Returns the distance from the camera up to which shadows are visible. - [[nodiscard]] inline constexpr float get_shadow_distance() const noexcept + /// Returns the maximum distance from a camera's near clipping plane up to which shadows are visible. + [[nodiscard]] inline constexpr float get_shadow_max_distance() const noexcept + { + return m_shadow_max_distance; + } + + /// Returns the distance from the maximum shadow distance at which shadows will begin to fade out. + [[nodiscard]] inline constexpr float get_shadow_fade_range() const noexcept { - return m_shadow_distance; + return m_shadow_fade_range; } /// Returns the shadow cascade distribution weight. @@ -187,11 +199,11 @@ public: /// Returns the array of shadow cascade far clipping plane distances. /// @{ - [[nodiscard]] inline constexpr std::span get_shadow_cascade_distances() const noexcept + [[nodiscard]] inline constexpr const math::fvec4& get_shadow_cascade_distances() const noexcept { return m_shadow_cascade_distances; } - [[nodiscard]] inline constexpr std::span get_shadow_cascade_distances() noexcept + [[nodiscard]] inline constexpr math::fvec4& get_shadow_cascade_distances() noexcept { return m_shadow_cascade_distances; } @@ -233,11 +245,12 @@ private: std::shared_ptr m_shadow_framebuffer{nullptr}; float m_shadow_bias{0.005f}; unsigned int m_shadow_cascade_count{4}; - float m_shadow_distance{1000.0f}; + float m_shadow_max_distance{100.0f}; + float m_shadow_fade_range{0.0f}; float m_shadow_cascade_distribution{0.8f}; - std::vector m_shadow_cascade_distances; - std::vector m_shadow_cascade_matrices; - std::vector m_shadow_scale_bias_matrices; + math::fvec4 m_shadow_cascade_distances; + math::fmat4 m_shadow_cascade_matrices[4]; + math::fmat4 m_shadow_scale_bias_matrices[4]; }; } // namespace scene diff --git a/src/engine/scene/point-light.cpp b/src/engine/scene/point-light.cpp index 018ad61..d8b4485 100644 --- a/src/engine/scene/point-light.cpp +++ b/src/engine/scene/point-light.cpp @@ -26,7 +26,7 @@ void point_light::color_updated() m_colored_luminous_flux = get_color() * m_luminous_flux; } -void point_light::luminous_flux_updated() +void point_light::luminous_flux_updated() noexcept { m_colored_luminous_flux = get_color() * m_luminous_flux; } diff --git a/src/game/game.cpp b/src/game/game.cpp index a4ddd9b..87f6d1b 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -85,7 +85,6 @@ #include #include #include -#include #include #include #include @@ -735,8 +734,6 @@ void game::setup_rendering() // Setup surface compositor { - surface_cascaded_shadow_map_pass = std::make_unique(window->get_rasterizer(), resource_manager.get()); - surface_clear_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get()); surface_clear_pass->set_clear_color({0.0f, 0.0f, 0.0f, 1.0f}); surface_clear_pass->set_clear_depth(0.0f); @@ -754,7 +751,6 @@ void game::setup_rendering() surface_outline_pass->set_outline_color(math::fvec4{1.0f, 1.0f, 1.0f, 1.0f}); surface_compositor = std::make_unique(); - surface_compositor->add_pass(surface_cascaded_shadow_map_pass.get()); surface_compositor->add_pass(surface_clear_pass.get()); surface_compositor->add_pass(sky_pass.get()); surface_compositor->add_pass(surface_material_pass.get()); diff --git a/src/game/game.hpp b/src/game/game.hpp index 3bebb97..b0339cc 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -90,7 +90,6 @@ namespace render class material_pass; class renderer; class outline_pass; - class cascaded_shadow_map_pass; class simple_render_pass; class sky_pass; } @@ -333,7 +332,6 @@ public: std::unique_ptr underground_clear_pass; std::unique_ptr underground_material_pass; std::unique_ptr underground_compositor; - std::unique_ptr surface_cascaded_shadow_map_pass; std::unique_ptr surface_clear_pass; std::unique_ptr sky_pass; std::unique_ptr surface_material_pass; diff --git a/src/game/states/experiments/treadmill-experiment-state.cpp b/src/game/states/experiments/treadmill-experiment-state.cpp index d41c211..7982675 100644 --- a/src/game/states/experiments/treadmill-experiment-state.cpp +++ b/src/game/states/experiments/treadmill-experiment-state.cpp @@ -117,10 +117,10 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): // Create nest exterior { scene_component nest_exterior_scene_component; - nest_exterior_scene_component.object = std::make_shared(ctx.resource_manager->load("cube-nest-200mm-exterior.mdl")); + nest_exterior_scene_component.object = std::make_shared(ctx.resource_manager->load("cube-nest-200mm-interior.mdl")); nest_exterior_scene_component.layer_mask = 1; - auto nest_exterior_mesh = ctx.resource_manager->load("cube-nest-200mm-exterior.msh"); + auto nest_exterior_mesh = ctx.resource_manager->load("cube-nest-200mm-interior.msh"); auto nest_exterior_rigid_body = std::make_unique(); nest_exterior_rigid_body->set_mass(0.0f); @@ -349,8 +349,8 @@ void treadmill_experiment_state::create_third_person_camera_rig() spring_arm.near_focal_plane_height = 8.0 * subject_scale; spring_arm.far_focal_plane_height = 80.0 * subject_scale; spring_arm.near_hfov = math::radians(90.0); - spring_arm.far_hfov = math::radians(45.0); - // spring_arm.far_hfov = math::radians(90.0); + // spring_arm.far_hfov = math::radians(45.0); + spring_arm.far_hfov = math::radians(90.0); spring_arm.zoom = 0.25; spring_arm.focal_point_offset = {0, static_cast(worker_phenome->legs->standing_height) * subject_scale, 0}; diff --git a/src/game/world.cpp b/src/game/world.cpp index 32ad064..9277f74 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -362,7 +362,8 @@ void create_sun(::game& ctx) ctx.sun_light->set_shadow_caster(true); ctx.sun_light->set_shadow_framebuffer(ctx.shadow_map_framebuffer); ctx.sun_light->set_shadow_bias(0.005f); - ctx.sun_light->set_shadow_distance(50.0f); + ctx.sun_light->set_shadow_max_distance(20.0f); + ctx.sun_light->set_shadow_fade_range(5.0f); ctx.sun_light->set_shadow_cascade_count(4); ctx.sun_light->set_shadow_cascade_distribution(0.8f);