Browse Source

Improve cascaded shadow maps

master
C. J. Howard 1 year ago
parent
commit
d027b2daf6
15 changed files with 238 additions and 164 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +8
    -4
      src/engine/render/passes/material-pass.cpp
  3. +2
    -1
      src/engine/render/passes/material-pass.hpp
  4. +4
    -0
      src/engine/render/renderer.cpp
  5. +2
    -0
      src/engine/render/renderer.hpp
  6. +149
    -89
      src/engine/render/stages/cascaded-shadow-map-stage.cpp
  7. +22
    -22
      src/engine/render/stages/cascaded-shadow-map-stage.hpp
  8. +5
    -7
      src/engine/scene/camera.cpp
  9. +12
    -16
      src/engine/scene/directional-light.cpp
  10. +26
    -13
      src/engine/scene/directional-light.hpp
  11. +1
    -1
      src/engine/scene/point-light.cpp
  12. +0
    -4
      src/game/game.cpp
  13. +0
    -2
      src/game/game.hpp
  14. +4
    -4
      src/game/states/experiments/treadmill-experiment-state.cpp
  15. +2
    -1
      src/game/world.cpp

+ 1
- 0
CMakeLists.txt View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_NAME "Application name" "Antkeeper")
option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_VERSION "Application version string" "0.0.0")
option(APPLICATION_AUTHOR "Application author" "C. J. Howard") option(APPLICATION_AUTHOR "Application author" "C. J. Howard")

+ 8
- 4
src/engine/render/passes/material-pass.cpp View File

