mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-28 14:47:13 +01:00
Relintai
5d2a594843
Existing shadow caster culling using the BVH takes no account of the camera. This PR adds the highly encapsulated class VisualServerLightCuller which can cut down the casters in the shadow volume to only those which can cast shadows on the camera frustum.
This is used to:
* More accurately defer dirty updates to shadows when the shadow volume does not intersect the camera frustum.
* Tighter cull shadow casters to the view frustum.
Lights dirty state is now automatically managed:
* Continuous (tighter caster culling)
* Static (all casters are rendered)
- lawnjelly
8ca631a466
1054 lines
28 KiB
C++
1054 lines
28 KiB
C++
/*************************************************************************/
|
|
/* rendering_server_light_culler.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* PANDEMONIUM ENGINE */
|
|
/* https://github.com/Relintai/pandemonium_engine */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2022-present Péter Magyar. */
|
|
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "rendering_server_light_culler.h"
|
|
#include "core/math/plane.h"
|
|
#include "core/math/projection.h"
|
|
#include "rendering_server_globals.h"
|
|
#include "rendering_server_scene.h"
|
|
#include "scene/3d/camera.h"
|
|
|
|
#ifdef RENDERING_SERVER_LIGHT_CULLER_DEBUG_STRINGS
|
|
const char *RenderingServerLightCuller::Data::string_planes[] = {
|
|
"NEAR",
|
|
"FAR",
|
|
"LEFT",
|
|
"TOP",
|
|
"RIGHT",
|
|
"BOTTOM",
|
|
};
|
|
const char *RenderingServerLightCuller::Data::string_points[] = {
|
|
"FAR_LEFT_TOP",
|
|
"FAR_LEFT_BOTTOM",
|
|
"FAR_RIGHT_TOP",
|
|
"FAR_RIGHT_BOTTOM",
|
|
"NEAR_LEFT_TOP",
|
|
"NEAR_LEFT_BOTTOM",
|
|
"NEAR_RIGHT_TOP",
|
|
"NEAR_RIGHT_BOTTOM",
|
|
};
|
|
|
|
String RenderingServerLightCuller::Data::plane_bitfield_to_string(unsigned int BF) {
|
|
String sz;
|
|
|
|
for (int n = 0; n < 6; n++) {
|
|
unsigned int bit = 1 << n;
|
|
if (BF & bit) {
|
|
sz += String(string_planes[n]) + ", ";
|
|
}
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
#endif
|
|
|
|
bool RenderingServerLightCuller::prepare_light(const RenderingServerScene::Instance &p_instance) {
|
|
if (!data.is_active()) {
|
|
return true;
|
|
}
|
|
|
|
LightSource lsource;
|
|
switch (RSG::storage->light_get_type(p_instance.base)) {
|
|
case RS::LIGHT_SPOT:
|
|
lsource.type = LightSource::ST_SPOTLIGHT;
|
|
lsource.angle = RSG::storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SPOT_ANGLE);
|
|
lsource.range = RSG::storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
|
|
break;
|
|
case RS::LIGHT_OMNI:
|
|
lsource.type = LightSource::ST_OMNI;
|
|
lsource.range = RSG::storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
|
|
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
|
|
break;
|
|
}
|
|
|
|
lsource.pos = p_instance.transform.origin;
|
|
lsource.dir = -p_instance.transform.basis.get_axis(2);
|
|
lsource.dir.normalize();
|
|
|
|
bool visible = _add_light_camera_planes(lsource);
|
|
|
|
if (data.light_culling_active) {
|
|
return visible;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int RenderingServerLightCuller::cull(int p_count, RenderingServerScene::Instance **p_result_array) {
|
|
if (!data.is_active() || !is_caster_culling_active()) {
|
|
return p_count;
|
|
}
|
|
|
|
// If the light is out of range, no need to check anything, just return 0 casters.
|
|
// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).
|
|
if (data.out_of_range) {
|
|
return 0;
|
|
}
|
|
|
|
int new_count = p_count;
|
|
|
|
// Go through all the casters in the list (the list will hopefully shrink as we go).
|
|
for (int n = 0; n < new_count; n++) {
|
|
// World space aabb.
|
|
const AABB &bb = p_result_array[n]->transformed_aabb;
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
if (is_logging()) {
|
|
print_line("bb : " + String(bb));
|
|
}
|
|
#endif
|
|
|
|
float r_min, r_max;
|
|
bool show = true;
|
|
|
|
for (int p = 0; p < data.num_cull_planes; p++) {
|
|
// As we only need r_min, could this be optimized?
|
|
bb.project_range_in_plane(data.cull_planes[p], r_min, r_max);
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
if (is_logging()) {
|
|
print_line("\tplane " + itos(p) + " : " + String(data.cull_planes[p]) + " r_min " + String(Variant(r_min)) + " r_max " + String(Variant(r_max)));
|
|
}
|
|
#endif
|
|
|
|
if (r_min > 0.0f) {
|
|
show = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove.
|
|
if (!show) {
|
|
// Quick unsorted remove - swap last element and reduce count.
|
|
p_result_array[n] = p_result_array[new_count - 1];
|
|
new_count--;
|
|
|
|
// Repeat this element next iteration of the loop as it has been removed and replaced by the last.
|
|
n--;
|
|
}
|
|
}
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
int removed = p_count - new_count;
|
|
if (removed) {
|
|
if (((data.debug_count) % 60) == 0) {
|
|
print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(p_count) + " after " + itos(new_count));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return new_count;
|
|
}
|
|
|
|
void RenderingServerLightCuller::add_cull_plane(const Plane &p) {
|
|
ERR_FAIL_COND(data.num_cull_planes >= MAX_CULL_PLANES);
|
|
data.cull_planes[data.num_cull_planes++] = p;
|
|
}
|
|
|
|
// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third
|
|
// points are derived differently.
|
|
bool RenderingServerLightCuller::add_light_camera_planes_directional(const LightSource &p_light_source) {
|
|
uint32_t lookup = 0;
|
|
|
|
// 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);
|
|
if (dot > 0.0f) {
|
|
lookup |= 1 << n;
|
|
|
|
// Add backfacing camera frustum planes.
|
|
add_cull_plane(data.frustum_planes[n]);
|
|
}
|
|
}
|
|
|
|
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
|
|
|
|
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
|
|
// then we will add the camera frustum planes to clip the light volume .. there is no need to
|
|
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
|
|
|
|
// Should never happen with directional light?? This may be able to be removed.
|
|
if (lookup == 63) {
|
|
data.num_cull_planes = 0;
|
|
for (int n = 0; n < data.frustum_planes.size(); n++) {
|
|
add_cull_plane(data.frustum_planes[n]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Each edge forms a plane.
|
|
#ifdef RENDERING_SERVER_LIGHT_CULLER_CALCULATE_LUT
|
|
const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];
|
|
|
|
// each edge forms a plane
|
|
int n_edges = entry.size() - 1;
|
|
#else
|
|
uint8_t *entry = &data.LUT_entries[lookup][0];
|
|
int n_edges = data.LUT_entry_sizes[lookup] - 1;
|
|
#endif
|
|
|
|
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];
|
|
|
|
// Create a third point from the light direction.
|
|
Vector3 pt2 = pt0 - p_light_source.dir;
|
|
|
|
// Create plane from 3 points.
|
|
Plane p(pt0, pt1, pt2);
|
|
add_cull_plane(p);
|
|
}
|
|
|
|
// Last to 0 edge.
|
|
if (n_edges) {
|
|
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];
|
|
|
|
// Create a third point from the light direction.
|
|
Vector3 pt2 = pt0 - p_light_source.dir;
|
|
|
|
// Create plane from 3 points.
|
|
Plane p(pt0, pt1, pt2);
|
|
add_cull_plane(p);
|
|
}
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
if (is_logging()) {
|
|
print_line("lcam.pos is " + String(p_light_source.pos));
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RenderingServerLightCuller::_add_light_camera_planes(const LightSource &p_light_source) {
|
|
if (!data.is_active()) {
|
|
return true;
|
|
}
|
|
|
|
// We should have called prepare_camera before this.
|
|
ERR_FAIL_COND_V(data.frustum_planes.size() != 6, true);
|
|
|
|
// Start with 0 cull planes.
|
|
data.num_cull_planes = 0;
|
|
data.out_of_range = false;
|
|
|
|
switch (p_light_source.type) {
|
|
case LightSource::ST_SPOTLIGHT:
|
|
case LightSource::ST_OMNI:
|
|
break;
|
|
case LightSource::ST_DIRECTIONAL:
|
|
return add_light_camera_planes_directional(p_light_source);
|
|
break;
|
|
default:
|
|
return false; // not yet supported
|
|
break;
|
|
}
|
|
|
|
uint32_t lookup = 0;
|
|
|
|
// Find which of the camera planes are facing away from the light.
|
|
// We can also test for the situation where the light max range means it cannot
|
|
// affect the camera frustum. This is absolutely worth doing because it is relatively
|
|
// cheap, and if the entire light can be culled this can vastly improve performance
|
|
// (much more than just culling casters).
|
|
|
|
// POINT LIGHT (spotlight, omni)
|
|
// Instead of using dot product to compare light direction to plane, we can simply
|
|
// find out which side of the plane the camera is on. By definition this marks the point at which the plane
|
|
// becomes invisible.
|
|
|
|
// 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);
|
|
if (dist < 0.0f) {
|
|
lookup |= 1 << n;
|
|
|
|
// Add backfacing camera frustum planes.
|
|
add_cull_plane(data.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
|
|
// be seen.
|
|
if (dist >= p_light_source.range) {
|
|
// If the light is out of range, no need to do anything else, everything will be culled.
|
|
data.out_of_range = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// SPOTLIGHTs, more complex to cull.
|
|
Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);
|
|
|
|
// This is the radius of the cone at distance 1.
|
|
float radius_at_dist_one = Math::tan(Math::deg2rad(p_light_source.angle));
|
|
|
|
// The worst case radius of the cone at the end point can be calculated
|
|
// (the radius will scale linearly with length along the cone).
|
|
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);
|
|
if (dist < 0.0f) {
|
|
// Either the plane is backfacing or we are inside the frustum.
|
|
lookup |= 1 << n;
|
|
|
|
// Add backfacing camera frustum planes.
|
|
add_cull_plane(data.frustum_planes[n]);
|
|
} else {
|
|
// The light is in front of the plane.
|
|
|
|
// Is the light out of range?
|
|
if (dist >= p_light_source.range) {
|
|
data.out_of_range = true;
|
|
return false;
|
|
}
|
|
|
|
// For a spotlight, we can use an extra test
|
|
// at this point the cone start is in front of the plane...
|
|
// 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);
|
|
|
|
if (dist_end >= end_cone_radius) {
|
|
data.out_of_range = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The lookup should be within the LUT, logic should prevent this.
|
|
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
|
|
|
|
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
|
|
// then we will add the camera frustum planes to clip the light volume .. there is no need to
|
|
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
|
|
if (lookup == 63) {
|
|
data.num_cull_planes = 0;
|
|
for (int n = 0; n < data.frustum_planes.size(); n++) {
|
|
add_cull_plane(data.frustum_planes[n]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Each edge forms a plane.
|
|
uint8_t *entry = &data.LUT_entries[lookup][0];
|
|
int n_edges = data.LUT_entry_sizes[lookup] - 1;
|
|
|
|
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];
|
|
|
|
// Create plane from 3 points.
|
|
Plane p(pt0, pt1, p_light_source.pos);
|
|
add_cull_plane(p);
|
|
}
|
|
|
|
// Last to 0 edge.
|
|
if (n_edges) {
|
|
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];
|
|
|
|
// Create plane from 3 points.
|
|
Plane p(pt0, pt1, p_light_source.pos);
|
|
add_cull_plane(p);
|
|
}
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
if (is_logging()) {
|
|
print_line("lsource.pos is " + String(p_light_source.pos));
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RenderingServerLightCuller::prepare_camera(const Transform &p_cam_transform, const Projection &p_cam_matrix) {
|
|
data.debug_count++;
|
|
|
|
// For debug flash off and on.
|
|
#ifdef LIGHT_CULLER_DEBUG_FLASH
|
|
if (!Engine::get_singleton()->is_editor_hint()) {
|
|
int dc = data.debug_count / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;
|
|
bool bnew_active;
|
|
bnew_active = (dc % 2) == 0;
|
|
|
|
if (bnew_active != data.active) {
|
|
data.active = bnew_active;
|
|
print_line("switching light culler " + String(Variant(data.active)));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!data.is_active()) {
|
|
return false;
|
|
}
|
|
|
|
// Get the camera frustum planes in world space.
|
|
data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
|
|
|
|
data.num_cull_planes = 0;
|
|
|
|
#ifdef LIGHT_CULLER_DEBUG_LOGGING
|
|
if (is_logging()) {
|
|
for (int p = 0; p < 6; p++) {
|
|
print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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 },
|
|
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
|
|
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
|
|
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
|
|
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
|
|
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
|
|
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
|
|
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
|
|
};
|
|
|
|
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]);
|
|
|
|
// 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]));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RenderingServerLightCuller::RenderingServerLightCuller() {
|
|
// Used to determine which frame to give debug output.
|
|
data.debug_count = -1;
|
|
|
|
// bactive is switching on and off the light culler
|
|
data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;
|
|
|
|
#ifdef RENDERING_SERVER_LIGHT_CULLER_CALCULATE_LUT
|
|
create_LUT();
|
|
#endif
|
|
}
|
|
|
|
/* clang-format off */
|
|
uint8_t RenderingServerLightCuller::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, };
|
|
|
|
// The lookup table used to determine which edges form the silhouette of the camera frustum,
|
|
// depending on the viewing angle (defined by which camera planes are backward facing).
|
|
uint8_t RenderingServerLightCuller::Data::LUT_entries[LUT_SIZE][8] = {
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{7, 6, 4, 5, 0, 0, 0, 0, },
|
|
{1, 0, 2, 3, 0, 0, 0, 0, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{1, 5, 4, 0, 0, 0, 0, 0, },
|
|
{1, 5, 7, 6, 4, 0, 0, 0, },
|
|
{4, 0, 2, 3, 1, 5, 0, 0, },
|
|
{5, 7, 6, 4, 0, 2, 3, 1, },
|
|
{0, 4, 6, 2, 0, 0, 0, 0, },
|
|
{0, 4, 5, 7, 6, 2, 0, 0, },
|
|
{6, 2, 3, 1, 0, 4, 0, 0, },
|
|
{2, 3, 1, 0, 4, 5, 7, 6, },
|
|
{0, 1, 5, 4, 6, 2, 0, 0, },
|
|
{0, 1, 5, 7, 6, 2, 0, 0, },
|
|
{6, 2, 3, 1, 5, 4, 0, 0, },
|
|
{2, 3, 1, 5, 7, 6, 0, 0, },
|
|
{2, 6, 7, 3, 0, 0, 0, 0, },
|
|
{2, 6, 4, 5, 7, 3, 0, 0, },
|
|
{7, 3, 1, 0, 2, 6, 0, 0, },
|
|
{3, 1, 0, 2, 6, 4, 5, 7, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{2, 6, 4, 0, 1, 5, 7, 3, },
|
|
{7, 3, 1, 5, 4, 0, 2, 6, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{2, 0, 4, 6, 7, 3, 0, 0, },
|
|
{2, 0, 4, 5, 7, 3, 0, 0, },
|
|
{7, 3, 1, 0, 4, 6, 0, 0, },
|
|
{3, 1, 0, 4, 5, 7, 0, 0, },
|
|
{2, 0, 1, 5, 4, 6, 7, 3, },
|
|
{2, 0, 1, 5, 7, 3, 0, 0, },
|
|
{7, 3, 1, 5, 4, 6, 0, 0, },
|
|
{3, 1, 5, 7, 0, 0, 0, 0, },
|
|
{3, 7, 5, 1, 0, 0, 0, 0, },
|
|
{3, 7, 6, 4, 5, 1, 0, 0, },
|
|
{5, 1, 0, 2, 3, 7, 0, 0, },
|
|
{7, 6, 4, 5, 1, 0, 2, 3, },
|
|
{3, 7, 5, 4, 0, 1, 0, 0, },
|
|
{3, 7, 6, 4, 0, 1, 0, 0, },
|
|
{5, 4, 0, 2, 3, 7, 0, 0, },
|
|
{7, 6, 4, 0, 2, 3, 0, 0, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{3, 7, 6, 2, 0, 4, 5, 1, },
|
|
{5, 1, 0, 4, 6, 2, 3, 7, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{3, 7, 5, 4, 6, 2, 0, 1, },
|
|
{3, 7, 6, 2, 0, 1, 0, 0, },
|
|
{5, 4, 6, 2, 3, 7, 0, 0, },
|
|
{7, 6, 2, 3, 0, 0, 0, 0, },
|
|
{3, 2, 6, 7, 5, 1, 0, 0, },
|
|
{3, 2, 6, 4, 5, 1, 0, 0, },
|
|
{5, 1, 0, 2, 6, 7, 0, 0, },
|
|
{1, 0, 2, 6, 4, 5, 0, 0, },
|
|
{3, 2, 6, 7, 5, 4, 0, 1, },
|
|
{3, 2, 6, 4, 0, 1, 0, 0, },
|
|
{5, 4, 0, 2, 6, 7, 0, 0, },
|
|
{6, 4, 0, 2, 0, 0, 0, 0, },
|
|
{3, 2, 0, 4, 6, 7, 5, 1, },
|
|
{3, 2, 0, 4, 5, 1, 0, 0, },
|
|
{5, 1, 0, 4, 6, 7, 0, 0, },
|
|
{1, 0, 4, 5, 0, 0, 0, 0, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
{3, 2, 0, 1, 0, 0, 0, 0, },
|
|
{5, 4, 6, 7, 0, 0, 0, 0, },
|
|
{0, 0, 0, 0, 0, 0, 0, 0, },
|
|
};
|
|
|
|
/* clang-format on */
|
|
|
|
#ifdef RENDERING_SERVER_LIGHT_CULLER_CALCULATE_LUT
|
|
|
|
// See e.g. http://lspiroengine.com/?p=153 for reference.
|
|
// Principles are the same, but differences to the article:
|
|
// * Order of planes / points is different in Godot.
|
|
// * We use a lookup table at runtime.
|
|
void RenderingServerLightCuller::create_LUT() {
|
|
// Each pair of planes that are opposite can have an edge.
|
|
for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {
|
|
// For each neighbour of the plane.
|
|
PlaneOrder neighs[4];
|
|
get_neighbouring_planes((PlaneOrder)plane_0, neighs);
|
|
|
|
for (int n = 0; n < 4; n++) {
|
|
int plane_1 = neighs[n];
|
|
|
|
// If these are opposite we need to add the 2 points they share.
|
|
PointOrder pts[2];
|
|
get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);
|
|
|
|
add_LUT(plane_0, plane_1, pts);
|
|
}
|
|
}
|
|
|
|
for (uint32_t n = 0; n < LUT_SIZE; n++) {
|
|
compact_LUT_entry(n);
|
|
}
|
|
|
|
debug_print_LUT();
|
|
debug_print_LUT_as_table();
|
|
}
|
|
|
|
// we can pre-create the entire LUT and store it hard coded as a static inside the executable!
|
|
// it is only small in size, 64 entries with max 8 bytes per entry
|
|
void RenderingServerLightCuller::debug_print_LUT_as_table() {
|
|
print_line("\nLIGHT VOLUME TABLE BEGIN\n");
|
|
|
|
print_line("Copy this to LUT_entry_sizes:\n");
|
|
String sz = "{";
|
|
for (int n = 0; n < LUT_SIZE; n++) {
|
|
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
|
|
|
|
sz += itos(entry.size()) + ", ";
|
|
}
|
|
sz += "}";
|
|
print_line(sz);
|
|
print_line("\nCopy this to LUT_entries:\n");
|
|
|
|
for (int n = 0; n < LUT_SIZE; n++) {
|
|
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
|
|
|
|
String sz = "{";
|
|
|
|
// First is the number of points in the entry.
|
|
int s = entry.size();
|
|
|
|
for (int p = 0; p < 8; p++) {
|
|
if (p < s)
|
|
sz += itos(entry[p]);
|
|
else
|
|
sz += "0"; // just a spacer
|
|
|
|
sz += ", ";
|
|
}
|
|
|
|
sz += "},";
|
|
print_line(sz);
|
|
}
|
|
|
|
print_line("\nLIGHT VOLUME TABLE END\n");
|
|
}
|
|
|
|
void RenderingServerLightCuller::debug_print_LUT() {
|
|
for (uint32_t n = 0; n < LUT_SIZE; n++) {
|
|
String sz;
|
|
sz = "LUT" + itos(n) + ":\t";
|
|
|
|
sz += Data::plane_bitfield_to_string(n);
|
|
print_line(sz);
|
|
|
|
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
|
|
|
|
sz = "\t" + string_LUT_entry(entry);
|
|
|
|
print_line(sz);
|
|
}
|
|
}
|
|
|
|
String RenderingServerLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {
|
|
String string;
|
|
|
|
for (uint32_t n = 0; n < p_entry.size(); n++) {
|
|
uint8_t val = p_entry[n];
|
|
DEV_ASSERT(val < 8);
|
|
const char *sz_point = Data::string_points[val];
|
|
string += sz_point;
|
|
string += ", ";
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
String RenderingServerLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {
|
|
String string;
|
|
|
|
for (int i = 0; i < p_entry.size(); i++) {
|
|
int pt_order = p_entry[i];
|
|
|
|
if (p_pair && ((i % 2) == 0)) {
|
|
string += itos(pt_order) + "-";
|
|
} else {
|
|
string += itos(pt_order) + ", ";
|
|
}
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
void RenderingServerLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {
|
|
uint32_t bit0 = 1 << p_plane_0;
|
|
uint32_t bit1 = 1 << p_plane_1;
|
|
|
|
// All entries of the LUT that have plane 0 set and plane 1 not set.
|
|
for (uint32_t n = 0; n < 64; n++) {
|
|
// If bit0 not set...
|
|
if (!(n & bit0))
|
|
continue;
|
|
|
|
// If bit1 set...
|
|
if (n & bit1)
|
|
continue;
|
|
|
|
// Meets criteria.
|
|
add_LUT_entry(n, p_pts);
|
|
}
|
|
}
|
|
|
|
void RenderingServerLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {
|
|
DEV_ASSERT(p_entry_id < LUT_SIZE);
|
|
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
|
|
|
|
entry.push_back(p_pts[0]);
|
|
entry.push_back(p_pts[1]);
|
|
}
|
|
|
|
void RenderingServerLightCuller::compact_LUT_entry(uint32_t p_entry_id) {
|
|
DEV_ASSERT(p_entry_id < LUT_SIZE);
|
|
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
|
|
|
|
int num_pairs = entry.size() / 2;
|
|
|
|
if (num_pairs == 0)
|
|
return;
|
|
|
|
LocalVector<uint8_t> temp;
|
|
|
|
String string;
|
|
string = "Compact LUT" + itos(p_entry_id) + ":\t";
|
|
string += debug_string_LUT_entry(entry, true);
|
|
print_line(string);
|
|
|
|
// Add first pair.
|
|
temp.push_back(entry[0]);
|
|
temp.push_back(entry[1]);
|
|
unsigned int BFpairs = 1;
|
|
|
|
string = debug_string_LUT_entry(temp) + " -> ";
|
|
print_line(string);
|
|
|
|
// Attempt to add a pair each time.
|
|
for (int done = 1; done < num_pairs; done++) {
|
|
string = "done " + itos(done) + ": ";
|
|
// Find a free pair.
|
|
for (int p = 1; p < num_pairs; p++) {
|
|
unsigned int bit = 1 << p;
|
|
// Is it done already?
|
|
if (BFpairs & bit)
|
|
continue;
|
|
|
|
// There must be at least 1 free pair.
|
|
// Attempt to add.
|
|
int a = entry[p * 2];
|
|
int b = entry[(p * 2) + 1];
|
|
|
|
string += "[" + itos(a) + "-" + itos(b) + "], ";
|
|
|
|
int found_a = temp.find(a);
|
|
int found_b = temp.find(b);
|
|
|
|
// Special case, if they are both already in the list, no need to add
|
|
// as this is a link from the tail to the head of the list.
|
|
if ((found_a != -1) && (found_b != -1)) {
|
|
string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";
|
|
BFpairs |= bit;
|
|
goto found;
|
|
}
|
|
|
|
// Find a.
|
|
if (found_a != -1) {
|
|
string += "foundA " + itos(found_a) + " ";
|
|
temp.insert(found_a + 1, b);
|
|
BFpairs |= bit;
|
|
goto found;
|
|
}
|
|
|
|
// Find b.
|
|
if (found_b != -1) {
|
|
string += "foundB " + itos(found_b) + " ";
|
|
temp.insert(found_b, a);
|
|
BFpairs |= bit;
|
|
goto found;
|
|
}
|
|
|
|
} // Check each pair for adding.
|
|
|
|
// If we got here before finding a link, the whole set of planes is INVALID
|
|
// e.g. far and near plane only, does not create continuous sillouhette of edges.
|
|
print_line("\tINVALID");
|
|
entry.clear();
|
|
return;
|
|
|
|
found:;
|
|
print_line(string);
|
|
string = "\ttemp now : " + debug_string_LUT_entry(temp);
|
|
print_line(string);
|
|
}
|
|
|
|
// temp should now be the sorted entry .. delete the old one and replace by temp.
|
|
entry.clear();
|
|
entry = temp;
|
|
}
|
|
|
|
void RenderingServerLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {
|
|
// Table of neighbouring planes to each.
|
|
static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {
|
|
{ // LSM_FP_NEAR
|
|
PLANE_LEFT,
|
|
PLANE_RIGHT,
|
|
PLANE_TOP,
|
|
PLANE_BOTTOM },
|
|
{ // LSM_FP_FAR
|
|
PLANE_LEFT,
|
|
PLANE_RIGHT,
|
|
PLANE_TOP,
|
|
PLANE_BOTTOM },
|
|
{ // LSM_FP_LEFT
|
|
PLANE_TOP,
|
|
PLANE_BOTTOM,
|
|
PLANE_NEAR,
|
|
PLANE_FAR },
|
|
{ // LSM_FP_TOP
|
|
PLANE_LEFT,
|
|
PLANE_RIGHT,
|
|
PLANE_NEAR,
|
|
PLANE_FAR },
|
|
{ // LSM_FP_RIGHT
|
|
PLANE_TOP,
|
|
PLANE_BOTTOM,
|
|
PLANE_NEAR,
|
|
PLANE_FAR },
|
|
{ // LSM_FP_BOTTOM
|
|
PLANE_LEFT,
|
|
PLANE_RIGHT,
|
|
PLANE_NEAR,
|
|
PLANE_FAR },
|
|
};
|
|
|
|
for (int n = 0; n < 4; n++) {
|
|
r_neigh_planes[n] = neigh_table[p_plane][n];
|
|
}
|
|
}
|
|
|
|
// Given two planes, returns the two points shared by those planes. The points are always
|
|
// returned in counter-clockwise order, assuming the first input plane is facing towards
|
|
// the viewer.
|
|
|
|
// param p_plane_a The plane facing towards the viewer.
|
|
// param p_plane_b A plane neighboring p_plane_a.
|
|
// param r_points An array of exactly two elements to be filled with the indices of the points
|
|
// on return.
|
|
|
|
void RenderingServerLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {
|
|
static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {
|
|
{
|
|
// LSM_FP_NEAR
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_NEAR_LEFT_TOP,
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_NEAR_RIGHT_TOP,
|
|
PT_NEAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
PT_NEAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
},
|
|
},
|
|
|
|
{
|
|
// LSM_FP_FAR
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_FAR_LEFT_BOTTOM,
|
|
PT_FAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_FAR_LEFT_TOP,
|
|
PT_FAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_FAR_RIGHT_TOP,
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
PT_FAR_LEFT_BOTTOM,
|
|
},
|
|
},
|
|
|
|
{
|
|
// LSM_FP_LEFT
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
PT_NEAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_LEFT_TOP,
|
|
PT_FAR_LEFT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_NEAR_LEFT_TOP,
|
|
PT_FAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_FAR_LEFT_BOTTOM,
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
},
|
|
},
|
|
|
|
{
|
|
// LSM_FP_TOP
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_NEAR_LEFT_TOP,
|
|
PT_NEAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_RIGHT_TOP,
|
|
PT_FAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_FAR_LEFT_TOP,
|
|
PT_NEAR_LEFT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_NEAR_RIGHT_TOP,
|
|
PT_FAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.
|
|
},
|
|
},
|
|
|
|
{
|
|
// LSM_FP_RIGHT
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_NEAR_RIGHT_TOP,
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
PT_FAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_FAR_RIGHT_TOP,
|
|
PT_NEAR_RIGHT_TOP,
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
},
|
|
},
|
|
|
|
// ==
|
|
|
|
// P_NEAR,
|
|
// P_FAR,
|
|
// P_LEFT,
|
|
// P_TOP,
|
|
// P_RIGHT,
|
|
// P_BOTTOM,
|
|
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
{
|
|
// LSM_FP_NEAR
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_FAR
|
|
PT_FAR_LEFT_BOTTOM,
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_LEFT
|
|
PT_NEAR_LEFT_BOTTOM,
|
|
PT_FAR_LEFT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_TOP
|
|
PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.
|
|
},
|
|
{
|
|
// LSM_FP_RIGHT
|
|
PT_FAR_RIGHT_BOTTOM,
|
|
PT_NEAR_RIGHT_BOTTOM,
|
|
},
|
|
{
|
|
// LSM_FP_BOTTOM
|
|
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.
|
|
},
|
|
},
|
|
|
|
// ==
|
|
|
|
};
|
|
r_points[0] = fp_table[p_plane_a][p_plane_b][0];
|
|
r_points[1] = fp_table[p_plane_a][p_plane_b][1];
|
|
}
|
|
|
|
#endif
|