diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 0c84348b699..60648ded089 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -3041,10 +3041,10 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul } for (uint32_t j = 0; j < cull_data.cull->shadow_count; j++) { - if (!light_culler->cull_directional_light(cull_data.scenario->instance_aabbs[i], j)) { - continue; - } for (uint32_t k = 0; k < cull_data.cull->shadows[j].cascade_count; k++) { + if (!light_culler->cull_directional_light(cull_data.scenario->instance_aabbs[i], j, k)) { // pass the cascade index + continue; + } if (IN_FRUSTUM(cull_data.cull->shadows[j].cascades[k].frustum) && VIS_CHECK) { uint32_t base_type = idata.flags & InstanceData::FLAG_BASE_TYPE_MASK; diff --git a/servers/rendering/rendering_light_culler.cpp b/servers/rendering/rendering_light_culler.cpp index d70433006d8..dce5626b03f 100644 --- a/servers/rendering/rendering_light_culler.cpp +++ b/servers/rendering/rendering_light_culler.cpp @@ -100,8 +100,26 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i break; case RS::LIGHT_DIRECTIONAL: lsource.type = LightSource::ST_DIRECTIONAL; - // Could deal with a max directional shadow range here? NYI - // LIGHT_PARAM_SHADOW_MAX_DISTANCE + + lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE); + switch (RSG::light_storage->light_directional_get_shadow_mode(p_instance.base)) { + case RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL: + lsource.cascade_count = 1; + break; + case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS: + lsource.cascade_count = 2; + break; + case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS: + lsource.cascade_count = 4; + break; + default: + ERR_FAIL_V_MSG(false, "Only directional lights with 1, 2, or 4 shadow cascades are supported."); + break; + } + lsource.cascade_splits[0] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET); + lsource.cascade_splits[1] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET); + lsource.cascade_splits[2] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET); + lsource.blend_splits = RSG::light_storage->light_directional_get_blend_splits(p_instance.base); break; } @@ -109,11 +127,84 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i lsource.dir = -p_instance.transform.basis.get_column(2); lsource.dir.normalize(); - bool visible; + // In reality there's always going to be at least one cascade, but the compiler can't know that. + // If SOMEHOW there's actually 0 cascades though, I suppose there isn't going to be anything visible after all. + bool visible = false; if (p_directional_light_id == -1) { - visible = _add_light_camera_planes(data.regular_cull_planes, lsource); + visible = _add_light_camera_planes(data.regular_cull_planes, lsource, { &data.frustum_planes[0], data.frustum_points }); } else { - visible = _add_light_camera_planes(data.directional_cull_planes[p_directional_light_id], lsource); + int used_planes = 1 + lsource.cascade_count; // 2 for ortho (near+far), 3 for pssm2 (near+mid+far), 5 for pssm4 (near+3mids+far). + Plane boundary_planes[5]; + { + constexpr const int MAX_PLANES = 5; + real_t plane_distances[MAX_PLANES] = { + data.camera_projection.get_z_near(), + lsource.cascade_splits[0] * lsource.range, + lsource.cascade_splits[1] * lsource.range, + lsource.cascade_splits[2] * lsource.range, + lsource.range, + }; + //If not 4 cascades, replace last used cascade plane distance with max shadow range (shadow far plane distance). + plane_distances[used_planes - 1] = lsource.range; +#ifdef LIGHT_CULLER_DEBUG_LOGGING + if (is_logging()) { + print_line("cascade split planes (first " + itos(used_planes) + " used): " + + String(Variant(plane_distances[0])) + "m, " + + String(Variant(plane_distances[1])) + "m, " + + String(Variant(plane_distances[2])) + "m, " + + String(Variant(plane_distances[3])) + "m, " + + String(Variant(plane_distances[4])) + "m"); + } +#endif + Vector3 camera_normal = data.camera_transform.basis.xform(Vector3(0, 0, 1)).normalized(); + for (int i = 0; i < used_planes; i++) { + real_t plane_distance = plane_distances[i]; + + //Plane compute + boundary_planes[i] = Plane( + camera_normal, + data.camera_transform.origin + camera_normal * -plane_distance); + } + } + + for (int i = 0; i < lsource.cascade_count; i++) { + /* + enum PlaneOrder { + PLANE_NEAR, + PLANE_FAR, + PLANE_LEFT, + PLANE_TOP, + PLANE_RIGHT, + PLANE_BOTTOM, + PLANE_TOTAL, + }; + */ + Plane cull_planes[6] = { + boundary_planes[MAX(i - (lsource.blend_splits ? 1 : 0), 0)], + Plane(-boundary_planes[i + 1].normal, -boundary_planes[i + 1].d), // Normal flip to ensure far is outward-facing. + data.frustum_planes[2], + data.frustum_planes[3], + data.frustum_planes[4], + data.frustum_planes[5], + }; + +#ifdef LIGHT_CULLER_DEBUG_LOGGING + if (is_logging()) { + for (int p = 0; p < 6; p++) { + print_line("cascade " + itos(i) + " plane " + itos(p) + " : " + String(cull_planes[p])); + } + } +#endif + + // Frustum point calculation + Vector3 frustum_points[8]; + bool success = create_frustum_points(cull_planes, frustum_points); + ERR_FAIL_COND_V(!success, false); + + // Replace frustum arguments with cascade's. + LightCullPlanes &destination = data.directional_cull_planes[p_directional_light_id].planes[i]; + visible = _add_light_camera_planes(destination, lsource, { cull_planes, frustum_points }); + } } if (data.light_culling_active) { @@ -122,14 +213,14 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i return true; } -bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id) { +bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade) { if (!data.is_active() || !is_caster_culling_active()) { return true; } ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true); - LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id]; + LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id].planes[p_cascade]; Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]); Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]); @@ -228,18 +319,20 @@ void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) { // Directional lights are different to points, as the origin is infinitely in the distance, so the plane third // points are derived differently. -bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) { +bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) { uint32_t lookup = 0; r_cull_planes.num_cull_planes = 0; + const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes; + // Directional light, we will use dot against the light direction to determine back facing planes. for (int n = 0; n < 6; n++) { - float dot = data.frustum_planes[n].normal.dot(p_light_source.dir); + float dot = cull_frustum_planes[n].normal.dot(p_light_source.dir); if (dot > 0.0f) { lookup |= 1 << n; // Add backfacing camera frustum planes. - r_cull_planes.add_cull_plane(data.frustum_planes[n]); + r_cull_planes.add_cull_plane(cull_frustum_planes[n]); } } @@ -252,8 +345,8 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes & // Should never happen with directional light?? This may be able to be removed. if (lookup == 63) { r_cull_planes.num_cull_planes = 0; - for (int n = 0; n < data.frustum_planes.size(); n++) { - r_cull_planes.add_cull_plane(data.frustum_planes[n]); + for (int n = 0; n < 6; n++) { + r_cull_planes.add_cull_plane(cull_frustum_planes[n]); } return true; @@ -270,11 +363,14 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes & int n_edges = data.LUT_entry_sizes[lookup] - 1; #endif + const Vector3 *const frustum_points = p_cull_frustum.frustum_points; + for (int e = 0; e < n_edges; e++) { int i0 = entry[e]; int i1 = entry[e + 1]; - const Vector3 &pt0 = data.frustum_points[i0]; - const Vector3 &pt1 = data.frustum_points[i1]; + + const Vector3 &pt0 = frustum_points[i0]; + const Vector3 &pt1 = frustum_points[i1]; // Create a third point from the light direction. Vector3 pt2 = pt0 - p_light_source.dir; @@ -291,8 +387,8 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes & int i0 = entry[n_edges]; // Last. int i1 = entry[0]; // First. - const Vector3 &pt0 = data.frustum_points[i0]; - const Vector3 &pt1 = data.frustum_points[i1]; + const Vector3 &pt0 = frustum_points[i0]; + const Vector3 &pt1 = frustum_points[i1]; // Create a third point from the light direction. Vector3 pt2 = pt0 - p_light_source.dir; @@ -313,20 +409,19 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes & return true; } -bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) { +bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) { if (!data.is_active()) { return true; } - // We should have called prepare_camera before this. - ERR_FAIL_COND_V(data.frustum_planes.size() != 6, true); + const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes; switch (p_light_source.type) { case LightSource::ST_SPOTLIGHT: case LightSource::ST_OMNI: break; case LightSource::ST_DIRECTIONAL: - return add_light_camera_planes_directional(r_cull_planes, p_light_source); + return add_light_camera_planes_directional(r_cull_planes, p_light_source, p_cull_frustum); break; default: return false; // not yet supported @@ -352,12 +447,12 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan // OMNIS if (p_light_source.type == LightSource::ST_OMNI) { for (int n = 0; n < 6; n++) { - float dist = data.frustum_planes[n].distance_to(p_light_source.pos); + float dist = cull_frustum_planes[n].distance_to(p_light_source.pos); if (dist < 0.0f) { lookup |= 1 << n; // Add backfacing camera frustum planes. - r_cull_planes.add_cull_plane(data.frustum_planes[n]); + r_cull_planes.add_cull_plane(cull_frustum_planes[n]); } else { // Is the light out of range? // This is one of the tests. If the point source is more than range distance from a frustum plane, it can't @@ -381,13 +476,13 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan float end_cone_radius = radius_at_dist_one * p_light_source.range; for (int n = 0; n < 6; n++) { - float dist = data.frustum_planes[n].distance_to(p_light_source.pos); + float dist = cull_frustum_planes[n].distance_to(p_light_source.pos); if (dist < 0.0f) { // Either the plane is backfacing or we are inside the frustum. lookup |= 1 << n; // Add backfacing camera frustum planes. - r_cull_planes.add_cull_plane(data.frustum_planes[n]); + r_cull_planes.add_cull_plane(cull_frustum_planes[n]); } else { // The light is in front of the plane. @@ -402,7 +497,7 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan // If the cone end point is further than the maximum possible distance to the plane // we can guarantee that the cone does not cross the plane, and hence the cone // is outside the frustum. - float dist_end = data.frustum_planes[n].distance_to(pos_end); + float dist_end = cull_frustum_planes[n].distance_to(pos_end); if (dist_end >= end_cone_radius) { data.out_of_range = true; @@ -420,8 +515,8 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan // render shadow casters outside the frustum as shadows can never re-enter the frustum. if (lookup == 63) { r_cull_planes.num_cull_planes = 0; - for (int n = 0; n < data.frustum_planes.size(); n++) { - r_cull_planes.add_cull_plane(data.frustum_planes[n]); + for (int n = 0; n < 6; n++) { + r_cull_planes.add_cull_plane(cull_frustum_planes[n]); } return true; @@ -431,13 +526,15 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan uint8_t *entry = &data.LUT_entries[lookup][0]; int n_edges = data.LUT_entry_sizes[lookup] - 1; + const Vector3 *const frustum_points = p_cull_frustum.frustum_points; + const Vector3 &pt2 = p_light_source.pos; for (int e = 0; e < n_edges; e++) { int i0 = entry[e]; int i1 = entry[e + 1]; - const Vector3 &pt0 = data.frustum_points[i0]; - const Vector3 &pt1 = data.frustum_points[i1]; + const Vector3 &pt0 = frustum_points[i0]; + const Vector3 &pt1 = frustum_points[i1]; if (!_is_colinear_tri(pt0, pt1, pt2)) { // Create plane from 3 points. @@ -451,8 +548,8 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan int i0 = entry[n_edges]; // Last. int i1 = entry[0]; // First. - const Vector3 &pt0 = data.frustum_points[i0]; - const Vector3 &pt1 = data.frustum_points[i1]; + const Vector3 &pt0 = frustum_points[i0]; + const Vector3 &pt1 = frustum_points[i1]; if (!_is_colinear_tri(pt0, pt1, pt2)) { // Create plane from 3 points. @@ -493,6 +590,9 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co if (!data.is_active()) { return false; } + // These are needed later to build per-cascade cull frustums for directional lights. + data.camera_transform = p_cam_transform; + data.camera_projection = p_cam_matrix; // Get the camera frustum planes in world space. data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform); @@ -524,6 +624,10 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co } #endif + return create_frustum_points(&data.frustum_planes[0], data.frustum_points); +} + +bool RenderingLightCuller::create_frustum_points(const Plane *p_frustum_planes, Vector3 *r_result) const { // We want to calculate the frustum corners in a specific order. const Projection::Planes intersections[8][3] = { { Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP }, @@ -538,18 +642,17 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co for (int i = 0; i < 8; i++) { // 3 plane intersection, gives us a point. - bool res = data.frustum_planes[intersections[i][0]].intersect_3(data.frustum_planes[intersections[i][1]], data.frustum_planes[intersections[i][2]], &data.frustum_points[i]); + bool res = p_frustum_planes[intersections[i][0]].intersect_3(p_frustum_planes[intersections[i][1]], p_frustum_planes[intersections[i][2]], &r_result[i]); // What happens with a zero frustum? NYI - deal with this. ERR_FAIL_COND_V(!res, false); #ifdef LIGHT_CULLER_DEBUG_LOGGING if (is_logging()) { - print_line("point " + itos(i) + " -> " + String(data.frustum_points[i])); + print_line("point " + itos(i) + " -> " + String(result[i])); } #endif } - return true; } diff --git a/servers/rendering/rendering_light_culler.h b/servers/rendering/rendering_light_culler.h index 2de786e8545..d849572c0ba 100644 --- a/servers/rendering/rendering_light_culler.h +++ b/servers/rendering/rendering_light_culler.h @@ -43,7 +43,7 @@ struct Transform3D; // Uncomment LIGHT_CULLER_DEBUG_LOGGING to get periodic print of the number of casters culled before / after. // Uncomment LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT to get periodic print of the number of casters culled for the directional light.. -// #define LIGHT_CULLER_DEBUG_LOGGING +// #define LIGHT_CULLER_DEBUG_LOGGING // #define LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT // #define LIGHT_CULLER_DEBUG_REGULAR_LIGHT // #define LIGHT_CULLER_DEBUG_FLASH @@ -84,6 +84,9 @@ private: type = ST_UNKNOWN; angle = 0.0f; range = FLT_MAX; + cascade_count = 0; + cascade_splits[0] = cascade_splits[1] = cascade_splits[2] = 0; + blend_splits = false; } // All in world space, culling done in world space. @@ -93,6 +96,18 @@ private: float angle; // For spotlight. float range; + + int cascade_count; + float cascade_splits[3]; // Max 4 cascades, which only has 3 splits. + bool blend_splits; + }; + + // Directional lights have separate cull frustums for each cascade, so this struct is needed to specify which one to use for each cull step. + struct CullFrustumData { + // Functions expect this to store a frustum, so it must be ALWAYS at least Plane[6]. Undefined behavior otherwise. + const Plane *frustum_planes = nullptr; + // Functions expect this to store frustum corners, so it must be ALWAYS at least Vector3[8]. UB otherwise. + const Vector3 *frustum_points = nullptr; }; // Same order as godot. @@ -144,7 +159,7 @@ public: void prepare_directional_light(const RendererSceneCull::Instance *p_instance, int32_t p_directional_light_id); // Return false if the instance is to be culled. - bool cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id); + bool cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade); // Can turn on and off from the engine if desired. void set_caster_culling_active(bool p_active) { data.caster_culling_active = p_active; } @@ -160,11 +175,20 @@ private: #endif }; + struct DirectionalCullPlanes { + LightCullPlanes planes[4]; // One set of cull planes per cascade + }; + bool _prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id = -1); // Avoid adding extra culling planes derived from near colinear triangles. // The normals derived from these will be inaccurate, and can lead to false // culling of objects that should be within the light volume. + // See: + // - issue GH-89702 "Tighter Shadow Caster Culling causes some object shadows to not render for a Frame" + // - issue GH-89560 "Directional Shadows disappear with large Camera Z Far values at some angles" + // - issue GH-91976 "SpotLight3D shadows exhibit flickering when moved around." + // - PR GH-92078 which gave it the current value. bool _is_colinear_tri(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c) const { // Lengths of sides a, b and c. float la = (p_b - p_a).length(); @@ -187,6 +211,14 @@ private: float ld = ((la + lb) - lc) / lc; // ld will be close to zero for colinear tris. + + // Long frustums are made out of significantly stretched-out triangles, + // so large threshold will produce large amounts of cullable but unculled meshes. + // For example: 0.001 fails to cull cullable meshes with camera FOV of 70 and ortho shadows at ~50m at certain view angles. + // 0.0001 fails to cull cullable meshes with camera FOV of 70 and ortho shadows at ~500m at certain view angles. + // ...These apply less to cascades since they have large near planes in comparison to far planes, unlike ortho lights. + // If you're reading this and the value for directional lights is still 0.001f, + // that is fine as is and it only means GH-115176 didn't make it. return ld < 0.001f; } @@ -196,15 +228,18 @@ private: } // Internal version uses LightSource. - bool _add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source); + bool _add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum); // Directional light gives parallel culling planes (as opposed to point lights). - bool add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source); + bool add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum); // Is the light culler active? maybe not in the editor... bool is_caster_culling_active() const { return data.caster_culling_active; } bool is_light_culling_active() const { return data.light_culling_active; } + // Ensure result is at least a Plane[6] for the frustum input and Vector3[8] to store result before calling. If not, undefined behavior. + bool create_frustum_points(const Plane *p_frustum_planes, Vector3 *r_result) const; + // Do we want to log some debug output? bool is_logging() const { return data.debug_count == 0; } @@ -220,7 +255,10 @@ private: // chops and changes between culling different lights // instead of doing one by one, and we don't want to prepare // lights multiple times per frame. - LocalVector directional_cull_planes; + LocalVector directional_cull_planes; + + Transform3D camera_transform; + Projection camera_projection; // Single threaded cull planes for regular lights // (OMNI, SPOT). These lights reuse the same set of cull plane data.