@ -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_maps.resize(directional_shadow_count);
directional_shadow_splits.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_matrices.resize(directional_shadow_count);
} }
directional_shadow_maps[index] = static_cast<const gl::texture_2d*>(directional_light.get_shadow_framebuffer()->get_depth_attachment()); directional_shadow_maps[index] = static_cast<const gl::texture_2d*>(directional_light.get_shadow_framebuffer()->get_depth_attachment());
directional_shadow_splits[index] = directional_light.get_shadow_cascade_distances(); 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(); directional_shadow_matrices[index] = directional_light.get_shadow_cascade_matrices();
} }
break; break;
@ -681,22 +683,24 @@ void material_pass::build_shader_command_buffer(std::vector
if (auto directional_shadow_maps_var = shader_program.variable("directional_shadow_maps")) if (auto directional_shadow_maps_var = shader_program.variable("directional_shadow_maps"))
{ {
auto directional_shadow_splits_var = shader_program.variable("directional_shadow_splits"); auto directional_shadow_splits_var = shader_program.variable("directional_shadow_splits");
auto directional_shadow_fade_ranges_var = shader_program.variable("directional_shadow_fade_ranges");
auto directional_shadow_matrices_var = shader_program.variable("directional_shadow_matrices"); auto directional_shadow_matrices_var = shader_program.variable("directional_shadow_matrices");
if (directional_shadow_maps_var && directional_shadow_splits_var && directional_shadow_matrices_var)
if (directional_shadow_maps_var && directional_shadow_splits_var && directional_shadow_fade_ranges_var && directional_shadow_matrices_var)
{ {
command_buffer.emplace_back command_buffer.emplace_back
( (
[&, directional_shadow_maps_var, directional_shadow_splits_var, directional_shadow_matrices_var]()
[&, directional_shadow_maps_var, directional_shadow_splits_var, directional_shadow_fade_ranges_var, directional_shadow_matrices_var]()
{ {
directional_shadow_maps_var->update(std::span<const gl::texture_2d* const>{directional_shadow_maps.data(), directional_shadow_count}); directional_shadow_maps_var->update(std::span<const gl::texture_2d* const>{directional_shadow_maps.data(), directional_shadow_count});
std::size_t offset = 0; std::size_t offset = 0;
for (std::size_t i = 0; i < directional_shadow_count; ++i) 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); directional_shadow_matrices_var->update(directional_shadow_matrices[i], offset);
offset += directional_shadow_splits[i].size();
offset += directional_shadow_matrices[i].size();
} }
} }
); );

+ 2
- 1
src/engine/render/passes/material-pass.hpp View File

@ -115,7 +115,8 @@ private:
// Directional shadows // Directional shadows
std::vector<const gl::texture_2d*> directional_shadow_maps; std::vector<const gl::texture_2d*> directional_shadow_maps;
std::vector<std::span<const float>> directional_shadow_splits;
std::vector<math::fvec4> directional_shadow_splits;
std::vector<float> directional_shadow_fade_ranges;
std::vector<std::span<const math::fmat4>> directional_shadow_matrices; std::vector<std::span<const math::fmat4>> directional_shadow_matrices;
std::size_t directional_shadow_count; std::size_t directional_shadow_count;

+ 4
- 0
src/engine/render/renderer.cpp View File

@ -41,6 +41,7 @@ namespace render {
renderer::renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager) renderer::renderer(gl::rasterizer& rasterizer, ::resource_manager& resource_manager)
{ {
m_light_probe_stage = std::make_unique<render::light_probe_stage>(rasterizer, resource_manager); m_light_probe_stage = std::make_unique<render::light_probe_stage>(rasterizer, resource_manager);
m_cascaded_shadow_map_stage = std::make_unique<render::cascaded_shadow_map_stage>(rasterizer, resource_manager);
m_culling_stage = std::make_unique<render::culling_stage>(); m_culling_stage = std::make_unique<render::culling_stage>();
m_queue_stage = std::make_unique<render::queue_stage>(); m_queue_stage = std::make_unique<render::queue_stage>();
} }
@ -78,6 +79,9 @@ void renderer::render(float t, float dt, float alpha, scene::collection& collect
m_ctx.objects.clear(); m_ctx.objects.clear();
m_ctx.operations.clear(); m_ctx.operations.clear();
// Execute cascaded shadow map stage
m_cascaded_shadow_map_stage->execute(m_ctx);
// Execute culling stage // Execute culling stage
m_culling_stage->execute(m_ctx); m_culling_stage->execute(m_ctx);

+ 2
- 0
src/engine/render/renderer.hpp View File

@ -23,6 +23,7 @@
#include <engine/render/context.hpp> #include <engine/render/context.hpp>
#include <engine/render/stages/culling-stage.hpp> #include <engine/render/stages/culling-stage.hpp>
#include <engine/render/stages/queue-stage.hpp> #include <engine/render/stages/queue-stage.hpp>
#include <engine/render/stages/cascaded-shadow-map-stage.hpp>
#include <engine/render/stages/light-probe-stage.hpp> #include <engine/render/stages/light-probe-stage.hpp>
#include <engine/scene/collection.hpp> #include <engine/scene/collection.hpp>
#include <engine/gl/rasterizer.hpp> #include <engine/gl/rasterizer.hpp>
@ -58,6 +59,7 @@ public:
private: private:
render::context m_ctx; render::context m_ctx;
std::unique_ptr<render::light_probe_stage> m_light_probe_stage; std::unique_ptr<render::light_probe_stage> m_light_probe_stage;
std::unique_ptr<render::cascaded_shadow_map_stage> m_cascaded_shadow_map_stage;
std::unique_ptr<render::culling_stage> m_culling_stage; std::unique_ptr<render::culling_stage> m_culling_stage;
std::unique_ptr<render::queue_stage> m_queue_stage; std::unique_ptr<render::queue_stage> m_queue_stage;
}; };

src/engine/render/passes/cascaded-shadow-map-pass.cpp → src/engine/render/stages/cascaded-shadow-map-stage.cpp View File

@ -17,7 +17,7 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <engine/render/passes/cascaded-shadow-map-pass.hpp>
#include <engine/render/stages/cascaded-shadow-map-stage.hpp>
#include <engine/resources/resource-manager.hpp> #include <engine/resources/resource-manager.hpp>
#include <engine/gl/rasterizer.hpp> #include <engine/gl/rasterizer.hpp>
#include <engine/gl/framebuffer.hpp> #include <engine/gl/framebuffer.hpp>
@ -26,7 +26,6 @@
#include <engine/render/context.hpp> #include <engine/render/context.hpp>
#include <engine/render/material.hpp> #include <engine/render/material.hpp>
#include <engine/render/vertex-attribute.hpp> #include <engine/render/vertex-attribute.hpp>
#include <engine/gl/shader-template.hpp>
#include <engine/scene/camera.hpp> #include <engine/scene/camera.hpp>
#include <engine/scene/collection.hpp> #include <engine/scene/collection.hpp>
#include <engine/scene/light.hpp> #include <engine/scene/light.hpp>
@ -36,16 +35,19 @@
#include <engine/math/matrix.hpp> #include <engine/math/matrix.hpp>
#include <engine/math/quaternion.hpp> #include <engine/math/quaternion.hpp>
#include <engine/math/projection.hpp> #include <engine/math/projection.hpp>
#include <engine/geom/primitives/view-frustum.hpp>
#include <cmath> #include <cmath>
#include <glad/glad.h> #include <glad/glad.h>
#include <algorithm>
#include <execution> #include <execution>
#include <mutex>
namespace render { namespace render {
static bool operation_compare(const render::operation* a, const render::operation* b); 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 // Init shader template definitions
m_shader_template_definitions["VERTEX_POSITION"] = std::to_string(vertex_attribute::position); 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 // Static mesh shader
{ {
// Load static mesh shader template // Load static mesh shader template
m_static_mesh_shader_template = resource_manager->load<gl::shader_template>("shadow-cascade-static-mesh.glsl");
m_static_mesh_shader_template = resource_manager.load<gl::shader_template>("shadow-cascade-static-mesh.glsl");
// Build static mesh shader program // Build static mesh shader program
rebuild_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 // Skeletal mesh shader
{ {
// Load skeletal mesh shader template // Load skeletal mesh shader template
m_skeletal_mesh_shader_template = resource_manager->load<gl::shader_template>("shadow-cascade-skeletal-mesh.glsl");
m_skeletal_mesh_shader_template = resource_manager.load<gl::shader_template>("shadow-cascade-skeletal-mesh.glsl");
// Build static mesh shader program // Build static mesh shader program
rebuild_skeletal_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 // For each light
const auto& lights = ctx.collection->get_objects(scene::light::object_type_id); 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 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) 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<float> light_view_frustum(light_view_projection);
// Tests whether a box is completely outside a plane
auto box_outside_plane = [](const geom::box<float>& box, const geom::plane<float>& 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 // Disable blending
glDisable(GL_BLEND); glDisable(GL_BLEND);
@ -138,114 +204,98 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren
glDepthFunc(GL_GREATER); glDepthFunc(GL_GREATER);
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
// Disable depth clipping (enable "pancaking")
glEnable(GL_DEPTH_CLAMP);
// Enable back-face culling // Enable back-face culling
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); glCullFace(GL_BACK);
bool two_sided = false; bool two_sided = false;
// Bind and clear shadow atlas framebuffer // 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 // Get camera
const scene::camera& camera = *ctx.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 // Get light shadow cascade distances and matrices
const auto cascade_count = light.get_shadow_cascade_count(); 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(); const auto cascade_matrices = light.get_shadow_cascade_matrices();
// Calculate cascade far clipping plane distances // 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) for (unsigned int i = 0; i < cascade_count - 1; ++i)
{ {
const float weight = static_cast<float>(i + 1) / static_cast<float>(cascade_count); const float weight = static_cast<float>(i + 1) / static_cast<float>(cascade_count);
// Calculate linear and logarithmic split distances // 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 // Interpolate between linear and logarithmic split distances
cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution()); 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<int>(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<int>(light.get_shadow_framebuffer()->get_depth_attachment()->get_width());
const auto cascade_resolution = atlas_resolution >> 1;
// Sort render operations // Sort render operations
std::sort(std::execution::par_unseq, ctx.operations.begin(), ctx.operations.end(), operation_compare); std::sort(std::execution::par_unseq, ctx.operations.begin(), ctx.operations.end(), operation_compare);
gl::shader_program* active_shader_program = nullptr; 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) 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<float> 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<float>(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<float> 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 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 // 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 // Update world-space to cascade texture-space transformation matrix
cascade_matrices[i] = light.get_shadow_scale_bias_matrices()[i] * light_view_projection; 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<int>(i % 2) * cascade_resolution;
const auto viewport_y = static_cast<int>(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) 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(); const render::material* material = operation->material.get();
if (material) if (material)
{ {
@ -291,11 +348,11 @@ void cascaded_shadow_map_pass::render_atlas(scene::directional_light& light, ren
if (active_shader_program != shader_program) if (active_shader_program != shader_program)
{ {
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 // 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 // Upload operation-dependent parameters to shader program
if (active_shader_program == m_static_mesh_shader_program.get()) 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 // 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); m_static_mesh_shader_program = m_static_mesh_shader_template->build(m_shader_template_definitions);
if (!m_static_mesh_shader_program->linked()) 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); m_skeletal_mesh_shader_program = m_skeletal_mesh_shader_template->build(m_shader_template_definitions);
if (!m_skeletal_mesh_shader_program->linked()) if (!m_skeletal_mesh_shader_program->linked())

src/engine/render/passes/cascaded-shadow-map-pass.hpp → src/engine/render/stages/cascaded-shadow-map-stage.hpp View File

@ -17,44 +17,37 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/ */
#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 <engine/render/pass.hpp>
#include <engine/math/vector.hpp>
#include <engine/scene/directional-light.hpp>
#include <engine/render/stage.hpp>
#include <engine/gl/shader-template.hpp>
#include <engine/gl/shader-program.hpp> #include <engine/gl/shader-program.hpp>
#include <engine/gl/shader-variable.hpp> #include <engine/gl/shader-variable.hpp>
#include <engine/gl/rasterizer.hpp>
#include <engine/scene/directional-light.hpp>
#include <engine/resources/resource-manager.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
class resource_manager;
namespace render { namespace render {
/** /**
* Renders cascaded shadow maps for directional lights. * Renders cascaded shadow maps for directional lights.
*/ */
class cascaded_shadow_map_pass: public pass
class cascaded_shadow_map_stage: public stage
{ {
public: 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. * Sets the maximum bone count for shadow-casting skeletal meshes.
@ -72,13 +65,18 @@ public:
} }
private: 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. * Renders an atlas of cascaded shadow maps for a single directional light.
* *
* @param light Shadow-casting directional light. * @param light Shadow-casting directional light.
* @param ctx Render context. * @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. /// Rebuilds the shader program for static meshes.
void rebuild_static_mesh_shader_program(); void rebuild_static_mesh_shader_program();
@ -86,6 +84,8 @@ private:
/// Rebuilds the shader program for skeletal meshes. /// Rebuilds the shader program for skeletal meshes.
void rebuild_skeletal_mesh_shader_program(); void rebuild_skeletal_mesh_shader_program();
gl::rasterizer* m_rasterizer;
std::size_t m_max_bone_count{64}; std::size_t m_max_bone_count{64};
std::unordered_map<std::string, std::string> m_shader_template_definitions; std::unordered_map<std::string, std::string> m_shader_template_definitions;
@ -102,4 +102,4 @@ private:
} // namespace render } // namespace render
#endif // ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_PASS_HPP
#endif // ANTKEEPER_RENDER_CASCADED_SHADOW_MAP_STAGE_HPP

+ 5
- 7
src/engine/scene/camera.cpp View File

@ -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 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 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}; 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::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}; 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); return math::fvec3(result);
} }

+ 12
- 16
src/engine/scene/directional-light.cpp View File

@ -21,10 +21,7 @@
namespace scene { 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); set_shadow_bias(m_shadow_bias);
update_shadow_cascade_distances(); 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 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_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_scale_bias_matrices();
update_shadow_cascade_distances(); 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(); 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 void directional_light::set_shadow_cascade_distribution(float weight) noexcept
{ {
m_shadow_cascade_distribution = weight; m_shadow_cascade_distribution = weight;
@ -90,10 +89,7 @@ void directional_light::illuminance_updated()
void directional_light::update_shadow_scale_bias_matrices() 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}); 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 // Apply cascade scale
@ -113,14 +109,14 @@ void directional_light::update_shadow_cascade_distances()
return; 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) for (unsigned int i = 0; i < m_shadow_cascade_count - 1; ++i)
{ {
const auto weight = static_cast<float>(i + 1) / static_cast<float>(m_shadow_cascade_count); const auto weight = static_cast<float>(i + 1) / static_cast<float>(m_shadow_cascade_count);
// Calculate linear and logarithmic distribution distances // 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 // Interpolate between linear and logarithmic distribution distances
// cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution()); // cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution());

+ 26
- 13
src/engine/scene/directional-light.hpp View File

@ -24,7 +24,6 @@
#include <engine/gl/texture-2d.hpp> #include <engine/gl/texture-2d.hpp>
#include <engine/math/vector.hpp> #include <engine/math/vector.hpp>
#include <memory> #include <memory>
#include <vector>
#include <span> #include <span>
namespace scene { namespace scene {
@ -136,11 +135,18 @@ public:
void set_shadow_cascade_count(unsigned int count) noexcept; 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. * Sets the shadow cascade distribution.
@ -173,10 +179,16 @@ public:
return m_shadow_cascade_count; 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. /// Returns the shadow cascade distribution weight.
@ -187,11 +199,11 @@ public:
/// Returns the array of shadow cascade far clipping plane distances. /// Returns the array of shadow cascade far clipping plane distances.
/// @{ /// @{
[[nodiscard]] inline constexpr std::span<const float> get_shadow_cascade_distances() const noexcept
[[nodiscard]] inline constexpr const math::fvec4& get_shadow_cascade_distances() const noexcept
{ {
return m_shadow_cascade_distances; return m_shadow_cascade_distances;
} }
[[nodiscard]] inline constexpr std::span<float> get_shadow_cascade_distances() noexcept
[[nodiscard]] inline constexpr math::fvec4& get_shadow_cascade_distances() noexcept
{ {
return m_shadow_cascade_distances; return m_shadow_cascade_distances;
} }
@ -233,11 +245,12 @@ private:
std::shared_ptr<gl::framebuffer> m_shadow_framebuffer{nullptr}; std::shared_ptr<gl::framebuffer> m_shadow_framebuffer{nullptr};
float m_shadow_bias{0.005f}; float m_shadow_bias{0.005f};
unsigned int m_shadow_cascade_count{4}; 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}; float m_shadow_cascade_distribution{0.8f};
std::vector<float> m_shadow_cascade_distances;
std::vector<math::fmat4> m_shadow_cascade_matrices;
std::vector<math::fmat4> 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 } // namespace scene

+ 1
- 1
src/engine/scene/point-light.cpp View File

@ -26,7 +26,7 @@ void point_light::color_updated()
m_colored_luminous_flux = get_color() * m_luminous_flux; 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; m_colored_luminous_flux = get_color() * m_luminous_flux;
} }

+ 0
- 4
src/game/game.cpp View File

@ -85,7 +85,6 @@
#include <engine/render/passes/material-pass.hpp> #include <engine/render/passes/material-pass.hpp>
#include <engine/render/passes/outline-pass.hpp> #include <engine/render/passes/outline-pass.hpp>
#include <engine/render/passes/resample-pass.hpp> #include <engine/render/passes/resample-pass.hpp>
#include <engine/render/passes/cascaded-shadow-map-pass.hpp>
#include <engine/render/passes/sky-pass.hpp> #include <engine/render/passes/sky-pass.hpp>
#include <engine/render/renderer.hpp> #include <engine/render/renderer.hpp>
#include <engine/render/vertex-attribute.hpp> #include <engine/render/vertex-attribute.hpp>
@ -735,8 +734,6 @@ void game::setup_rendering()
// Setup surface compositor // Setup surface compositor
{ {
surface_cascaded_shadow_map_pass = std::make_unique<render::cascaded_shadow_map_pass>(window->get_rasterizer(), resource_manager.get());
surface_clear_pass = std::make_unique<render::clear_pass>(window->get_rasterizer(), hdr_framebuffer.get()); surface_clear_pass = std::make_unique<render::clear_pass>(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_color({0.0f, 0.0f, 0.0f, 1.0f});
surface_clear_pass->set_clear_depth(0.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_outline_pass->set_outline_color(math::fvec4{1.0f, 1.0f, 1.0f, 1.0f});
surface_compositor = std::make_unique<render::compositor>(); surface_compositor = std::make_unique<render::compositor>();
surface_compositor->add_pass(surface_cascaded_shadow_map_pass.get());
surface_compositor->add_pass(surface_clear_pass.get()); surface_compositor->add_pass(surface_clear_pass.get());
surface_compositor->add_pass(sky_pass.get()); surface_compositor->add_pass(sky_pass.get());
surface_compositor->add_pass(surface_material_pass.get()); surface_compositor->add_pass(surface_material_pass.get());

+ 0
- 2
src/game/game.hpp View File

@ -90,7 +90,6 @@ namespace render
class material_pass; class material_pass;
class renderer; class renderer;
class outline_pass; class outline_pass;
class cascaded_shadow_map_pass;
class simple_render_pass; class simple_render_pass;
class sky_pass; class sky_pass;
} }
@ -333,7 +332,6 @@ public:
std::unique_ptr<render::clear_pass> underground_clear_pass; std::unique_ptr<render::clear_pass> underground_clear_pass;
std::unique_ptr<render::material_pass> underground_material_pass; std::unique_ptr<render::material_pass> underground_material_pass;
std::unique_ptr<render::compositor> underground_compositor; std::unique_ptr<render::compositor> underground_compositor;
std::unique_ptr<render::cascaded_shadow_map_pass> surface_cascaded_shadow_map_pass;
std::unique_ptr<render::clear_pass> surface_clear_pass; std::unique_ptr<render::clear_pass> surface_clear_pass;
std::unique_ptr<render::sky_pass> sky_pass; std::unique_ptr<render::sky_pass> sky_pass;
std::unique_ptr<render::material_pass> surface_material_pass; std::unique_ptr<render::material_pass> surface_material_pass;

+ 4
- 4
src/game/states/experiments/treadmill-experiment-state.cpp View File

@ -117,10 +117,10 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
// Create nest exterior // Create nest exterior
{ {
scene_component nest_exterior_scene_component; scene_component nest_exterior_scene_component;
nest_exterior_scene_component.object = std::make_shared<scene::static_mesh>(ctx.resource_manager->load<render::model>("cube-nest-200mm-exterior.mdl"));
nest_exterior_scene_component.object = std::make_shared<scene::static_mesh>(ctx.resource_manager->load<render::model>("cube-nest-200mm-interior.mdl"));
nest_exterior_scene_component.layer_mask = 1; nest_exterior_scene_component.layer_mask = 1;
auto nest_exterior_mesh = ctx.resource_manager->load<geom::brep_mesh>("cube-nest-200mm-exterior.msh");
auto nest_exterior_mesh = ctx.resource_manager->load<geom::brep_mesh>("cube-nest-200mm-interior.msh");
auto nest_exterior_rigid_body = std::make_unique<physics::rigid_body>(); auto nest_exterior_rigid_body = std::make_unique<physics::rigid_body>();
nest_exterior_rigid_body->set_mass(0.0f); 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.near_focal_plane_height = 8.0 * subject_scale;
spring_arm.far_focal_plane_height = 80.0 * subject_scale; spring_arm.far_focal_plane_height = 80.0 * subject_scale;
spring_arm.near_hfov = math::radians(90.0); 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.zoom = 0.25;
spring_arm.focal_point_offset = {0, static_cast<double>(worker_phenome->legs->standing_height) * subject_scale, 0}; spring_arm.focal_point_offset = {0, static_cast<double>(worker_phenome->legs->standing_height) * subject_scale, 0};

+ 2
- 1
src/game/world.cpp View File

@ -362,7 +362,8 @@ void create_sun(::game& ctx)
ctx.sun_light->set_shadow_caster(true); ctx.sun_light->set_shadow_caster(true);
ctx.sun_light->set_shadow_framebuffer(ctx.shadow_map_framebuffer); ctx.sun_light->set_shadow_framebuffer(ctx.shadow_map_framebuffer);
ctx.sun_light->set_shadow_bias(0.005f); 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_count(4);
ctx.sun_light->set_shadow_cascade_distribution(0.8f); ctx.sun_light->set_shadow_cascade_distribution(0.8f);

Loading…
Cancel
Save