Path: blob/master/servers/rendering/rendering_light_culler.cpp
10277 views
/**************************************************************************/1/* rendering_light_culler.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "rendering_light_culler.h"3132#include "core/math/plane.h"33#include "core/math/projection.h"34#include "rendering_server_globals.h"3536#ifdef RENDERING_LIGHT_CULLER_DEBUG_STRINGS37const char *RenderingLightCuller::Data::string_planes[] = {38"NEAR",39"FAR",40"LEFT",41"TOP",42"RIGHT",43"BOTTOM",44};45const char *RenderingLightCuller::Data::string_points[] = {46"FAR_LEFT_TOP",47"FAR_LEFT_BOTTOM",48"FAR_RIGHT_TOP",49"FAR_RIGHT_BOTTOM",50"NEAR_LEFT_TOP",51"NEAR_LEFT_BOTTOM",52"NEAR_RIGHT_TOP",53"NEAR_RIGHT_BOTTOM",54};5556String RenderingLightCuller::Data::plane_bitfield_to_string(unsigned int BF) {57String sz;5859for (int n = 0; n < 6; n++) {60unsigned int bit = 1 << n;61if (BF & bit) {62sz += String(string_planes[n]) + ", ";63}64}6566return sz;67}68#endif6970void RenderingLightCuller::prepare_directional_light(const RendererSceneCull::Instance *p_instance, int32_t p_directional_light_id) {71//data.directional_light = p_instance;72// Something is probably going wrong, we shouldn't have this many directional lights...73ERR_FAIL_COND(p_directional_light_id > 512);74DEV_ASSERT(p_directional_light_id >= 0);7576// First make sure we have enough directional lights to hold this one.77if (p_directional_light_id >= (int32_t)data.directional_cull_planes.size()) {78data.directional_cull_planes.resize(p_directional_light_id + 1);79}8081_prepare_light(*p_instance, p_directional_light_id);82}8384bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id) {85if (!data.is_active()) {86return true;87}8889LightSource lsource;90switch (RSG::light_storage->light_get_type(p_instance.base)) {91case RS::LIGHT_SPOT:92lsource.type = LightSource::ST_SPOTLIGHT;93lsource.angle = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SPOT_ANGLE);94lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);95break;96case RS::LIGHT_OMNI:97lsource.type = LightSource::ST_OMNI;98lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);99break;100case RS::LIGHT_DIRECTIONAL:101lsource.type = LightSource::ST_DIRECTIONAL;102// Could deal with a max directional shadow range here? NYI103// LIGHT_PARAM_SHADOW_MAX_DISTANCE104break;105}106107lsource.pos = p_instance.transform.origin;108lsource.dir = -p_instance.transform.basis.get_column(2);109lsource.dir.normalize();110111bool visible;112if (p_directional_light_id == -1) {113visible = _add_light_camera_planes(data.regular_cull_planes, lsource);114} else {115visible = _add_light_camera_planes(data.directional_cull_planes[p_directional_light_id], lsource);116}117118if (data.light_culling_active) {119return visible;120}121return true;122}123124bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id) {125if (!data.is_active() || !is_caster_culling_active()) {126return true;127}128129ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true);130131LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id];132133Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]);134Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]);135AABB bb(mins, maxs - mins);136137real_t r_min, r_max;138for (int p = 0; p < cull_planes.num_cull_planes; p++) {139bb.project_range_in_plane(cull_planes.cull_planes[p], r_min, r_max);140if (r_min > 0.0f) {141#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT142cull_planes.rejected_count++;143#endif144145return false;146}147}148149return true;150}151152void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {153if (!data.is_active() || !is_caster_culling_active()) {154return;155}156157// If the light is out of range, no need to check anything, just return 0 casters.158// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).159if (data.out_of_range) {160return;161}162163// Shorter local alias.164PagedArray<RendererSceneCull::Instance *> &list = r_instance_shadow_cull_result;165166#ifdef LIGHT_CULLER_DEBUG_LOGGING167uint32_t count_before = r_instance_shadow_cull_result.size();168#endif169170// Go through all the casters in the list (the list will hopefully shrink as we go).171for (int n = 0; n < (int)list.size(); n++) {172// World space aabb.173const AABB &bb = list[n]->transformed_aabb;174175#ifdef LIGHT_CULLER_DEBUG_LOGGING176if (is_logging()) {177print_line("bb : " + String(bb));178}179#endif180181real_t r_min, r_max;182bool show = true;183184for (int p = 0; p < data.regular_cull_planes.num_cull_planes; p++) {185// As we only need r_min, could this be optimized?186bb.project_range_in_plane(data.regular_cull_planes.cull_planes[p], r_min, r_max);187188#ifdef LIGHT_CULLER_DEBUG_LOGGING189if (is_logging()) {190print_line("\tplane " + itos(p) + " : " + String(data.regular_cull_planes.cull_planes[p]) + " r_min " + String(Variant(r_min)) + " r_max " + String(Variant(r_max)));191}192#endif193194if (r_min > 0.0f) {195show = false;196break;197}198}199200// Remove.201if (!show) {202list.remove_at_unordered(n);203204// Repeat this element next iteration of the loop as it has been removed and replaced by the last.205n--;206207#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT208data.regular_rejected_count++;209#endif210}211}212213#ifdef LIGHT_CULLER_DEBUG_LOGGING214uint32_t removed = r_instance_shadow_cull_result.size() - count_before;215if (removed) {216if (((data.debug_count) % 60) == 0) {217print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(count_before) + " after " + itos(r_instance_shadow_cull_result.size()));218}219}220#endif221}222223void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) {224ERR_FAIL_COND(num_cull_planes >= MAX_CULL_PLANES);225cull_planes[num_cull_planes++] = p;226}227228// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third229// points are derived differently.230bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {231uint32_t lookup = 0;232r_cull_planes.num_cull_planes = 0;233234// Directional light, we will use dot against the light direction to determine back facing planes.235for (int n = 0; n < 6; n++) {236float dot = data.frustum_planes[n].normal.dot(p_light_source.dir);237if (dot > 0.0f) {238lookup |= 1 << n;239240// Add backfacing camera frustum planes.241r_cull_planes.add_cull_plane(data.frustum_planes[n]);242}243}244245ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);246247// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)248// then we will add the camera frustum planes to clip the light volume .. there is no need to249// render shadow casters outside the frustum as shadows can never re-enter the frustum.250251// Should never happen with directional light?? This may be able to be removed.252if (lookup == 63) {253r_cull_planes.num_cull_planes = 0;254for (int n = 0; n < data.frustum_planes.size(); n++) {255r_cull_planes.add_cull_plane(data.frustum_planes[n]);256}257258return true;259}260261// Each edge forms a plane.262#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT263const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];264265// each edge forms a plane266int n_edges = entry.size() - 1;267#else268uint8_t *entry = &data.LUT_entries[lookup][0];269int n_edges = data.LUT_entry_sizes[lookup] - 1;270#endif271272for (int e = 0; e < n_edges; e++) {273int i0 = entry[e];274int i1 = entry[e + 1];275const Vector3 &pt0 = data.frustum_points[i0];276const Vector3 &pt1 = data.frustum_points[i1];277278// Create a third point from the light direction.279Vector3 pt2 = pt0 - p_light_source.dir;280281if (!_is_colinear_tri(pt0, pt1, pt2)) {282// Create plane from 3 points.283Plane p(pt0, pt1, pt2);284r_cull_planes.add_cull_plane(p);285}286}287288// Last to 0 edge.289if (n_edges) {290int i0 = entry[n_edges]; // Last.291int i1 = entry[0]; // First.292293const Vector3 &pt0 = data.frustum_points[i0];294const Vector3 &pt1 = data.frustum_points[i1];295296// Create a third point from the light direction.297Vector3 pt2 = pt0 - p_light_source.dir;298299if (!_is_colinear_tri(pt0, pt1, pt2)) {300// Create plane from 3 points.301Plane p(pt0, pt1, pt2);302r_cull_planes.add_cull_plane(p);303}304}305306#ifdef LIGHT_CULLER_DEBUG_LOGGING307if (is_logging()) {308print_line("lcam.pos is " + String(p_light_source.pos));309}310#endif311312return true;313}314315bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {316if (!data.is_active()) {317return true;318}319320// We should have called prepare_camera before this.321ERR_FAIL_COND_V(data.frustum_planes.size() != 6, true);322323switch (p_light_source.type) {324case LightSource::ST_SPOTLIGHT:325case LightSource::ST_OMNI:326break;327case LightSource::ST_DIRECTIONAL:328return add_light_camera_planes_directional(r_cull_planes, p_light_source);329break;330default:331return false; // not yet supported332break;333}334335// Start with 0 cull planes.336r_cull_planes.num_cull_planes = 0;337data.out_of_range = false;338uint32_t lookup = 0;339340// Find which of the camera planes are facing away from the light.341// We can also test for the situation where the light max range means it cannot342// affect the camera frustum. This is absolutely worth doing because it is relatively343// cheap, and if the entire light can be culled this can vastly improve performance344// (much more than just culling casters).345346// POINT LIGHT (spotlight, omni)347// Instead of using dot product to compare light direction to plane, we can simply348// find out which side of the plane the camera is on. By definition this marks the point at which the plane349// becomes invisible.350351// OMNIS352if (p_light_source.type == LightSource::ST_OMNI) {353for (int n = 0; n < 6; n++) {354float dist = data.frustum_planes[n].distance_to(p_light_source.pos);355if (dist < 0.0f) {356lookup |= 1 << n;357358// Add backfacing camera frustum planes.359r_cull_planes.add_cull_plane(data.frustum_planes[n]);360} else {361// Is the light out of range?362// This is one of the tests. If the point source is more than range distance from a frustum plane, it can't363// be seen.364if (dist >= p_light_source.range) {365// If the light is out of range, no need to do anything else, everything will be culled.366data.out_of_range = true;367return false;368}369}370}371} else {372// SPOTLIGHTs, more complex to cull.373Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);374375// This is the radius of the cone at distance 1.376float radius_at_dist_one = Math::tan(Math::deg_to_rad(p_light_source.angle));377378// The worst case radius of the cone at the end point can be calculated379// (the radius will scale linearly with length along the cone).380float end_cone_radius = radius_at_dist_one * p_light_source.range;381382for (int n = 0; n < 6; n++) {383float dist = data.frustum_planes[n].distance_to(p_light_source.pos);384if (dist < 0.0f) {385// Either the plane is backfacing or we are inside the frustum.386lookup |= 1 << n;387388// Add backfacing camera frustum planes.389r_cull_planes.add_cull_plane(data.frustum_planes[n]);390} else {391// The light is in front of the plane.392393// Is the light out of range?394if (dist >= p_light_source.range) {395data.out_of_range = true;396return false;397}398399// For a spotlight, we can use an extra test400// at this point the cone start is in front of the plane...401// If the cone end point is further than the maximum possible distance to the plane402// we can guarantee that the cone does not cross the plane, and hence the cone403// is outside the frustum.404float dist_end = data.frustum_planes[n].distance_to(pos_end);405406if (dist_end >= end_cone_radius) {407data.out_of_range = true;408return false;409}410}411}412}413414// The lookup should be within the LUT, logic should prevent this.415ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);416417// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)418// then we will add the camera frustum planes to clip the light volume .. there is no need to419// render shadow casters outside the frustum as shadows can never re-enter the frustum.420if (lookup == 63) {421r_cull_planes.num_cull_planes = 0;422for (int n = 0; n < data.frustum_planes.size(); n++) {423r_cull_planes.add_cull_plane(data.frustum_planes[n]);424}425426return true;427}428429// Each edge forms a plane.430uint8_t *entry = &data.LUT_entries[lookup][0];431int n_edges = data.LUT_entry_sizes[lookup] - 1;432433const Vector3 &pt2 = p_light_source.pos;434435for (int e = 0; e < n_edges; e++) {436int i0 = entry[e];437int i1 = entry[e + 1];438const Vector3 &pt0 = data.frustum_points[i0];439const Vector3 &pt1 = data.frustum_points[i1];440441if (!_is_colinear_tri(pt0, pt1, pt2)) {442// Create plane from 3 points.443Plane p(pt0, pt1, pt2);444r_cull_planes.add_cull_plane(p);445}446}447448// Last to 0 edge.449if (n_edges) {450int i0 = entry[n_edges]; // Last.451int i1 = entry[0]; // First.452453const Vector3 &pt0 = data.frustum_points[i0];454const Vector3 &pt1 = data.frustum_points[i1];455456if (!_is_colinear_tri(pt0, pt1, pt2)) {457// Create plane from 3 points.458Plane p(pt0, pt1, pt2);459r_cull_planes.add_cull_plane(p);460}461}462463#ifdef LIGHT_CULLER_DEBUG_LOGGING464if (is_logging()) {465print_line("lsource.pos is " + String(p_light_source.pos));466}467#endif468469return true;470}471472bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {473data.debug_count++;474if (data.debug_count >= 120) {475data.debug_count = 0;476}477478// For debug flash off and on.479#ifdef LIGHT_CULLER_DEBUG_FLASH480if (!Engine::get_singleton()->is_editor_hint()) {481int dc = Engine::get_singleton()->get_process_frames() / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;482bool bnew_active;483bnew_active = (dc % 2) == 0;484485if (bnew_active != data.light_culling_active) {486data.light_culling_active = bnew_active;487print_line("switching light culler " + String(Variant(data.light_culling_active)));488}489}490#endif491492if (!data.is_active()) {493return false;494}495496// Get the camera frustum planes in world space.497data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);498DEV_CHECK_ONCE(data.frustum_planes.size() == 6);499500data.regular_cull_planes.num_cull_planes = 0;501502#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT503if (is_logging()) {504for (uint32_t n = 0; n < data.directional_cull_planes.size(); n++) {505print_line("LightCuller directional light " + itos(n) + " rejected " + itos(data.directional_cull_planes[n].rejected_count) + " instances.");506}507}508#endif509#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT510if (data.regular_rejected_count) {511print_line("LightCuller regular lights rejected " + itos(data.regular_rejected_count) + " instances.");512}513data.regular_rejected_count = 0;514#endif515516data.directional_cull_planes.resize(0);517518#ifdef LIGHT_CULLER_DEBUG_LOGGING519if (is_logging()) {520for (int p = 0; p < 6; p++) {521print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));522}523}524#endif525526// We want to calculate the frustum corners in a specific order.527const Projection::Planes intersections[8][3] = {528{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },529{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },530{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },531{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },532{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },533{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },534{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },535{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },536};537538for (int i = 0; i < 8; i++) {539// 3 plane intersection, gives us a point.540bool 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]);541542// What happens with a zero frustum? NYI - deal with this.543ERR_FAIL_COND_V(!res, false);544545#ifdef LIGHT_CULLER_DEBUG_LOGGING546if (is_logging()) {547print_line("point " + itos(i) + " -> " + String(data.frustum_points[i]));548}549#endif550}551552return true;553}554555RenderingLightCuller::RenderingLightCuller() {556// Used to determine which frame to give debug output.557data.debug_count = -1;558559// Uncomment below to switch off light culler in the editor.560// data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;561562#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT563create_LUT();564#endif565}566567/* clang-format off */568uint8_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, };569570// The lookup table used to determine which edges form the silhouette of the camera frustum,571// depending on the viewing angle (defined by which camera planes are backward facing).572uint8_t RenderingLightCuller::Data::LUT_entries[LUT_SIZE][8] = {573{0, 0, 0, 0, 0, 0, 0, 0, },574{7, 6, 4, 5, 0, 0, 0, 0, },575{1, 0, 2, 3, 0, 0, 0, 0, },576{0, 0, 0, 0, 0, 0, 0, 0, },577{1, 5, 4, 0, 0, 0, 0, 0, },578{1, 5, 7, 6, 4, 0, 0, 0, },579{4, 0, 2, 3, 1, 5, 0, 0, },580{5, 7, 6, 4, 0, 2, 3, 1, },581{0, 4, 6, 2, 0, 0, 0, 0, },582{0, 4, 5, 7, 6, 2, 0, 0, },583{6, 2, 3, 1, 0, 4, 0, 0, },584{2, 3, 1, 0, 4, 5, 7, 6, },585{0, 1, 5, 4, 6, 2, 0, 0, },586{0, 1, 5, 7, 6, 2, 0, 0, },587{6, 2, 3, 1, 5, 4, 0, 0, },588{2, 3, 1, 5, 7, 6, 0, 0, },589{2, 6, 7, 3, 0, 0, 0, 0, },590{2, 6, 4, 5, 7, 3, 0, 0, },591{7, 3, 1, 0, 2, 6, 0, 0, },592{3, 1, 0, 2, 6, 4, 5, 7, },593{0, 0, 0, 0, 0, 0, 0, 0, },594{2, 6, 4, 0, 1, 5, 7, 3, },595{7, 3, 1, 5, 4, 0, 2, 6, },596{0, 0, 0, 0, 0, 0, 0, 0, },597{2, 0, 4, 6, 7, 3, 0, 0, },598{2, 0, 4, 5, 7, 3, 0, 0, },599{7, 3, 1, 0, 4, 6, 0, 0, },600{3, 1, 0, 4, 5, 7, 0, 0, },601{2, 0, 1, 5, 4, 6, 7, 3, },602{2, 0, 1, 5, 7, 3, 0, 0, },603{7, 3, 1, 5, 4, 6, 0, 0, },604{3, 1, 5, 7, 0, 0, 0, 0, },605{3, 7, 5, 1, 0, 0, 0, 0, },606{3, 7, 6, 4, 5, 1, 0, 0, },607{5, 1, 0, 2, 3, 7, 0, 0, },608{7, 6, 4, 5, 1, 0, 2, 3, },609{3, 7, 5, 4, 0, 1, 0, 0, },610{3, 7, 6, 4, 0, 1, 0, 0, },611{5, 4, 0, 2, 3, 7, 0, 0, },612{7, 6, 4, 0, 2, 3, 0, 0, },613{0, 0, 0, 0, 0, 0, 0, 0, },614{3, 7, 6, 2, 0, 4, 5, 1, },615{5, 1, 0, 4, 6, 2, 3, 7, },616{0, 0, 0, 0, 0, 0, 0, 0, },617{3, 7, 5, 4, 6, 2, 0, 1, },618{3, 7, 6, 2, 0, 1, 0, 0, },619{5, 4, 6, 2, 3, 7, 0, 0, },620{7, 6, 2, 3, 0, 0, 0, 0, },621{3, 2, 6, 7, 5, 1, 0, 0, },622{3, 2, 6, 4, 5, 1, 0, 0, },623{5, 1, 0, 2, 6, 7, 0, 0, },624{1, 0, 2, 6, 4, 5, 0, 0, },625{3, 2, 6, 7, 5, 4, 0, 1, },626{3, 2, 6, 4, 0, 1, 0, 0, },627{5, 4, 0, 2, 6, 7, 0, 0, },628{6, 4, 0, 2, 0, 0, 0, 0, },629{3, 2, 0, 4, 6, 7, 5, 1, },630{3, 2, 0, 4, 5, 1, 0, 0, },631{5, 1, 0, 4, 6, 7, 0, 0, },632{1, 0, 4, 5, 0, 0, 0, 0, },633{0, 0, 0, 0, 0, 0, 0, 0, },634{3, 2, 0, 1, 0, 0, 0, 0, },635{5, 4, 6, 7, 0, 0, 0, 0, },636{0, 0, 0, 0, 0, 0, 0, 0, },637};638639/* clang-format on */640641#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT642643// See e.g. http://lspiroengine.com/?p=153 for reference.644// Principles are the same, but differences to the article:645// * Order of planes / points is different in Godot.646// * We use a lookup table at runtime.647void RenderingLightCuller::create_LUT() {648// Each pair of planes that are opposite can have an edge.649for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {650// For each neighbor of the plane.651PlaneOrder neighs[4];652get_neighbouring_planes((PlaneOrder)plane_0, neighs);653654for (int n = 0; n < 4; n++) {655int plane_1 = neighs[n];656657// If these are opposite we need to add the 2 points they share.658PointOrder pts[2];659get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);660661add_LUT(plane_0, plane_1, pts);662}663}664665for (uint32_t n = 0; n < LUT_SIZE; n++) {666compact_LUT_entry(n);667}668669debug_print_LUT();670debug_print_LUT_as_table();671}672673// we can pre-create the entire LUT and store it hard coded as a static inside the executable!674// it is only small in size, 64 entries with max 8 bytes per entry675void RenderingLightCuller::debug_print_LUT_as_table() {676print_line("\nLIGHT VOLUME TABLE BEGIN\n");677678print_line("Copy this to LUT_entry_sizes:\n");679String sz = "{";680for (int n = 0; n < LUT_SIZE; n++) {681const LocalVector<uint8_t> &entry = _calculated_LUT[n];682683sz += itos(entry.size()) + ", ";684}685sz += "}";686print_line(sz);687print_line("\nCopy this to LUT_entries:\n");688689for (int n = 0; n < LUT_SIZE; n++) {690const LocalVector<uint8_t> &entry = _calculated_LUT[n];691692String sz = "{";693694// First is the number of points in the entry.695int s = entry.size();696697for (int p = 0; p < 8; p++) {698if (p < s) {699sz += itos(entry[p]);700} else {701sz += "0"; // just a spacer702}703704sz += ", ";705}706707sz += "},";708print_line(sz);709}710711print_line("\nLIGHT VOLUME TABLE END\n");712}713714void RenderingLightCuller::debug_print_LUT() {715for (int n = 0; n < LUT_SIZE; n++) {716String sz;717sz = "LUT" + itos(n) + ":\t";718719sz += Data::plane_bitfield_to_string(n);720print_line(sz);721722const LocalVector<uint8_t> &entry = _calculated_LUT[n];723724sz = "\t" + string_LUT_entry(entry);725726print_line(sz);727}728}729730String RenderingLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {731String string;732733for (uint32_t n = 0; n < p_entry.size(); n++) {734uint8_t val = p_entry[n];735DEV_ASSERT(val < 8);736const char *sz_point = Data::string_points[val];737string += sz_point;738string += ", ";739}740741return string;742}743744String RenderingLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {745String string;746747for (uint32_t i = 0; i < p_entry.size(); i++) {748int pt_order = p_entry[i];749if (p_pair && ((i % 2) == 0)) {750string += itos(pt_order) + "-";751} else {752string += itos(pt_order) + ", ";753}754}755756return string;757}758759void RenderingLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {760// Note that some entries to the LUT will be "impossible" situations,761// because it contains all combinations of plane flips.762uint32_t bit0 = 1 << p_plane_0;763uint32_t bit1 = 1 << p_plane_1;764765// All entries of the LUT that have plane 0 set and plane 1 not set.766for (uint32_t n = 0; n < 64; n++) {767// If bit0 not set...768if (!(n & bit0)) {769continue;770}771772// If bit1 set...773if (n & bit1) {774continue;775}776777// Meets criteria.778add_LUT_entry(n, p_pts);779}780}781782void RenderingLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {783DEV_ASSERT(p_entry_id < LUT_SIZE);784LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];785786entry.push_back(p_pts[0]);787entry.push_back(p_pts[1]);788}789790void RenderingLightCuller::compact_LUT_entry(uint32_t p_entry_id) {791DEV_ASSERT(p_entry_id < LUT_SIZE);792LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];793794int num_pairs = entry.size() / 2;795796if (num_pairs == 0) {797return;798}799800LocalVector<uint8_t> temp;801802String string;803string = "Compact LUT" + itos(p_entry_id) + ":\t";804string += debug_string_LUT_entry(entry, true);805print_line(string);806807// Add first pair.808temp.push_back(entry[0]);809temp.push_back(entry[1]);810unsigned int BFpairs = 1;811812string = debug_string_LUT_entry(temp) + " -> ";813print_line(string);814815// Attempt to add a pair each time.816for (int done = 1; done < num_pairs; done++) {817string = "done " + itos(done) + ": ";818// Find a free pair.819for (int p = 1; p < num_pairs; p++) {820unsigned int bit = 1 << p;821// Is it done already?822if (BFpairs & bit) {823continue;824}825826// There must be at least 1 free pair.827// Attempt to add.828int a = entry[p * 2];829int b = entry[(p * 2) + 1];830831string += "[" + itos(a) + "-" + itos(b) + "], ";832833int found_a = temp.find(a);834int found_b = temp.find(b);835836// Special case, if they are both already in the list, no need to add837// as this is a link from the tail to the head of the list.838if ((found_a != -1) && (found_b != -1)) {839string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";840BFpairs |= bit;841goto found;842}843844// Find a.845if (found_a != -1) {846string += "foundA " + itos(found_a) + " ";847temp.insert(found_a + 1, b);848BFpairs |= bit;849goto found;850}851852// Find b.853if (found_b != -1) {854string += "foundB " + itos(found_b) + " ";855temp.insert(found_b, a);856BFpairs |= bit;857goto found;858}859860} // Check each pair for adding.861862// If we got here before finding a link, the whole set of planes is INVALID863// e.g. far and near plane only, does not create continuous sillouhette of edges.864print_line("\tINVALID");865entry.clear();866return;867868found:;869print_line(string);870string = "\ttemp now : " + debug_string_LUT_entry(temp);871print_line(string);872}873874// temp should now be the sorted entry .. delete the old one and replace by temp.875entry.clear();876entry = temp;877}878879void RenderingLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {880// Table of neighboring planes to each.881static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {882{ // LSM_FP_NEAR883PLANE_LEFT,884PLANE_RIGHT,885PLANE_TOP,886PLANE_BOTTOM },887{ // LSM_FP_FAR888PLANE_LEFT,889PLANE_RIGHT,890PLANE_TOP,891PLANE_BOTTOM },892{ // LSM_FP_LEFT893PLANE_TOP,894PLANE_BOTTOM,895PLANE_NEAR,896PLANE_FAR },897{ // LSM_FP_TOP898PLANE_LEFT,899PLANE_RIGHT,900PLANE_NEAR,901PLANE_FAR },902{ // LSM_FP_RIGHT903PLANE_TOP,904PLANE_BOTTOM,905PLANE_NEAR,906PLANE_FAR },907{ // LSM_FP_BOTTOM908PLANE_LEFT,909PLANE_RIGHT,910PLANE_NEAR,911PLANE_FAR },912};913914for (int n = 0; n < 4; n++) {915r_neigh_planes[n] = neigh_table[p_plane][n];916}917}918919// Given two planes, returns the two points shared by those planes. The points are always920// returned in counter-clockwise order, assuming the first input plane is facing towards921// the viewer.922923// param p_plane_a The plane facing towards the viewer.924// param p_plane_b A plane neighboring p_plane_a.925// param r_points An array of exactly two elements to be filled with the indices of the points926// on return.927928void RenderingLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {929static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {930{931// LSM_FP_NEAR932{933// LSM_FP_NEAR934PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP, // Invalid combination.935},936{937// LSM_FP_FAR938PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.939},940{941// LSM_FP_LEFT942PT_NEAR_LEFT_TOP,943PT_NEAR_LEFT_BOTTOM,944},945{946// LSM_FP_TOP947PT_NEAR_RIGHT_TOP,948PT_NEAR_LEFT_TOP,949},950{951// LSM_FP_RIGHT952PT_NEAR_RIGHT_BOTTOM,953PT_NEAR_RIGHT_TOP,954},955{956// LSM_FP_BOTTOM957PT_NEAR_LEFT_BOTTOM,958PT_NEAR_RIGHT_BOTTOM,959},960},961962{963// LSM_FP_FAR964{965// LSM_FP_NEAR966PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP, // Invalid combination.967},968{969// LSM_FP_FAR970PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.971},972{973// LSM_FP_LEFT974PT_FAR_LEFT_BOTTOM,975PT_FAR_LEFT_TOP,976},977{978// LSM_FP_TOP979PT_FAR_LEFT_TOP,980PT_FAR_RIGHT_TOP,981},982{983// LSM_FP_RIGHT984PT_FAR_RIGHT_TOP,985PT_FAR_RIGHT_BOTTOM,986},987{988// LSM_FP_BOTTOM989PT_FAR_RIGHT_BOTTOM,990PT_FAR_LEFT_BOTTOM,991},992},993994{995// LSM_FP_LEFT996{997// LSM_FP_NEAR998PT_NEAR_LEFT_BOTTOM,999PT_NEAR_LEFT_TOP,1000},1001{1002// LSM_FP_FAR1003PT_FAR_LEFT_TOP,1004PT_FAR_LEFT_BOTTOM,1005},1006{1007// LSM_FP_LEFT1008PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.1009},1010{1011// LSM_FP_TOP1012PT_NEAR_LEFT_TOP,1013PT_FAR_LEFT_TOP,1014},1015{1016// LSM_FP_RIGHT1017PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.1018},1019{1020// LSM_FP_BOTTOM1021PT_FAR_LEFT_BOTTOM,1022PT_NEAR_LEFT_BOTTOM,1023},1024},10251026{1027// LSM_FP_TOP1028{1029// LSM_FP_NEAR1030PT_NEAR_LEFT_TOP,1031PT_NEAR_RIGHT_TOP,1032},1033{1034// LSM_FP_FAR1035PT_FAR_RIGHT_TOP,1036PT_FAR_LEFT_TOP,1037},1038{1039// LSM_FP_LEFT1040PT_FAR_LEFT_TOP,1041PT_NEAR_LEFT_TOP,1042},1043{1044// LSM_FP_TOP1045PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP, // Invalid combination.1046},1047{1048// LSM_FP_RIGHT1049PT_NEAR_RIGHT_TOP,1050PT_FAR_RIGHT_TOP,1051},1052{1053// LSM_FP_BOTTOM1054PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.1055},1056},10571058{1059// LSM_FP_RIGHT1060{1061// LSM_FP_NEAR1062PT_NEAR_RIGHT_TOP,1063PT_NEAR_RIGHT_BOTTOM,1064},1065{1066// LSM_FP_FAR1067PT_FAR_RIGHT_BOTTOM,1068PT_FAR_RIGHT_TOP,1069},1070{1071// LSM_FP_LEFT1072PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.1073},1074{1075// LSM_FP_TOP1076PT_FAR_RIGHT_TOP,1077PT_NEAR_RIGHT_TOP,1078},1079{1080// LSM_FP_RIGHT1081PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM, // Invalid combination.1082},1083{1084// LSM_FP_BOTTOM1085PT_NEAR_RIGHT_BOTTOM,1086PT_FAR_RIGHT_BOTTOM,1087},1088},10891090// ==10911092// P_NEAR,1093// P_FAR,1094// P_LEFT,1095// P_TOP,1096// P_RIGHT,1097// P_BOTTOM,10981099{1100// LSM_FP_BOTTOM1101{1102// LSM_FP_NEAR1103PT_NEAR_RIGHT_BOTTOM,1104PT_NEAR_LEFT_BOTTOM,1105},1106{1107// LSM_FP_FAR1108PT_FAR_LEFT_BOTTOM,1109PT_FAR_RIGHT_BOTTOM,1110},1111{1112// LSM_FP_LEFT1113PT_NEAR_LEFT_BOTTOM,1114PT_FAR_LEFT_BOTTOM,1115},1116{1117// LSM_FP_TOP1118PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM, // Invalid combination.1119},1120{1121// LSM_FP_RIGHT1122PT_FAR_RIGHT_BOTTOM,1123PT_NEAR_RIGHT_BOTTOM,1124},1125{1126// LSM_FP_BOTTOM1127PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM, // Invalid combination.1128},1129},11301131// ==11321133};1134r_points[0] = fp_table[p_plane_a][p_plane_b][0];1135r_points[1] = fp_table[p_plane_a][p_plane_b][1];1136}11371138#endif113911401141