@@ -100,20 +100,111 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i
100100 break ;
101101 case RS ::LIGHT_DIRECTIONAL :
102102 lsource.type = LightSource::ST_DIRECTIONAL ;
103- // Could deal with a max directional shadow range here? NYI
104- // LIGHT_PARAM_SHADOW_MAX_DISTANCE
103+
104+ lsource.range = RSG ::light_storage->light_get_param (p_instance.base , RS ::LIGHT_PARAM_SHADOW_MAX_DISTANCE );
105+ switch (RSG ::light_storage->light_directional_get_shadow_mode (p_instance.base )) {
106+ case RS ::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL :
107+ lsource.cascade_count = 1 ;
108+ break ;
109+ case RS ::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS :
110+ lsource.cascade_count = 2 ;
111+ break ;
112+ case RS ::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS :
113+ lsource.cascade_count = 4 ;
114+ break ;
115+ default :
116+ ERR_FAIL_V_MSG (false , " Only directional lights with 1, 2, or 4 shadow cascades are supported." );
117+ break ;
118+ }
119+ lsource.cascade_splits [0 ] = RSG ::light_storage->light_get_param (p_instance.base , RS ::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET );
120+ lsource.cascade_splits [1 ] = RSG ::light_storage->light_get_param (p_instance.base , RS ::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET );
121+ lsource.cascade_splits [2 ] = RSG ::light_storage->light_get_param (p_instance.base , RS ::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET );
122+ lsource.blend_splits = RSG ::light_storage->light_directional_get_blend_splits (p_instance.base );
105123 break ;
106124 }
107125
108126 lsource.pos = p_instance.transform .origin ;
109127 lsource.dir = -p_instance.transform .basis .get_column (2 );
110128 lsource.dir .normalize ();
111129
112- bool visible;
130+ // In reality there's always going to be at least one cascade, but the compiler can't know that.
131+ // If SOMEHOW there's actually 0 cascades though, I suppose there isn't going to be anything visible after all.
132+ bool visible = false ;
113133 if (p_directional_light_id == -1 ) {
114- visible = _add_light_camera_planes (data.regular_cull_planes , lsource);
134+ visible = _add_light_camera_planes (data.regular_cull_planes , lsource, { &data. frustum_planes [ 0 ], data. frustum_points } );
115135 } else {
116- visible = _add_light_camera_planes (data.directional_cull_planes [p_directional_light_id], lsource);
136+ int used_planes = 1 + lsource.cascade_count ; // 2 for ortho (near+far), 3 for pssm2 (near+mid+far), 5 for pssm4 (near+3mids+far).
137+ Plane boundary_planes[5 ];
138+ {
139+ constexpr const int MAX_PLANES = 5 ;
140+ real_t plane_distances[MAX_PLANES ] = {
141+ data.camera_projection .get_z_near (),
142+ lsource.cascade_splits [0 ] * lsource.range ,
143+ lsource.cascade_splits [1 ] * lsource.range ,
144+ lsource.cascade_splits [2 ] * lsource.range ,
145+ lsource.range ,
146+ };
147+ // If not 4 cascades, replace last used cascade plane distance with max shadow range (shadow far plane distance).
148+ plane_distances[used_planes - 1 ] = lsource.range ;
149+ #ifdef LIGHT_CULLER_DEBUG_LOGGING
150+ if (is_logging ()) {
151+ print_line (" cascade split planes (first " + itos (used_planes) + " used): " +
152+ String (Variant (plane_distances[0 ])) + " m, " +
153+ String (Variant (plane_distances[1 ])) + " m, " +
154+ String (Variant (plane_distances[2 ])) + " m, " +
155+ String (Variant (plane_distances[3 ])) + " m, " +
156+ String (Variant (plane_distances[4 ])) + " m" );
157+ }
158+ #endif
159+ Vector3 camera_normal = data.camera_transform .basis .xform (Vector3 (0 , 0 , 1 )).normalized ();
160+ for (int i = 0 ; i < used_planes; i++) {
161+ real_t plane_distance = plane_distances[i];
162+
163+ // Plane compute
164+ boundary_planes[i] = Plane (
165+ camera_normal,
166+ data.camera_transform .origin + camera_normal * -plane_distance);
167+ }
168+ }
169+
170+ for (int i = 0 ; i < lsource.cascade_count ; i++) {
171+ /*
172+ enum PlaneOrder {
173+ PLANE_NEAR,
174+ PLANE_FAR,
175+ PLANE_LEFT,
176+ PLANE_TOP,
177+ PLANE_RIGHT,
178+ PLANE_BOTTOM,
179+ PLANE_TOTAL,
180+ };
181+ */
182+ Plane cull_planes[6 ] = {
183+ boundary_planes[MAX (i - (lsource.blend_splits ? 1 : 0 ), 0 )],
184+ Plane (-boundary_planes[i + 1 ].normal , -boundary_planes[i + 1 ].d ), // Normal flip to ensure far is outward-facing.
185+ data.frustum_planes [2 ],
186+ data.frustum_planes [3 ],
187+ data.frustum_planes [4 ],
188+ data.frustum_planes [5 ],
189+ };
190+
191+ #ifdef LIGHT_CULLER_DEBUG_LOGGING
192+ if (is_logging ()) {
193+ for (int p = 0 ; p < 6 ; p++) {
194+ print_line (" cascade " + itos (i) + " plane " + itos (p) + " : " + String (cull_planes[p]));
195+ }
196+ }
197+ #endif
198+
199+ // Frustum point calculation
200+ Vector3 frustum_points[8 ];
201+ bool success = create_frustum_points (cull_planes, frustum_points);
202+ ERR_FAIL_COND_V (!success, false );
203+
204+ // Replace frustum arguments with cascade's.
205+ LightCullPlanes &destination = data.directional_cull_planes [p_directional_light_id].planes [i];
206+ visible = _add_light_camera_planes (destination, lsource, { cull_planes, frustum_points });
207+ }
117208 }
118209
119210 if (data.light_culling_active ) {
@@ -122,14 +213,14 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i
122213 return true ;
123214}
124215
125- bool RenderingLightCuller::cull_directional_light (const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id) {
216+ bool RenderingLightCuller::cull_directional_light (const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade ) {
126217 if (!data.is_active () || !is_caster_culling_active ()) {
127218 return true ;
128219 }
129220
130221 ERR_FAIL_INDEX_V (p_directional_light_id, (int32_t )data.directional_cull_planes .size (), true );
131222
132- LightCullPlanes &cull_planes = data.directional_cull_planes [p_directional_light_id];
223+ LightCullPlanes &cull_planes = data.directional_cull_planes [p_directional_light_id]. planes [p_cascade] ;
133224
134225 Vector3 mins = Vector3 (p_bound.bounds [0 ], p_bound.bounds [1 ], p_bound.bounds [2 ]);
135226 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) {
228319
229320// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third
230321// points are derived differently.
231- bool RenderingLightCuller::add_light_camera_planes_directional (LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {
322+ bool RenderingLightCuller::add_light_camera_planes_directional (LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum ) {
232323 uint32_t lookup = 0 ;
233324 r_cull_planes.num_cull_planes = 0 ;
234325
326+ const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes ;
327+
235328 // Directional light, we will use dot against the light direction to determine back facing planes.
236329 for (int n = 0 ; n < 6 ; n++) {
237- float dot = data. frustum_planes [n].normal .dot (p_light_source.dir );
330+ float dot = cull_frustum_planes [n].normal .dot (p_light_source.dir );
238331 if (dot > 0 .0f ) {
239332 lookup |= 1 << n;
240333
241334 // Add backfacing camera frustum planes.
242- r_cull_planes.add_cull_plane (data. frustum_planes [n]);
335+ r_cull_planes.add_cull_plane (cull_frustum_planes [n]);
243336 }
244337 }
245338
@@ -252,8 +345,8 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &
252345 // Should never happen with directional light?? This may be able to be removed.
253346 if (lookup == 63 ) {
254347 r_cull_planes.num_cull_planes = 0 ;
255- for (int n = 0 ; n < data. frustum_planes . size () ; n++) {
256- r_cull_planes.add_cull_plane (data. frustum_planes [n]);
348+ for (int n = 0 ; n < 6 ; n++) {
349+ r_cull_planes.add_cull_plane (cull_frustum_planes [n]);
257350 }
258351
259352 return true ;
@@ -270,11 +363,14 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &
270363 int n_edges = data.LUT_entry_sizes [lookup] - 1 ;
271364#endif
272365
366+ const Vector3 *const frustum_points = p_cull_frustum.frustum_points ;
367+
273368 for (int e = 0 ; e < n_edges; e++) {
274369 int i0 = entry[e];
275370 int i1 = entry[e + 1 ];
276- const Vector3 &pt0 = data.frustum_points [i0];
277- const Vector3 &pt1 = data.frustum_points [i1];
371+
372+ const Vector3 &pt0 = frustum_points[i0];
373+ const Vector3 &pt1 = frustum_points[i1];
278374
279375 // Create a third point from the light direction.
280376 Vector3 pt2 = pt0 - p_light_source.dir ;
@@ -291,8 +387,8 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &
291387 int i0 = entry[n_edges]; // Last.
292388 int i1 = entry[0 ]; // First.
293389
294- const Vector3 &pt0 = data. frustum_points [i0];
295- const Vector3 &pt1 = data. frustum_points [i1];
390+ const Vector3 &pt0 = frustum_points[i0];
391+ const Vector3 &pt1 = frustum_points[i1];
296392
297393 // Create a third point from the light direction.
298394 Vector3 pt2 = pt0 - p_light_source.dir ;
@@ -313,20 +409,19 @@ bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &
313409 return true ;
314410}
315411
316- bool RenderingLightCuller::_add_light_camera_planes (LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {
412+ bool RenderingLightCuller::_add_light_camera_planes (LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum ) {
317413 if (!data.is_active ()) {
318414 return true ;
319415 }
320416
321- // We should have called prepare_camera before this.
322- ERR_FAIL_COND_V (data.frustum_planes .size () != 6 , true );
417+ const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes ;
323418
324419 switch (p_light_source.type ) {
325420 case LightSource::ST_SPOTLIGHT :
326421 case LightSource::ST_OMNI :
327422 break ;
328423 case LightSource::ST_DIRECTIONAL :
329- return add_light_camera_planes_directional (r_cull_planes, p_light_source);
424+ return add_light_camera_planes_directional (r_cull_planes, p_light_source, p_cull_frustum );
330425 break ;
331426 default :
332427 return false ; // not yet supported
@@ -352,12 +447,12 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
352447 // OMNIS
353448 if (p_light_source.type == LightSource::ST_OMNI ) {
354449 for (int n = 0 ; n < 6 ; n++) {
355- float dist = data. frustum_planes [n].distance_to (p_light_source.pos );
450+ float dist = cull_frustum_planes [n].distance_to (p_light_source.pos );
356451 if (dist < 0 .0f ) {
357452 lookup |= 1 << n;
358453
359454 // Add backfacing camera frustum planes.
360- r_cull_planes.add_cull_plane (data. frustum_planes [n]);
455+ r_cull_planes.add_cull_plane (cull_frustum_planes [n]);
361456 } else {
362457 // Is the light out of range?
363458 // 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
381476 float end_cone_radius = radius_at_dist_one * p_light_source.range ;
382477
383478 for (int n = 0 ; n < 6 ; n++) {
384- float dist = data. frustum_planes [n].distance_to (p_light_source.pos );
479+ float dist = cull_frustum_planes [n].distance_to (p_light_source.pos );
385480 if (dist < 0 .0f ) {
386481 // Either the plane is backfacing or we are inside the frustum.
387482 lookup |= 1 << n;
388483
389484 // Add backfacing camera frustum planes.
390- r_cull_planes.add_cull_plane (data. frustum_planes [n]);
485+ r_cull_planes.add_cull_plane (cull_frustum_planes [n]);
391486 } else {
392487 // The light is in front of the plane.
393488
@@ -402,7 +497,7 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
402497 // If the cone end point is further than the maximum possible distance to the plane
403498 // we can guarantee that the cone does not cross the plane, and hence the cone
404499 // is outside the frustum.
405- float dist_end = data. frustum_planes [n].distance_to (pos_end);
500+ float dist_end = cull_frustum_planes [n].distance_to (pos_end);
406501
407502 if (dist_end >= end_cone_radius) {
408503 data.out_of_range = true ;
@@ -420,8 +515,8 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
420515 // render shadow casters outside the frustum as shadows can never re-enter the frustum.
421516 if (lookup == 63 ) {
422517 r_cull_planes.num_cull_planes = 0 ;
423- for (int n = 0 ; n < data. frustum_planes . size () ; n++) {
424- r_cull_planes.add_cull_plane (data. frustum_planes [n]);
518+ for (int n = 0 ; n < 6 ; n++) {
519+ r_cull_planes.add_cull_plane (cull_frustum_planes [n]);
425520 }
426521
427522 return true ;
@@ -431,13 +526,15 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
431526 uint8_t *entry = &data.LUT_entries [lookup][0 ];
432527 int n_edges = data.LUT_entry_sizes [lookup] - 1 ;
433528
529+ const Vector3 *const frustum_points = p_cull_frustum.frustum_points ;
530+
434531 const Vector3 &pt2 = p_light_source.pos ;
435532
436533 for (int e = 0 ; e < n_edges; e++) {
437534 int i0 = entry[e];
438535 int i1 = entry[e + 1 ];
439- const Vector3 &pt0 = data. frustum_points [i0];
440- const Vector3 &pt1 = data. frustum_points [i1];
536+ const Vector3 &pt0 = frustum_points[i0];
537+ const Vector3 &pt1 = frustum_points[i1];
441538
442539 if (!_is_colinear_tri (pt0, pt1, pt2)) {
443540 // Create plane from 3 points.
@@ -451,8 +548,8 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan
451548 int i0 = entry[n_edges]; // Last.
452549 int i1 = entry[0 ]; // First.
453550
454- const Vector3 &pt0 = data. frustum_points [i0];
455- const Vector3 &pt1 = data. frustum_points [i1];
551+ const Vector3 &pt0 = frustum_points[i0];
552+ const Vector3 &pt1 = frustum_points[i1];
456553
457554 if (!_is_colinear_tri (pt0, pt1, pt2)) {
458555 // Create plane from 3 points.
@@ -493,6 +590,9 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co
493590 if (!data.is_active ()) {
494591 return false ;
495592 }
593+ // These are needed later to build per-cascade cull frustums for directional lights.
594+ data.camera_transform = p_cam_transform;
595+ data.camera_projection = p_cam_matrix;
496596
497597 // Get the camera frustum planes in world space.
498598 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
524624 }
525625#endif
526626
627+ return create_frustum_points (&data.frustum_planes [0 ], data.frustum_points );
628+ }
629+
630+ bool RenderingLightCuller::create_frustum_points (const Plane *p_frustum_planes, Vector3 *r_result) const {
527631 // We want to calculate the frustum corners in a specific order.
528632 const Projection::Planes intersections[8 ][3 ] = {
529633 { Projection::PLANE_FAR , Projection::PLANE_LEFT , Projection::PLANE_TOP },
@@ -538,18 +642,17 @@ bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, co
538642
539643 for (int i = 0 ; i < 8 ; i++) {
540644 // 3 plane intersection, gives us a point.
541- 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]);
645+ 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]);
542646
543647 // What happens with a zero frustum? NYI - deal with this.
544648 ERR_FAIL_COND_V (!res, false );
545649
546650#ifdef LIGHT_CULLER_DEBUG_LOGGING
547651 if (is_logging ()) {
548- print_line (" point " + itos (i) + " -> " + String (data. frustum_points [i]));
652+ print_line (" point " + itos (i) + " -> " + String (result [i]));
549653 }
550654#endif
551655 }
552-
553656 return true ;
554657}
555658
0 commit comments