Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/servers/rendering/rendering_light_culler.cpp
10277 views
1
/**************************************************************************/
2
/* rendering_light_culler.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "rendering_light_culler.h"
32
33
#include "core/math/plane.h"
34
#include "core/math/projection.h"
35
#include "rendering_server_globals.h"
36
37
#ifdef RENDERING_LIGHT_CULLER_DEBUG_STRINGS
38
const char *RenderingLightCuller::Data::string_planes[] = {
39
"NEAR",
40
"FAR",
41
"LEFT",
42
"TOP",
43
"RIGHT",
44
"BOTTOM",
45
};
46
const char *RenderingLightCuller::Data::string_points[] = {
47
"FAR_LEFT_TOP",
48
"FAR_LEFT_BOTTOM",
49
"FAR_RIGHT_TOP",
50
"FAR_RIGHT_BOTTOM",
51
"NEAR_LEFT_TOP",
52
"NEAR_LEFT_BOTTOM",
53
"NEAR_RIGHT_TOP",
54
"NEAR_RIGHT_BOTTOM",
55
};
56
57
String RenderingLightCuller::Data::plane_bitfield_to_string(unsigned int BF) {
58
String sz;
59
60
for (int n = 0; n < 6; n++) {
61
unsigned int bit = 1 << n;
62
if (BF & bit) {
63
sz += String(string_planes[n]) + ", ";
64
}
65
}
66
67
return sz;
68
}
69
#endif
70
71
void RenderingLightCuller::prepare_directional_light(const RendererSceneCull::Instance *p_instance, int32_t p_directional_light_id) {
72
//data.directional_light = p_instance;
73
// Something is probably going wrong, we shouldn't have this many directional lights...
74
ERR_FAIL_COND(p_directional_light_id > 512);
75
DEV_ASSERT(p_directional_light_id >= 0);
76
77
// First make sure we have enough directional lights to hold this one.
78
if (p_directional_light_id >= (int32_t)data.directional_cull_planes.size()) {
79
data.directional_cull_planes.resize(p_directional_light_id + 1);
80
}
81
82
_prepare_light(*p_instance, p_directional_light_id);
83
}
84
85
bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id) {
86
if (!data.is_active()) {
87
return true;
88
}
89
90
LightSource lsource;
91
switch (RSG::light_storage->light_get_type(p_instance.base)) {
92
case RS::LIGHT_SPOT:
93
lsource.type = LightSource::ST_SPOTLIGHT;
94
lsource.angle = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SPOT_ANGLE);
95
lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
96
break;
97
case RS::LIGHT_OMNI:
98
lsource.type = LightSource::ST_OMNI;
99
lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
100
break;
101
case RS::LIGHT_DIRECTIONAL:
102
lsource.type = LightSource::ST_DIRECTIONAL;
103
// Could deal with a max directional shadow range here? NYI
104
// LIGHT_PARAM_SHADOW_MAX_DISTANCE
105
break;
106
}
107
108
lsource.pos = p_instance.transform.origin;
109
lsource.dir = -p_instance.transform.basis.get_column(2);
110
lsource.dir.normalize();
111
112
bool visible;
113
if (p_directional_light_id == -1) {
114
visible = _add_light_camera_planes(data.regular_cull_planes, lsource);
115
} else {
116
visible = _add_light_camera_planes(data.directional_cull_planes[p_directional_light_id], lsource);
117
}
118
119
if (data.light_culling_active) {
120
return visible;
121
}
122
return true;
123
}
124
125
bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id) {
126
if (!data.is_active() || !is_caster_culling_active()) {
127
return true;
128
}
129
130
ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true);
131
132
LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id];
133
134
Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]);
135
Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]);
136
AABB bb(mins, maxs - mins);
137
138
real_t r_min, r_max;
139
for (int p = 0; p < cull_planes.num_cull_planes; p++) {
140
bb.project_range_in_plane(cull_planes.cull_planes[p], r_min, r_max);
141
if (r_min > 0.0f) {
142
#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
143
cull_planes.rejected_count++;
144
#endif
145
146
return false;
147
}
148
}
149
150
return true;
151
}
152
153
void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {
154
if (!data.is_active() || !is_caster_culling_active()) {
155
return;
156
}
157
158
// If the light is out of range, no need to check anything, just return 0 casters.
159
// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).
160
if (data.out_of_range) {
161
return;
162
}
163
164
// Shorter local alias.
165
PagedArray<RendererSceneCull::Instance *> &list = r_instance_shadow_cull_result;
166
167
#ifdef LIGHT_CULLER_DEBUG_LOGGING
168
uint32_t count_before = r_instance_shadow_cull_result.size();
169
#endif
170
171
// Go through all the casters in the list (the list will hopefully shrink as we go).
172
for (int n = 0; n < (int)list.size(); n++) {
173
// World space aabb.
174
const AABB &bb = list[n]->transformed_aabb;
175
176
#ifdef LIGHT_CULLER_DEBUG_LOGGING
177
if (is_logging()) {
178
print_line("bb : " + String(bb));
179
}
180
#endif
181
182
real_t r_min, r_max;
183
bool show = true;
184
185
for (int p = 0; p < data.regular_cull_planes.num_cull_planes; p++) {
186
// As we only need r_min, could this be optimized?
187
bb.project_range_in_plane(data.regular_cull_planes.cull_planes[p], r_min, r_max);
188
189
#ifdef LIGHT_CULLER_DEBUG_LOGGING
190
if (is_logging()) {
191
print_line("\tplane " + itos(p) + " : " + String(data.regular_cull_planes.cull_planes[p]) + " r_min " + String(Variant(r_min)) + " r_max " + String(Variant(r_max)));
192
}
193
#endif
194
195
if (r_min > 0.0f) {
196
show = false;
197
break;
198
}
199
}
200
201
// Remove.
202
if (!show) {
203
list.remove_at_unordered(n);
204
205
// Repeat this element next iteration of the loop as it has been removed and replaced by the last.
206
n--;
207
208
#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
209
data.regular_rejected_count++;
210
#endif
211
}
212
}
213
214
#ifdef LIGHT_CULLER_DEBUG_LOGGING
215
uint32_t removed = r_instance_shadow_cull_result.size() - count_before;
216
if (removed) {
217
if (((data.debug_count) % 60) == 0) {
218
print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(count_before) + " after " + itos(r_instance_shadow_cull_result.size()));
219
}
220
}
221
#endif
222
}
223
224
void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) {
225
ERR_FAIL_COND(num_cull_planes >= MAX_CULL_PLANES);
226
cull_planes[num_cull_planes++] = p;
227
}
228
229
// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third
230
// points are derived differently.
231
bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {
232
uint32_t lookup = 0;
233
r_cull_planes.num_cull_planes = 0;
234
235
// Directional light, we will use dot against the light direction to determine back facing planes.
236
for (int n = 0; n < 6; n++) {
237
float dot = data.frustum_planes[n].normal.dot(p_light_source.dir);
238
if (dot > 0.0f) {
239
lookup |= 1 << n;
240
241
// Add backfacing camera frustum planes.
242
r_cull_planes.add_cull_plane(data.frustum_planes[n]);
243
}
244
}
245
246
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
247
248
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
249
// then we will add the camera frustum planes to clip the light volume .. there is no need to
250
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
251
252
// Should never happen with directional light?? This may be able to be removed.
253
if (lookup == 63) {
254
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]);
257
}
258
259
return true;
260
}
261
262
// Each edge forms a plane.
263
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
264
const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];
265
266
// each edge forms a plane
267
int n_edges = entry.size() - 1;
268
#else
269
uint8_t *entry = &data.LUT_entries[lookup][0];
270
int n_edges = data.LUT_entry_sizes[lookup] - 1;
271
#endif
272
273
for (int e = 0; e < n_edges; e++) {
274
int i0 = entry[e];
275
int i1 = entry[e + 1];
276
const Vector3 &pt0 = data.frustum_points[i0];
277
const Vector3 &pt1 = data.frustum_points[i1];
278
279
// Create a third point from the light direction.
280
Vector3 pt2 = pt0 - p_light_source.dir;
281
282
if (!_is_colinear_tri(pt0, pt1, pt2)) {
283
// Create plane from 3 points.
284
Plane p(pt0, pt1, pt2);
285
r_cull_planes.add_cull_plane(p);
286
}
287
}
288
289
// Last to 0 edge.
290
if (n_edges) {
291
int i0 = entry[n_edges]; // Last.
292
int i1 = entry[0]; // First.
293
294
const Vector3 &pt0 = data.frustum_points[i0];
295
const Vector3 &pt1 = data.frustum_points[i1];
296
297
// Create a third point from the light direction.
298
Vector3 pt2 = pt0 - p_light_source.dir;
299
300
if (!_is_colinear_tri(pt0, pt1, pt2)) {
301
// Create plane from 3 points.
302
Plane p(pt0, pt1, pt2);
303
r_cull_planes.add_cull_plane(p);
304
}
305
}
306
307
#ifdef LIGHT_CULLER_DEBUG_LOGGING
308
if (is_logging()) {
309
print_line("lcam.pos is " + String(p_light_source.pos));
310
}
311
#endif
312
313
return true;
314
}
315
316
bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {
317
if (!data.is_active()) {
318
return true;
319
}
320
321
// We should have called prepare_camera before this.
322
ERR_FAIL_COND_V(data.frustum_planes.size() != 6, true);
323
324
switch (p_light_source.type) {
325
case LightSource::ST_SPOTLIGHT:
326
case LightSource::ST_OMNI:
327
break;
328
case LightSource::ST_DIRECTIONAL:
329
return add_light_camera_planes_directional(r_cull_planes, p_light_source);
330
break;
331
default:
332
return false; // not yet supported
333
break;
334
}
335
336
// Start with 0 cull planes.
337
r_cull_planes.num_cull_planes = 0;
338
data.out_of_range = false;
339
uint32_t lookup = 0;
340
341
// Find which of the camera planes are facing away from the light.
342
// We can also test for the situation where the light max range means it cannot
343
// affect the camera frustum. This is absolutely worth doing because it is relatively
344
// cheap, and if the entire light can be culled this can vastly improve performance
345
// (much more than just culling casters).
346
347
// POINT LIGHT (spotlight, omni)
348
// Instead of using dot product to compare light direction to plane, we can simply
349
// find out which side of the plane the camera is on. By definition this marks the point at which the plane
350
// becomes invisible.
351
352
// OMNIS
353
if (p_light_source.type == LightSource::ST_OMNI) {
354
for (int n = 0; n < 6; n++) {
355
float dist = data.frustum_planes[n].distance_to(p_light_source.pos);
356
if (dist < 0.0f) {
357
lookup |= 1 << n;
358
359
// Add backfacing camera frustum planes.
360
r_cull_planes.add_cull_plane(data.frustum_planes[n]);
361
} else {
362
// Is the light out of range?
363
// This is one of the tests. If the point source is more than range distance from a frustum plane, it can't
364
// be seen.
365
if (dist >= p_light_source.range) {
366
// If the light is out of range, no need to do anything else, everything will be culled.
367
data.out_of_range = true;
368
return false;
369
}
370
}
371
}
372
} else {
373
// SPOTLIGHTs, more complex to cull.
374
Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);
375
376
// This is the radius of the cone at distance 1.
377
float radius_at_dist_one = Math::tan(Math::deg_to_rad(p_light_source.angle));
378
379
// The worst case radius of the cone at the end point can be calculated
380
// (the radius will scale linearly with length along the cone).
381
float end_cone_radius = radius_at_dist_one * p_light_source.range;
382
383
for (int n = 0; n < 6; n++) {
384
float dist = data.frustum_planes[n].distance_to(p_light_source.pos);
385
if (dist < 0.0f) {
386
// Either the plane is backfacing or we are inside the frustum.
387
lookup |= 1 << n;
388
389
// Add backfacing camera frustum planes.
390
r_cull_planes.add_cull_plane(data.frustum_planes[n]);
391
} else {
392
// The light is in front of the plane.
393
394
// Is the light out of range?
395
if (dist >= p_light_source.range) {
396
data.out_of_range = true;
397
return false;
398
}
399
400
// For a spotlight, we can use an extra test
401
// at this point the cone start is in front of the plane...
402
// If the cone end point is further than the maximum possible distance to the plane
403
// we can guarantee that the cone does not cross the plane, and hence the cone
404
// is outside the frustum.
405
float dist_end = data.frustum_planes[n].distance_to(pos_end);
406
407
if (dist_end >= end_cone_radius) {
408
data.out_of_range = true;
409
return false;
410
}
411
}
412
}
413
}
414
415
// The lookup should be within the LUT, logic should prevent this.
416
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
417
418
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
419
// then we will add the camera frustum planes to clip the light volume .. there is no need to
420
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
421
if (lookup == 63) {
422
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]);
425
}
426
427
return true;
428
}
429
430
// Each edge forms a plane.
431
uint8_t *entry = &data.LUT_entries[lookup][0];
432
int n_edges = data.LUT_entry_sizes[lookup] - 1;
433
434
const Vector3 &pt2 = p_light_source.pos;
435
436
for (int e = 0; e < n_edges; e++) {
437
int i0 = entry[e];
438
int i1 = entry[e + 1];
439
const Vector3 &pt0 = data.frustum_points[i0];
440
const Vector3 &pt1 = data.frustum_points[i1];
441
442
if (!_is_colinear_tri(pt0, pt1, pt2)) {
443
// Create plane from 3 points.
444
Plane p(pt0, pt1, pt2);
445
r_cull_planes.add_cull_plane(p);
446
}
447
}
448
449
// Last to 0 edge.
450
if (n_edges) {
451
int i0 = entry[n_edges]; // Last.
452
int i1 = entry[0]; // First.
453
454
const Vector3 &pt0 = data.frustum_points[i0];
455
const Vector3 &pt1 = data.frustum_points[i1];
456
457
if (!_is_colinear_tri(pt0, pt1, pt2)) {
458
// Create plane from 3 points.
459
Plane p(pt0, pt1, pt2);
460
r_cull_planes.add_cull_plane(p);
461
}
462
}
463
464
#ifdef LIGHT_CULLER_DEBUG_LOGGING
465
if (is_logging()) {
466
print_line("lsource.pos is " + String(p_light_source.pos));
467
}
468
#endif
469
470
return true;
471
}
472
473
bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {
474
data.debug_count++;
475
if (data.debug_count >= 120) {
476
data.debug_count = 0;
477
}
478
479
// For debug flash off and on.
480
#ifdef LIGHT_CULLER_DEBUG_FLASH
481
if (!Engine::get_singleton()->is_editor_hint()) {
482
int dc = Engine::get_singleton()->get_process_frames() / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;
483
bool bnew_active;
484
bnew_active = (dc % 2) == 0;
485
486
if (bnew_active != data.light_culling_active) {
487
data.light_culling_active = bnew_active;
488
print_line("switching light culler " + String(Variant(data.light_culling_active)));
489
}
490
}
491
#endif
492
493
if (!data.is_active()) {
494
return false;
495
}
496
497
// Get the camera frustum planes in world space.
498
data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
499
DEV_CHECK_ONCE(data.frustum_planes.size() == 6);
500
501
data.regular_cull_planes.num_cull_planes = 0;
502
503
#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
504
if (is_logging()) {
505
for (uint32_t n = 0; n < data.directional_cull_planes.size(); n++) {
506
print_line("LightCuller directional light " + itos(n) + " rejected " + itos(data.directional_cull_planes[n].rejected_count) + " instances.");
507
}
508
}
509
#endif
510
#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
511
if (data.regular_rejected_count) {
512
print_line("LightCuller regular lights rejected " + itos(data.regular_rejected_count) + " instances.");
513
}
514
data.regular_rejected_count = 0;
515
#endif
516
517
data.directional_cull_planes.resize(0);
518
519
#ifdef LIGHT_CULLER_DEBUG_LOGGING
520
if (is_logging()) {
521
for (int p = 0; p < 6; p++) {
522
print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));
523
}
524
}
525
#endif
526
527
// We want to calculate the frustum corners in a specific order.
528
const Projection::Planes intersections[8][3] = {
529
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
530
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
531
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
532
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
533
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
534
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
535
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
536
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
537
};
538
539
for (int i = 0; i < 8; i++) {
540
// 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]);
542
543
// What happens with a zero frustum? NYI - deal with this.
544
ERR_FAIL_COND_V(!res, false);
545
546
#ifdef LIGHT_CULLER_DEBUG_LOGGING
547
if (is_logging()) {
548
print_line("point " + itos(i) + " -> " + String(data.frustum_points[i]));
549
}
550
#endif
551
}
552
553
return true;
554
}
555
556
RenderingLightCuller::RenderingLightCuller() {
557
// Used to determine which frame to give debug output.
558
data.debug_count = -1;
559
560
// Uncomment below to switch off light culler in the editor.
561
// data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;
562
563
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
564
create_LUT();
565
#endif
566
}
567
568
/* clang-format off */
569
uint8_t RenderingLightCuller::Data::LUT_entry_sizes[LUT_SIZE] = {0, 4, 4, 0, 4, 6, 6, 8, 4, 6, 6, 8, 6, 6, 6, 6, 4, 6, 6, 8, 0, 8, 8, 0, 6, 6, 6, 6, 8, 6, 6, 4, 4, 6, 6, 8, 6, 6, 6, 6, 0, 8, 8, 0, 8, 6, 6, 4, 6, 6, 6, 6, 8, 6, 6, 4, 8, 6, 6, 4, 0, 4, 4, 0, };
570
571
// The lookup table used to determine which edges form the silhouette of the camera frustum,
572
// depending on the viewing angle (defined by which camera planes are backward facing).
573
uint8_t RenderingLightCuller::Data::LUT_entries[LUT_SIZE][8] = {
574
{0, 0, 0, 0, 0, 0, 0, 0, },
575
{7, 6, 4, 5, 0, 0, 0, 0, },
576
{1, 0, 2, 3, 0, 0, 0, 0, },
577
{0, 0, 0, 0, 0, 0, 0, 0, },
578
{1, 5, 4, 0, 0, 0, 0, 0, },
579
{1, 5, 7, 6, 4, 0, 0, 0, },
580
{4, 0, 2, 3, 1, 5, 0, 0, },
581
{5, 7, 6, 4, 0, 2, 3, 1, },
582
{0, 4, 6, 2, 0, 0, 0, 0, },
583
{0, 4, 5, 7, 6, 2, 0, 0, },
584
{6, 2, 3, 1, 0, 4, 0, 0, },
585
{2, 3, 1, 0, 4, 5, 7, 6, },
586
{0, 1, 5, 4, 6, 2, 0, 0, },
587
{0, 1, 5, 7, 6, 2, 0, 0, },
588
{6, 2, 3, 1, 5, 4, 0, 0, },
589
{2, 3, 1, 5, 7, 6, 0, 0, },
590
{2, 6, 7, 3, 0, 0, 0, 0, },
591
{2, 6, 4, 5, 7, 3, 0, 0, },
592
{7, 3, 1, 0, 2, 6, 0, 0, },
593
{3, 1, 0, 2, 6, 4, 5, 7, },
594
{0, 0, 0, 0, 0, 0, 0, 0, },
595
{2, 6, 4, 0, 1, 5, 7, 3, },
596
{7, 3, 1, 5, 4, 0, 2, 6, },
597
{0, 0, 0, 0, 0, 0, 0, 0, },
598
{2, 0, 4, 6, 7, 3, 0, 0, },
599
{2, 0, 4, 5, 7, 3, 0, 0, },
600
{7, 3, 1, 0, 4, 6, 0, 0, },
601
{3, 1, 0, 4, 5, 7, 0, 0, },
602
{2, 0, 1, 5, 4, 6, 7, 3, },
603
{2, 0, 1, 5, 7, 3, 0, 0, },
604
{7, 3, 1, 5, 4, 6, 0, 0, },
605
{3, 1, 5, 7, 0, 0, 0, 0, },
606
{3, 7, 5, 1, 0, 0, 0, 0, },
607
{3, 7, 6, 4, 5, 1, 0, 0, },
608
{5, 1, 0, 2, 3, 7, 0, 0, },
609
{7, 6, 4, 5, 1, 0, 2, 3, },
610
{3, 7, 5, 4, 0, 1, 0, 0, },
611
{3, 7, 6, 4, 0, 1, 0, 0, },
612
{5, 4, 0, 2, 3, 7, 0, 0, },
613
{7, 6, 4, 0, 2, 3, 0, 0, },
614
{0, 0, 0, 0, 0, 0, 0, 0, },
615
{3, 7, 6, 2, 0, 4, 5, 1, },
616
{5, 1, 0, 4, 6, 2, 3, 7, },
617
{0, 0, 0, 0, 0, 0, 0, 0, },
618
{3, 7, 5, 4, 6, 2, 0, 1, },
619
{3, 7, 6, 2, 0, 1, 0, 0, },
620
{5, 4, 6, 2, 3, 7, 0, 0, },
621
{7, 6, 2, 3, 0, 0, 0, 0, },
622
{3, 2, 6, 7, 5, 1, 0, 0, },
623
{3, 2, 6, 4, 5, 1, 0, 0, },
624
{5, 1, 0, 2, 6, 7, 0, 0, },
625
{1, 0, 2, 6, 4, 5, 0, 0, },
626
{3, 2, 6, 7, 5, 4, 0, 1, },
627
{3, 2, 6, 4, 0, 1, 0, 0, },
628
{5, 4, 0, 2, 6, 7, 0, 0, },
629
{6, 4, 0, 2, 0, 0, 0, 0, },
630
{3, 2, 0, 4, 6, 7, 5, 1, },
631
{3, 2, 0, 4, 5, 1, 0, 0, },
632
{5, 1, 0, 4, 6, 7, 0, 0, },
633
{1, 0, 4, 5, 0, 0, 0, 0, },
634
{0, 0, 0, 0, 0, 0, 0, 0, },
635
{3, 2, 0, 1, 0, 0, 0, 0, },
636
{5, 4, 6, 7, 0, 0, 0, 0, },
637
{0, 0, 0, 0, 0, 0, 0, 0, },
638
};
639
640
/* clang-format on */
641
642
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
643
644
// See e.g. http://lspiroengine.com/?p=153 for reference.
645
// Principles are the same, but differences to the article:
646
// * Order of planes / points is different in Godot.
647
// * We use a lookup table at runtime.
648
void RenderingLightCuller::create_LUT() {
649
// Each pair of planes that are opposite can have an edge.
650
for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {
651
// For each neighbor of the plane.
652
PlaneOrder neighs[4];
653
get_neighbouring_planes((PlaneOrder)plane_0, neighs);
654
655
for (int n = 0; n < 4; n++) {
656
int plane_1 = neighs[n];
657
658
// If these are opposite we need to add the 2 points they share.
659
PointOrder pts[2];
660
get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);
661
662
add_LUT(plane_0, plane_1, pts);
663
}
664
}
665
666
for (uint32_t n = 0; n < LUT_SIZE; n++) {
667
compact_LUT_entry(n);
668
}
669
670
debug_print_LUT();
671
debug_print_LUT_as_table();
672
}
673
674
// we can pre-create the entire LUT and store it hard coded as a static inside the executable!
675
// it is only small in size, 64 entries with max 8 bytes per entry
676
void RenderingLightCuller::debug_print_LUT_as_table() {
677
print_line("\nLIGHT VOLUME TABLE BEGIN\n");
678
679
print_line("Copy this to LUT_entry_sizes:\n");
680
String sz = "{";
681
for (int n = 0; n < LUT_SIZE; n++) {
682
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
683
684
sz += itos(entry.size()) + ", ";
685
}
686
sz += "}";
687
print_line(sz);
688
print_line("\nCopy this to LUT_entries:\n");
689
690
for (int n = 0; n < LUT_SIZE; n++) {
691
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
692
693
String sz = "{";
694
695
// First is the number of points in the entry.
696
int s = entry.size();
697
698
for (int p = 0; p < 8; p++) {
699
if (p < s) {
700
sz += itos(entry[p]);
701
} else {
702
sz += "0"; // just a spacer
703
}
704
705
sz += ", ";
706
}
707
708
sz += "},";
709
print_line(sz);
710
}
711
712
print_line("\nLIGHT VOLUME TABLE END\n");
713
}
714
715
void RenderingLightCuller::debug_print_LUT() {
716
for (int n = 0; n < LUT_SIZE; n++) {
717
String sz;
718
sz = "LUT" + itos(n) + ":\t";
719
720
sz += Data::plane_bitfield_to_string(n);
721
print_line(sz);
722
723
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
724
725
sz = "\t" + string_LUT_entry(entry);
726
727
print_line(sz);
728
}
729
}
730
731
String RenderingLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {
732
String string;
733
734
for (uint32_t n = 0; n < p_entry.size(); n++) {
735
uint8_t val = p_entry[n];
736
DEV_ASSERT(val < 8);
737
const char *sz_point = Data::string_points[val];
738
string += sz_point;
739
string += ", ";
740
}
741
742
return string;
743
}
744
745
String RenderingLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {
746
String string;
747
748
for (uint32_t i = 0; i < p_entry.size(); i++) {
749
int pt_order = p_entry[i];
750
if (p_pair && ((i % 2) == 0)) {
751
string += itos(pt_order) + "-";
752
} else {
753
string += itos(pt_order) + ", ";
754
}
755
}
756
757
return string;
758
}
759
760
void RenderingLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {
761
// Note that some entries to the LUT will be "impossible" situations,
762
// because it contains all combinations of plane flips.
763
uint32_t bit0 = 1 << p_plane_0;
764
uint32_t bit1 = 1 << p_plane_1;
765
766
// All entries of the LUT that have plane 0 set and plane 1 not set.
767
for (uint32_t n = 0; n < 64; n++) {
768
// If bit0 not set...
769
if (!(n & bit0)) {
770
continue;
771
}
772
773
// If bit1 set...
774
if (n & bit1) {
775
continue;
776
}
777
778
// Meets criteria.
779
add_LUT_entry(n, p_pts);
780
}
781
}
782
783
void RenderingLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {
784
DEV_ASSERT(p_entry_id < LUT_SIZE);
785
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
786
787
entry.push_back(p_pts[0]);
788
entry.push_back(p_pts[1]);
789
}
790
791
void RenderingLightCuller::compact_LUT_entry(uint32_t p_entry_id) {
792
DEV_ASSERT(p_entry_id < LUT_SIZE);
793
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
794
795
int num_pairs = entry.size() / 2;
796
797
if (num_pairs == 0) {
798
return;
799
}
800
801
LocalVector<uint8_t> temp;
802
803
String string;
804
string = "Compact LUT" + itos(p_entry_id) + ":\t";
805
string += debug_string_LUT_entry(entry, true);
806
print_line(string);
807
808
// Add first pair.
809
temp.push_back(entry[0]);
810
temp.push_back(entry[1]);
811
unsigned int BFpairs = 1;
812
813
string = debug_string_LUT_entry(temp) + " -> ";
814
print_line(string);
815
816
// Attempt to add a pair each time.
817
for (int done = 1; done < num_pairs; done++) {
818
string = "done " + itos(done) + ": ";
819
// Find a free pair.
820
for (int p = 1; p < num_pairs; p++) {
821
unsigned int bit = 1 << p;
822
// Is it done already?
823
if (BFpairs & bit) {
824
continue;
825
}
826
827
// There must be at least 1 free pair.
828
// Attempt to add.
829
int a = entry[p * 2];
830
int b = entry[(p * 2) + 1];
831
832
string += "[" + itos(a) + "-" + itos(b) + "], ";
833
834
int found_a = temp.find(a);
835
int found_b = temp.find(b);
836
837
// Special case, if they are both already in the list, no need to add
838
// as this is a link from the tail to the head of the list.
839
if ((found_a != -1) && (found_b != -1)) {
840
string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";
841
BFpairs |= bit;
842
goto found;
843
}
844
845
// Find a.
846
if (found_a != -1) {
847
string += "foundA " + itos(found_a) + " ";
848
temp.insert(found_a + 1, b);
849
BFpairs |= bit;
850
goto found;
851
}
852
853
// Find b.
854
if (found_b != -1) {
855
string += "foundB " + itos(found_b) + " ";
856
temp.insert(found_b, a);
857
BFpairs |= bit;
858
goto found;
859
}
860
861
} // Check each pair for adding.
862
863
// If we got here before finding a link, the whole set of planes is INVALID
864
// e.g. far and near plane only, does not create continuous sillouhette of edges.
865
print_line("\tINVALID");
866
entry.clear();
867
return;
868
869
found:;
870
print_line(string);
871
string = "\ttemp now : " + debug_string_LUT_entry(temp);
872
print_line(string);
873
}
874
875
// temp should now be the sorted entry .. delete the old one and replace by temp.
876
entry.clear();
877
entry = temp;
878
}
879
880
void RenderingLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {
881
// Table of neighboring planes to each.
882
static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {
883
{ // LSM_FP_NEAR
884
PLANE_LEFT,
885
PLANE_RIGHT,
886
PLANE_TOP,
887
PLANE_BOTTOM },
888
{ // LSM_FP_FAR
889
PLANE_LEFT,
890
PLANE_RIGHT,
891
PLANE_TOP,
892
PLANE_BOTTOM },
893
{ // LSM_FP_LEFT
894
PLANE_TOP,
895
PLANE_BOTTOM,
896
PLANE_NEAR,
897
PLANE_FAR },
898
{ // LSM_FP_TOP
899
PLANE_LEFT,
900
PLANE_RIGHT,
901
PLANE_NEAR,
902
PLANE_FAR },
903
{ // LSM_FP_RIGHT
904
PLANE_TOP,
905
PLANE_BOTTOM,
906
PLANE_NEAR,
907
PLANE_FAR },
908
{ // LSM_FP_BOTTOM
909
PLANE_LEFT,
910
PLANE_RIGHT,
911
PLANE_NEAR,
912
PLANE_FAR },
913
};
914
915
for (int n = 0; n < 4; n++) {
916
r_neigh_planes[n] = neigh_table[p_plane][n];
917
}
918
}
919
920
// Given two planes, returns the two points shared by those planes. The points are always
921
// returned in counter-clockwise order, assuming the first input plane is facing towards
922
// the viewer.
923
924
// param p_plane_a The plane facing towards the viewer.
925
// param p_plane_b A plane neighboring p_plane_a.
926
// param r_points An array of exactly two elements to be filled with the indices of the points
927
// on return.
928
929
void RenderingLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {
930
static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {
931
{
932
// LSM_FP_NEAR
933
{
934
// LSM_FP_NEAR
935
PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP, // Invalid combination.
936
},
937
{
938
// LSM_FP_FAR
939
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
940
},
941
{
942
// LSM_FP_LEFT
943
PT_NEAR_LEFT_TOP,
944
PT_NEAR_LEFT_BOTTOM,
945
},
946
{
947
// LSM_FP_TOP
948
PT_NEAR_RIGHT_TOP,
949
PT_NEAR_LEFT_TOP,
950
},
951
{
952
// LSM_FP_RIGHT
953
PT_NEAR_RIGHT_BOTTOM,
954
PT_NEAR_RIGHT_TOP,
955
},
956
{
957
// LSM_FP_BOTTOM
958
PT_NEAR_LEFT_BOTTOM,
959
PT_NEAR_RIGHT_BOTTOM,
960
},
961
},
962
963
{
964
// LSM_FP_FAR
965
{
966
// LSM_FP_NEAR
967
PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP, // Invalid combination.
968
},
969
{
970
// LSM_FP_FAR
971
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
972
},
973
{
974
// LSM_FP_LEFT
975
PT_FAR_LEFT_BOTTOM,
976
PT_FAR_LEFT_TOP,
977
},
978
{
979
// LSM_FP_TOP
980
PT_FAR_LEFT_TOP,
981
PT_FAR_RIGHT_TOP,
982
},
983
{
984
// LSM_FP_RIGHT
985
PT_FAR_RIGHT_TOP,
986
PT_FAR_RIGHT_BOTTOM,
987
},
988
{
989
// LSM_FP_BOTTOM
990
PT_FAR_RIGHT_BOTTOM,
991
PT_FAR_LEFT_BOTTOM,
992
},
993
},
994
995
{
996
// LSM_FP_LEFT
997
{
998
// LSM_FP_NEAR
999
PT_NEAR_LEFT_BOTTOM,
1000
PT_NEAR_LEFT_TOP,
1001
},
1002
{
1003
// LSM_FP_FAR
1004
PT_FAR_LEFT_TOP,
1005
PT_FAR_LEFT_BOTTOM,
1006
},
1007
{
1008
// LSM_FP_LEFT
1009
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
1010
},
1011
{
1012
// LSM_FP_TOP
1013
PT_NEAR_LEFT_TOP,
1014
PT_FAR_LEFT_TOP,
1015
},
1016
{
1017
// LSM_FP_RIGHT
1018
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
1019
},
1020
{
1021
// LSM_FP_BOTTOM
1022
PT_FAR_LEFT_BOTTOM,
1023
PT_NEAR_LEFT_BOTTOM,
1024
},
1025
},
1026
1027
{
1028
// LSM_FP_TOP
1029
{
1030
// LSM_FP_NEAR
1031
PT_NEAR_LEFT_TOP,
1032
PT_NEAR_RIGHT_TOP,
1033
},
1034
{
1035
// LSM_FP_FAR
1036
PT_FAR_RIGHT_TOP,
1037
PT_FAR_LEFT_TOP,
1038
},
1039
{
1040
// LSM_FP_LEFT
1041
PT_FAR_LEFT_TOP,
1042
PT_NEAR_LEFT_TOP,
1043
},
1044
{
1045
// LSM_FP_TOP
1046
PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
1047
},
1048
{
1049
// LSM_FP_RIGHT
1050
PT_NEAR_RIGHT_TOP,
1051
PT_FAR_RIGHT_TOP,
1052
},
1053
{
1054
// LSM_FP_BOTTOM
1055
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.
1056
},
1057
},
1058
1059
{
1060
// LSM_FP_RIGHT
1061
{
1062
// LSM_FP_NEAR
1063
PT_NEAR_RIGHT_TOP,
1064
PT_NEAR_RIGHT_BOTTOM,
1065
},
1066
{
1067
// LSM_FP_FAR
1068
PT_FAR_RIGHT_BOTTOM,
1069
PT_FAR_RIGHT_TOP,
1070
},
1071
{
1072
// LSM_FP_LEFT
1073
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.
1074
},
1075
{
1076
// LSM_FP_TOP
1077
PT_FAR_RIGHT_TOP,
1078
PT_NEAR_RIGHT_TOP,
1079
},
1080
{
1081
// LSM_FP_RIGHT
1082
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.
1083
},
1084
{
1085
// LSM_FP_BOTTOM
1086
PT_NEAR_RIGHT_BOTTOM,
1087
PT_FAR_RIGHT_BOTTOM,
1088
},
1089
},
1090
1091
// ==
1092
1093
// P_NEAR,
1094
// P_FAR,
1095
// P_LEFT,
1096
// P_TOP,
1097
// P_RIGHT,
1098
// P_BOTTOM,
1099
1100
{
1101
// LSM_FP_BOTTOM
1102
{
1103
// LSM_FP_NEAR
1104
PT_NEAR_RIGHT_BOTTOM,
1105
PT_NEAR_LEFT_BOTTOM,
1106
},
1107
{
1108
// LSM_FP_FAR
1109
PT_FAR_LEFT_BOTTOM,
1110
PT_FAR_RIGHT_BOTTOM,
1111
},
1112
{
1113
// LSM_FP_LEFT
1114
PT_NEAR_LEFT_BOTTOM,
1115
PT_FAR_LEFT_BOTTOM,
1116
},
1117
{
1118
// LSM_FP_TOP
1119
PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
1120
},
1121
{
1122
// LSM_FP_RIGHT
1123
PT_FAR_RIGHT_BOTTOM,
1124
PT_NEAR_RIGHT_BOTTOM,
1125
},
1126
{
1127
// LSM_FP_BOTTOM
1128
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.
1129
},
1130
},
1131
1132
// ==
1133
1134
};
1135
r_points[0] = fp_table[p_plane_a][p_plane_b][0];
1136
r_points[1] = fp_table[p_plane_a][p_plane_b][1];
1137
}
1138
1139
#endif
1140
1141