Skip to content

Commit 4cce65b

Browse files
committed
Merge pull request #114678 from Image-unavailable/culling-wars-pr
Make directional lights' tighter shadow caster culling truly tight (implement per-cascade culling)
2 parents eaa21e1 + a17e172 commit 4cce65b

3 files changed

Lines changed: 183 additions & 42 deletions

File tree

servers/rendering/renderer_scene_cull.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3134,10 +3134,10 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
31343134
}
31353135

31363136
for (uint32_t j = 0; j < cull_data.cull->shadow_count; j++) {
3137-
if (!light_culler->cull_directional_light(cull_data.scenario->instance_aabbs[i], j)) {
3138-
continue;
3139-
}
31403137
for (uint32_t k = 0; k < cull_data.cull->shadows[j].cascade_count; k++) {
3138+
if (!light_culler->cull_directional_light(cull_data.scenario->instance_aabbs[i], j, k)) { // pass the cascade index
3139+
continue;
3140+
}
31413141
if (IN_FRUSTUM(cull_data.cull->shadows[j].cascades[k].frustum) && VIS_CHECK) {
31423142
uint32_t base_type = idata.flags & InstanceData::FLAG_BASE_TYPE_MASK;
31433143

servers/rendering/rendering_light_culler.cpp

Lines changed: 137 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)