Path: blob/master/servers/rendering/rendering_light_culler.cpp
20873 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;102103lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE);104switch (RSG::light_storage->light_directional_get_shadow_mode(p_instance.base)) {105case RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL:106lsource.cascade_count = 1;107break;108case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS:109lsource.cascade_count = 2;110break;111case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS:112lsource.cascade_count = 4;113break;114default:115ERR_FAIL_V_MSG(false, "Only directional lights with 1, 2, or 4 shadow cascades are supported.");116break;117}118lsource.cascade_splits[0] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET);119lsource.cascade_splits[1] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET);120lsource.cascade_splits[2] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET);121lsource.blend_splits = RSG::light_storage->light_directional_get_blend_splits(p_instance.base);122break;123}124125lsource.pos = p_instance.transform.origin;126lsource.dir = -p_instance.transform.basis.get_column(2);127lsource.dir.normalize();128129// In reality there's always going to be at least one cascade, but the compiler can't know that.130// If SOMEHOW there's actually 0 cascades though, I suppose there isn't going to be anything visible after all.131bool visible = false;132if (p_directional_light_id == -1) {133visible = _add_light_camera_planes(data.regular_cull_planes, lsource, { &data.frustum_planes[0], data.frustum_points });134} else {135int used_planes = 1 + lsource.cascade_count; // 2 for ortho (near+far), 3 for pssm2 (near+mid+far), 5 for pssm4 (near+3mids+far).136Plane boundary_planes[5];137{138constexpr const int MAX_PLANES = 5;139real_t plane_distances[MAX_PLANES] = {140data.camera_projection.get_z_near(),141lsource.cascade_splits[0] * lsource.range,142lsource.cascade_splits[1] * lsource.range,143lsource.cascade_splits[2] * lsource.range,144lsource.range,145};146//If not 4 cascades, replace last used cascade plane distance with max shadow range (shadow far plane distance).147plane_distances[used_planes - 1] = lsource.range;148#ifdef LIGHT_CULLER_DEBUG_LOGGING149if (is_logging()) {150print_line("cascade split planes (first " + itos(used_planes) + " used): " +151String(Variant(plane_distances[0])) + "m, " +152String(Variant(plane_distances[1])) + "m, " +153String(Variant(plane_distances[2])) + "m, " +154String(Variant(plane_distances[3])) + "m, " +155String(Variant(plane_distances[4])) + "m");156}157#endif158Vector3 camera_normal = data.camera_transform.basis.xform(Vector3(0, 0, 1)).normalized();159for (int i = 0; i < used_planes; i++) {160real_t plane_distance = plane_distances[i];161162//Plane compute163boundary_planes[i] = Plane(164camera_normal,165data.camera_transform.origin + camera_normal * -plane_distance);166}167}168169for (int i = 0; i < lsource.cascade_count; i++) {170/*171enum PlaneOrder {172PLANE_NEAR,173PLANE_FAR,174PLANE_LEFT,175PLANE_TOP,176PLANE_RIGHT,177PLANE_BOTTOM,178PLANE_TOTAL,179};180*/181Plane cull_planes[6] = {182boundary_planes[MAX(i - (lsource.blend_splits ? 1 : 0), 0)],183Plane(-boundary_planes[i + 1].normal, -boundary_planes[i + 1].d), // Normal flip to ensure far is outward-facing.184data.frustum_planes[2],185data.frustum_planes[3],186data.frustum_planes[4],187data.frustum_planes[5],188};189190#ifdef LIGHT_CULLER_DEBUG_LOGGING191if (is_logging()) {192for (int p = 0; p < 6; p++) {193print_line("cascade " + itos(i) + " plane " + itos(p) + " : " + String(cull_planes[p]));194}195}196#endif197198// Frustum point calculation199Vector3 frustum_points[8];200bool success = create_frustum_points(cull_planes, frustum_points);201ERR_FAIL_COND_V(!success, false);202203// Replace frustum arguments with cascade's.204LightCullPlanes &destination = data.directional_cull_planes[p_directional_light_id].planes[i];205visible = _add_light_camera_planes(destination, lsource, { cull_planes, frustum_points });206}207}208209if (data.light_culling_active) {210return visible;211}212return true;213}214215bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade) {216if (!data.is_active() || !is_caster_culling_active()) {217return true;218}219220ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true);221222LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id].planes[p_cascade];223224Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]);225Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]);226AABB bb(mins, maxs - mins);227228real_t r_min, r_max;229for (int p = 0; p < cull_planes.num_cull_planes; p++) {230bb.project_range_in_plane(cull_planes.cull_planes[p], r_min, r_max);231if (r_min > 0.0f) {232#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT233cull_planes.rejected_count++;234#endif235236return false;237}238}239240return true;241}242243void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {244if (!data.is_active() || !is_caster_culling_active()) {245return;246}247248// If the light is out of range, no need to check anything, just return 0 casters.249// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).250if (data.out_of_range) {251return;252}253254// Shorter local alias.255PagedArray<RendererSceneCull::Instance *> &list = r_instance_shadow_cull_result;256257#ifdef LIGHT_CULLER_DEBUG_LOGGING258uint32_t count_before = r_instance_shadow_cull_result.size();259#endif260261// Go through all the casters in the list (the list will hopefully shrink as we go).262for (int n = 0; n < (int)list.size(); n++) {263// World space aabb.264const AABB &bb = list[n]->transformed_aabb;265266#ifdef LIGHT_CULLER_DEBUG_LOGGING267if (is_logging()) {268print_line("bb : " + String(bb));269}270#endif271272real_t r_min, r_max;273bool show = true;274275for (int p = 0; p < data.regular_cull_planes.num_cull_planes; p++) {276// As we only need r_min, could this be optimized?277bb.project_range_in_plane(data.regular_cull_planes.cull_planes[p], r_min, r_max);278279#ifdef LIGHT_CULLER_DEBUG_LOGGING280if (is_logging()) {281print_line("\tplane " + itos(p) + " : " + String(data.regular_cull_planes.cull_planes[p]) + " r_min " + String(Variant(r_min)) + " r_max " + String(Variant(r_max)));282}283#endif284285if (r_min > 0.0f) {286show = false;287break;288}289}290291// Remove.292if (!show) {293list.remove_at_unordered(n);294295// Repeat this element next iteration of the loop as it has been removed and replaced by the last.296n--;297298#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT299data.regular_rejected_count++;300#endif301}302}303304#ifdef LIGHT_CULLER_DEBUG_LOGGING305uint32_t removed = r_instance_shadow_cull_result.size() - count_before;306if (removed) {307if (((data.debug_count) % 60) == 0) {308print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(count_before) + " after " + itos(r_instance_shadow_cull_result.size()));309}310}311#endif312}313314void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) {315ERR_FAIL_COND(num_cull_planes >= MAX_CULL_PLANES);316cull_planes[num_cull_planes++] = p;317}318319// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third320// points are derived differently.321bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {322uint32_t lookup = 0;323r_cull_planes.num_cull_planes = 0;324325const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;326327// Directional light, we will use dot against the light direction to determine back facing planes.328for (int n = 0; n < 6; n++) {329float dot = cull_frustum_planes[n].normal.dot(p_light_source.dir);330if (dot > 0.0f) {331lookup |= 1 << n;332333// Add backfacing camera frustum planes.334r_cull_planes.add_cull_plane(cull_frustum_planes[n]);335}336}337338ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);339340// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)341// then we will add the camera frustum planes to clip the light volume .. there is no need to342// render shadow casters outside the frustum as shadows can never re-enter the frustum.343344// Should never happen with directional light?? This may be able to be removed.345if (lookup == 63) {346r_cull_planes.num_cull_planes = 0;347for (int n = 0; n < 6; n++) {348r_cull_planes.add_cull_plane(cull_frustum_planes[n]);349}350351return true;352}353354// Each edge forms a plane.355#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT356const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];357358// each edge forms a plane359int n_edges = entry.size() - 1;360#else361uint8_t *entry = &data.LUT_entries[lookup][0];362int n_edges = data.LUT_entry_sizes[lookup] - 1;363#endif364365const Vector3 *const frustum_points = p_cull_frustum.frustum_points;366367for (int e = 0; e < n_edges; e++) {368int i0 = entry[e];369int i1 = entry[e + 1];370371const Vector3 &pt0 = frustum_points[i0];372const Vector3 &pt1 = frustum_points[i1];373374// Create a third point from the light direction.375Vector3 pt2 = pt0 - p_light_source.dir;376377if (!_is_colinear_tri(pt0, pt1, pt2)) {378// Create plane from 3 points.379Plane p(pt0, pt1, pt2);380r_cull_planes.add_cull_plane(p);381}382}383384// Last to 0 edge.385if (n_edges) {386int i0 = entry[n_edges]; // Last.387int i1 = entry[0]; // First.388389const Vector3 &pt0 = frustum_points[i0];390const Vector3 &pt1 = frustum_points[i1];391392// Create a third point from the light direction.393Vector3 pt2 = pt0 - p_light_source.dir;394395if (!_is_colinear_tri(pt0, pt1, pt2)) {396// Create plane from 3 points.397Plane p(pt0, pt1, pt2);398r_cull_planes.add_cull_plane(p);399}400}401402#ifdef LIGHT_CULLER_DEBUG_LOGGING403if (is_logging()) {404print_line("lcam.pos is " + String(p_light_source.pos));405}406#endif407408return true;409}410411bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {412if (!data.is_active()) {413return true;414}415416const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;417418switch (p_light_source.type) {419case LightSource::ST_SPOTLIGHT:420case LightSource::ST_OMNI:421break;422case LightSource::ST_DIRECTIONAL:423return add_light_camera_planes_directional(r_cull_planes, p_light_source, p_cull_frustum);424break;425default:426return false; // not yet supported427break;428}429430// Start with 0 cull planes.431r_cull_planes.num_cull_planes = 0;432data.out_of_range = false;433uint32_t lookup = 0;434435// Find which of the camera planes are facing away from the light.436// We can also test for the situation where the light max range means it cannot437// affect the camera frustum. This is absolutely worth doing because it is relatively438// cheap, and if the entire light can be culled this can vastly improve performance439// (much more than just culling casters).440441// POINT LIGHT (spotlight, omni)442// Instead of using dot product to compare light direction to plane, we can simply443// find out which side of the plane the camera is on. By definition this marks the point at which the plane444// becomes invisible.445446// OMNIS447if (p_light_source.type == LightSource::ST_OMNI) {448for (int n = 0; n < 6; n++) {449float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);450if (dist < 0.0f) {451lookup |= 1 << n;452453// Add backfacing camera frustum planes.454r_cull_planes.add_cull_plane(cull_frustum_planes[n]);455} else {456// Is the light out of range?457// This is one of the tests. If the point source is more than range distance from a frustum plane, it can't458// be seen.459if (dist >= p_light_source.range) {460// If the light is out of range, no need to do anything else, everything will be culled.461data.out_of_range = true;462return false;463}464}465}466} else {467// SPOTLIGHTs, more complex to cull.468Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);469470// This is the radius of the cone at distance 1.471float radius_at_dist_one = Math::tan(Math::deg_to_rad(p_light_source.angle));472473// The worst case radius of the cone at the end point can be calculated474// (the radius will scale linearly with length along the cone).475float end_cone_radius = radius_at_dist_one * p_light_source.range;476477for (int n = 0; n < 6; n++) {478float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);479if (dist < 0.0f) {480// Either the plane is backfacing or we are inside the frustum.481lookup |= 1 << n;482483// Add backfacing camera frustum planes.484r_cull_planes.add_cull_plane(cull_frustum_planes[n]);485} else {486// The light is in front of the plane.487488// Is the light out of range?489if (dist >= p_light_source.range) {490data.out_of_range = true;491return false;492}493494// For a spotlight, we can use an extra test495// at this point the cone start is in front of the plane...496// If the cone end point is further than the maximum possible distance to the plane497// we can guarantee that the cone does not cross the plane, and hence the cone498// is outside the frustum.499float dist_end = cull_frustum_planes[n].distance_to(pos_end);500501if (dist_end >= end_cone_radius) {502data.out_of_range = true;503return false;504}505}506}507}508509// The lookup should be within the LUT, logic should prevent this.510ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);511512// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)513// then we will add the camera frustum planes to clip the light volume .. there is no need to514// render shadow casters outside the frustum as shadows can never re-enter the frustum.515if (lookup == 63) {516r_cull_planes.num_cull_planes = 0;517for (int n = 0; n < 6; n++) {518r_cull_planes.add_cull_plane(cull_frustum_planes[n]);519}520521return true;522}523524// Each edge forms a plane.525uint8_t *entry = &data.LUT_entries[lookup][0];526int n_edges = data.LUT_entry_sizes[lookup] - 1;527528const Vector3 *const frustum_points = p_cull_frustum.frustum_points;529530const Vector3 &pt2 = p_light_source.pos;531532for (int e = 0; e < n_edges; e++) {533int i0 = entry[e];534int i1 = entry[e + 1];535const Vector3 &pt0 = frustum_points[i0];536const Vector3 &pt1 = frustum_points[i1];537538if (!_is_colinear_tri(pt0, pt1, pt2)) {539// Create plane from 3 points.540Plane p(pt0, pt1, pt2);541r_cull_planes.add_cull_plane(p);542}543}544545// Last to 0 edge.546if (n_edges) {547int i0 = entry[n_edges]; // Last.548int i1 = entry[0]; // First.549550const Vector3 &pt0 = frustum_points[i0];551const Vector3 &pt1 = frustum_points[i1];552553if (!_is_colinear_tri(pt0, pt1, pt2)) {554// Create plane from 3 points.555Plane p(pt0, pt1, pt2);556r_cull_planes.add_cull_plane(p);557}558}559560#ifdef LIGHT_CULLER_DEBUG_LOGGING561if (is_logging()) {562print_line("lsource.pos is " + String(p_light_source.pos));563}564#endif565566return true;567}568569bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {570data.debug_count++;571if (data.debug_count >= 120) {572data.debug_count = 0;573}574575// For debug flash off and on.576#ifdef LIGHT_CULLER_DEBUG_FLASH577if (!Engine::get_singleton()->is_editor_hint()) {578int dc = Engine::get_singleton()->get_process_frames() / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;579bool bnew_active;580bnew_active = (dc % 2) == 0;581582if (bnew_active != data.light_culling_active) {583data.light_culling_active = bnew_active;584print_line("switching light culler " + String(Variant(data.light_culling_active)));585}586}587#endif588589if (!data.is_active()) {590return false;591}592// These are needed later to build per-cascade cull frustums for directional lights.593data.camera_transform = p_cam_transform;594data.camera_projection = p_cam_matrix;595596// Get the camera frustum planes in world space.597data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);598DEV_CHECK_ONCE(data.frustum_planes.size() == 6);599600data.regular_cull_planes.num_cull_planes = 0;601602#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT603if (is_logging()) {604for (uint32_t n = 0; n < data.directional_cull_planes.size(); n++) {605print_line("LightCuller directional light " + itos(n) + " rejected " + itos(data.directional_cull_planes[n].rejected_count) + " instances.");606}607}608#endif609#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT610if (data.regular_rejected_count) {611print_line("LightCuller regular lights rejected " + itos(data.regular_rejected_count) + " instances.");612}613data.regular_rejected_count = 0;614#endif615616data.directional_cull_planes.resize(0);617618#ifdef LIGHT_CULLER_DEBUG_LOGGING619if (is_logging()) {620for (int p = 0; p < 6; p++) {621print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));622}623}624#endif625626return create_frustum_points(&data.frustum_planes[0], data.frustum_points);627}628629bool RenderingLightCuller::create_frustum_points(const Plane *p_frustum_planes, Vector3 *r_result) const {630// We want to calculate the frustum corners in a specific order.631const Projection::Planes intersections[8][3] = {632{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },633{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },634{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },635{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },636{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },637{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },638{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },639{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },640};641642for (int i = 0; i < 8; i++) {643// 3 plane intersection, gives us a point.644bool res = p_frustum_planes[intersections[i][0]].intersect_3(p_frustum_planes[intersections[i][1]], p_frustum_planes[intersections[i][2]], &r_result[i]);645646// What happens with a zero frustum? NYI - deal with this.647ERR_FAIL_COND_V(!res, false);648649#ifdef LIGHT_CULLER_DEBUG_LOGGING650if (is_logging()) {651print_line("point " + itos(i) + " -> " + String(result[i]));652}653#endif654}655return true;656}657658RenderingLightCuller::RenderingLightCuller() {659// Used to determine which frame to give debug output.660data.debug_count = -1;661662// Uncomment below to switch off light culler in the editor.663// data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;664665#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT666create_LUT();667#endif668}669670/* clang-format off */671uint8_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, };672673// The lookup table used to determine which edges form the silhouette of the camera frustum,674// depending on the viewing angle (defined by which camera planes are backward facing).675uint8_t RenderingLightCuller::Data::LUT_entries[LUT_SIZE][8] = {676{0, 0, 0, 0, 0, 0, 0, 0, },677{7, 6, 4, 5, 0, 0, 0, 0, },678{1, 0, 2, 3, 0, 0, 0, 0, },679{0, 0, 0, 0, 0, 0, 0, 0, },680{1, 5, 4, 0, 0, 0, 0, 0, },681{1, 5, 7, 6, 4, 0, 0, 0, },682{4, 0, 2, 3, 1, 5, 0, 0, },683{5, 7, 6, 4, 0, 2, 3, 1, },684{0, 4, 6, 2, 0, 0, 0, 0, },685{0, 4, 5, 7, 6, 2, 0, 0, },686{6, 2, 3, 1, 0, 4, 0, 0, },687{2, 3, 1, 0, 4, 5, 7, 6, },688{0, 1, 5, 4, 6, 2, 0, 0, },689{0, 1, 5, 7, 6, 2, 0, 0, },690{6, 2, 3, 1, 5, 4, 0, 0, },691{2, 3, 1, 5, 7, 6, 0, 0, },692{2, 6, 7, 3, 0, 0, 0, 0, },693{2, 6, 4, 5, 7, 3, 0, 0, },694{7, 3, 1, 0, 2, 6, 0, 0, },695{3, 1, 0, 2, 6, 4, 5, 7, },696{0, 0, 0, 0, 0, 0, 0, 0, },697{2, 6, 4, 0, 1, 5, 7, 3, },698{7, 3, 1, 5, 4, 0, 2, 6, },699{0, 0, 0, 0, 0, 0, 0, 0, },700{2, 0, 4, 6, 7, 3, 0, 0, },701{2, 0, 4, 5, 7, 3, 0, 0, },702{7, 3, 1, 0, 4, 6, 0, 0, },703{3, 1, 0, 4, 5, 7, 0, 0, },704{2, 0, 1, 5, 4, 6, 7, 3, },705{2, 0, 1, 5, 7, 3, 0, 0, },706{7, 3, 1, 5, 4, 6, 0, 0, },707{3, 1, 5, 7, 0, 0, 0, 0, },708{3, 7, 5, 1, 0, 0, 0, 0, },709{3, 7, 6, 4, 5, 1, 0, 0, },710{5, 1, 0, 2, 3, 7, 0, 0, },711{7, 6, 4, 5, 1, 0, 2, 3, },712{3, 7, 5, 4, 0, 1, 0, 0, },713{3, 7, 6, 4, 0, 1, 0, 0, },714{5, 4, 0, 2, 3, 7, 0, 0, },715{7, 6, 4, 0, 2, 3, 0, 0, },716{0, 0, 0, 0, 0, 0, 0, 0, },717{3, 7, 6, 2, 0, 4, 5, 1, },718{5, 1, 0, 4, 6, 2, 3, 7, },719{0, 0, 0, 0, 0, 0, 0, 0, },720{3, 7, 5, 4, 6, 2, 0, 1, },721{3, 7, 6, 2, 0, 1, 0, 0, },722{5, 4, 6, 2, 3, 7, 0, 0, },723{7, 6, 2, 3, 0, 0, 0, 0, },724{3, 2, 6, 7, 5, 1, 0, 0, },725{3, 2, 6, 4, 5, 1, 0, 0, },726{5, 1, 0, 2, 6, 7, 0, 0, },727{1, 0, 2, 6, 4, 5, 0, 0, },728{3, 2, 6, 7, 5, 4, 0, 1, },729{3, 2, 6, 4, 0, 1, 0, 0, },730{5, 4, 0, 2, 6, 7, 0, 0, },731{6, 4, 0, 2, 0, 0, 0, 0, },732{3, 2, 0, 4, 6, 7, 5, 1, },733{3, 2, 0, 4, 5, 1, 0, 0, },734{5, 1, 0, 4, 6, 7, 0, 0, },735{1, 0, 4, 5, 0, 0, 0, 0, },736{0, 0, 0, 0, 0, 0, 0, 0, },737{3, 2, 0, 1, 0, 0, 0, 0, },738{5, 4, 6, 7, 0, 0, 0, 0, },739{0, 0, 0, 0, 0, 0, 0, 0, },740};741742/* clang-format on */743744#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT745746// See e.g. http://lspiroengine.com/?p=153 for reference.747// Principles are the same, but differences to the article:748// * Order of planes / points is different in Godot.749// * We use a lookup table at runtime.750void RenderingLightCuller::create_LUT() {751// Each pair of planes that are opposite can have an edge.752for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {753// For each neighbor of the plane.754PlaneOrder neighs[4];755get_neighbouring_planes((PlaneOrder)plane_0, neighs);756757for (int n = 0; n < 4; n++) {758int plane_1 = neighs[n];759760// If these are opposite we need to add the 2 points they share.761PointOrder pts[2];762get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);763764add_LUT(plane_0, plane_1, pts);765}766}767768for (uint32_t n = 0; n < LUT_SIZE; n++) {769compact_LUT_entry(n);770}771772debug_print_LUT();773debug_print_LUT_as_table();774}775776// we can pre-create the entire LUT and store it hard coded as a static inside the executable!777// it is only small in size, 64 entries with max 8 bytes per entry778void RenderingLightCuller::debug_print_LUT_as_table() {779print_line("\nLIGHT VOLUME TABLE BEGIN\n");780781print_line("Copy this to LUT_entry_sizes:\n");782String sz = "{";783for (int n = 0; n < LUT_SIZE; n++) {784const LocalVector<uint8_t> &entry = _calculated_LUT[n];785786sz += itos(entry.size()) + ", ";787}788sz += "}";789print_line(sz);790print_line("\nCopy this to LUT_entries:\n");791792for (int n = 0; n < LUT_SIZE; n++) {793const LocalVector<uint8_t> &entry = _calculated_LUT[n];794795String sz = "{";796797// First is the number of points in the entry.798int s = entry.size();799800for (int p = 0; p < 8; p++) {801if (p < s) {802sz += itos(entry[p]);803} else {804sz += "0"; // just a spacer805}806807sz += ", ";808}809810sz += "},";811print_line(sz);812}813814print_line("\nLIGHT VOLUME TABLE END\n");815}816817void RenderingLightCuller::debug_print_LUT() {818for (int n = 0; n < LUT_SIZE; n++) {819String sz;820sz = "LUT" + itos(n) + ":\t";821822sz += Data::plane_bitfield_to_string(n);823print_line(sz);824825const LocalVector<uint8_t> &entry = _calculated_LUT[n];826827sz = "\t" + string_LUT_entry(entry);828829print_line(sz);830}831}832833String RenderingLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {834String string;835836for (uint32_t n = 0; n < p_entry.size(); n++) {837uint8_t val = p_entry[n];838DEV_ASSERT(val < 8);839const char *sz_point = Data::string_points[val];840string += sz_point;841string += ", ";842}843844return string;845}846847String RenderingLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {848String string;849850for (uint32_t i = 0; i < p_entry.size(); i++) {851int pt_order = p_entry[i];852if (p_pair && ((i % 2) == 0)) {853string += itos(pt_order) + "-";854} else {855string += itos(pt_order) + ", ";856}857}858859return string;860}861862void RenderingLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {863// Note that some entries to the LUT will be "impossible" situations,864// because it contains all combinations of plane flips.865uint32_t bit0 = 1 << p_plane_0;866uint32_t bit1 = 1 << p_plane_1;867868// All entries of the LUT that have plane 0 set and plane 1 not set.869for (uint32_t n = 0; n < 64; n++) {870// If bit0 not set...871if (!(n & bit0)) {872continue;873}874875// If bit1 set...876if (n & bit1) {877continue;878}879880// Meets criteria.881add_LUT_entry(n, p_pts);882}883}884885void RenderingLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {886DEV_ASSERT(p_entry_id < LUT_SIZE);887LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];888889entry.push_back(p_pts[0]);890entry.push_back(p_pts[1]);891}892893void RenderingLightCuller::compact_LUT_entry(uint32_t p_entry_id) {894DEV_ASSERT(p_entry_id < LUT_SIZE);895LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];896897int num_pairs = entry.size() / 2;898899if (num_pairs == 0) {900return;901}902903LocalVector<uint8_t> temp;904905String string;906string = "Compact LUT" + itos(p_entry_id) + ":\t";907string += debug_string_LUT_entry(entry, true);908print_line(string);909910// Add first pair.911temp.push_back(entry[0]);912temp.push_back(entry[1]);913unsigned int BFpairs = 1;914915string = debug_string_LUT_entry(temp) + " -> ";916print_line(string);917918// Attempt to add a pair each time.919for (int done = 1; done < num_pairs; done++) {920string = "done " + itos(done) + ": ";921// Find a free pair.922for (int p = 1; p < num_pairs; p++) {923unsigned int bit = 1 << p;924// Is it done already?925if (BFpairs & bit) {926continue;927}928929// There must be at least 1 free pair.930// Attempt to add.931int a = entry[p * 2];932int b = entry[(p * 2) + 1];933934string += "[" + itos(a) + "-" + itos(b) + "], ";935936int found_a = temp.find(a);937int found_b = temp.find(b);938939// Special case, if they are both already in the list, no need to add940// as this is a link from the tail to the head of the list.941if ((found_a != -1) && (found_b != -1)) {942string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";943BFpairs |= bit;944goto found;945}946947// Find a.948if (found_a != -1) {949string += "foundA " + itos(found_a) + " ";950temp.insert(found_a + 1, b);951BFpairs |= bit;952goto found;953}954955// Find b.956if (found_b != -1) {957string += "foundB " + itos(found_b) + " ";958temp.insert(found_b, a);959BFpairs |= bit;960goto found;961}962963} // Check each pair for adding.964965// If we got here before finding a link, the whole set of planes is INVALID966// e.g. far and near plane only, does not create continuous sillouhette of edges.967print_line("\tINVALID");968entry.clear();969return;970971found:;972print_line(string);973string = "\ttemp now : " + debug_string_LUT_entry(temp);974print_line(string);975}976977// temp should now be the sorted entry .. delete the old one and replace by temp.978entry.clear();979entry = temp;980}981982void RenderingLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {983// Table of neighboring planes to each.984static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {985{ // LSM_FP_NEAR986PLANE_LEFT,987PLANE_RIGHT,988PLANE_TOP,989PLANE_BOTTOM },990{ // LSM_FP_FAR991PLANE_LEFT,992PLANE_RIGHT,993PLANE_TOP,994PLANE_BOTTOM },995{ // LSM_FP_LEFT996PLANE_TOP,997PLANE_BOTTOM,998PLANE_NEAR,999PLANE_FAR },1000{ // LSM_FP_TOP1001PLANE_LEFT,1002PLANE_RIGHT,1003PLANE_NEAR,1004PLANE_FAR },1005{ // LSM_FP_RIGHT1006PLANE_TOP,1007PLANE_BOTTOM,1008PLANE_NEAR,1009PLANE_FAR },1010{ // LSM_FP_BOTTOM1011PLANE_LEFT,1012PLANE_RIGHT,1013PLANE_NEAR,1014PLANE_FAR },1015};10161017for (int n = 0; n < 4; n++) {1018r_neigh_planes[n] = neigh_table[p_plane][n];1019}1020}10211022// Given two planes, returns the two points shared by those planes. The points are always1023// returned in counter-clockwise order, assuming the first input plane is facing towards1024// the viewer.10251026// param p_plane_a The plane facing towards the viewer.1027// param p_plane_b A plane neighboring p_plane_a.1028// param r_points An array of exactly two elements to be filled with the indices of the points1029// on return.10301031void RenderingLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {1032static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {1033{1034// LSM_FP_NEAR1035{1036// LSM_FP_NEAR1037PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP // Invalid combination.1038},1039{1040// LSM_FP_FAR1041PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.1042},1043{1044// LSM_FP_LEFT1045PT_NEAR_LEFT_TOP,1046PT_NEAR_LEFT_BOTTOM,1047},1048{1049// LSM_FP_TOP1050PT_NEAR_RIGHT_TOP,1051PT_NEAR_LEFT_TOP,1052},1053{1054// LSM_FP_RIGHT1055PT_NEAR_RIGHT_BOTTOM,1056PT_NEAR_RIGHT_TOP,1057},1058{1059// LSM_FP_BOTTOM1060PT_NEAR_LEFT_BOTTOM,1061PT_NEAR_RIGHT_BOTTOM,1062},1063},10641065{1066// LSM_FP_FAR1067{1068// LSM_FP_NEAR1069PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP // Invalid combination.1070},1071{1072// LSM_FP_FAR1073PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.1074},1075{1076// LSM_FP_LEFT1077PT_FAR_LEFT_BOTTOM,1078PT_FAR_LEFT_TOP,1079},1080{1081// LSM_FP_TOP1082PT_FAR_LEFT_TOP,1083PT_FAR_RIGHT_TOP,1084},1085{1086// LSM_FP_RIGHT1087PT_FAR_RIGHT_TOP,1088PT_FAR_RIGHT_BOTTOM,1089},1090{1091// LSM_FP_BOTTOM1092PT_FAR_RIGHT_BOTTOM,1093PT_FAR_LEFT_BOTTOM,1094},1095},10961097{1098// LSM_FP_LEFT1099{1100// LSM_FP_NEAR1101PT_NEAR_LEFT_BOTTOM,1102PT_NEAR_LEFT_TOP,1103},1104{1105// LSM_FP_FAR1106PT_FAR_LEFT_TOP,1107PT_FAR_LEFT_BOTTOM,1108},1109{1110// LSM_FP_LEFT1111PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.1112},1113{1114// LSM_FP_TOP1115PT_NEAR_LEFT_TOP,1116PT_FAR_LEFT_TOP,1117},1118{1119// LSM_FP_RIGHT1120PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.1121},1122{1123// LSM_FP_BOTTOM1124PT_FAR_LEFT_BOTTOM,1125PT_NEAR_LEFT_BOTTOM,1126},1127},11281129{1130// LSM_FP_TOP1131{1132// LSM_FP_NEAR1133PT_NEAR_LEFT_TOP,1134PT_NEAR_RIGHT_TOP,1135},1136{1137// LSM_FP_FAR1138PT_FAR_RIGHT_TOP,1139PT_FAR_LEFT_TOP,1140},1141{1142// LSM_FP_LEFT1143PT_FAR_LEFT_TOP,1144PT_NEAR_LEFT_TOP,1145},1146{1147// LSM_FP_TOP1148PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP // Invalid combination.1149},1150{1151// LSM_FP_RIGHT1152PT_NEAR_RIGHT_TOP,1153PT_FAR_RIGHT_TOP,1154},1155{1156// LSM_FP_BOTTOM1157PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.1158},1159},11601161{1162// LSM_FP_RIGHT1163{1164// LSM_FP_NEAR1165PT_NEAR_RIGHT_TOP,1166PT_NEAR_RIGHT_BOTTOM,1167},1168{1169// LSM_FP_FAR1170PT_FAR_RIGHT_BOTTOM,1171PT_FAR_RIGHT_TOP,1172},1173{1174// LSM_FP_LEFT1175PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.1176},1177{1178// LSM_FP_TOP1179PT_FAR_RIGHT_TOP,1180PT_NEAR_RIGHT_TOP,1181},1182{1183// LSM_FP_RIGHT1184PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.1185},1186{1187// LSM_FP_BOTTOM1188PT_NEAR_RIGHT_BOTTOM,1189PT_FAR_RIGHT_BOTTOM,1190},1191},11921193// ==11941195// P_NEAR,1196// P_FAR,1197// P_LEFT,1198// P_TOP,1199// P_RIGHT,1200// P_BOTTOM,12011202{1203// LSM_FP_BOTTOM1204{1205// LSM_FP_NEAR1206PT_NEAR_RIGHT_BOTTOM,1207PT_NEAR_LEFT_BOTTOM,1208},1209{1210// LSM_FP_FAR1211PT_FAR_LEFT_BOTTOM,1212PT_FAR_RIGHT_BOTTOM,1213},1214{1215// LSM_FP_LEFT1216PT_NEAR_LEFT_BOTTOM,1217PT_FAR_LEFT_BOTTOM,1218},1219{1220// LSM_FP_TOP1221PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.1222},1223{1224// LSM_FP_RIGHT1225PT_FAR_RIGHT_BOTTOM,1226PT_NEAR_RIGHT_BOTTOM,1227},1228{1229// LSM_FP_BOTTOM1230PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.1231},1232},12331234// ==12351236};1237r_points[0] = fp_table[p_plane_a][p_plane_b][0];1238r_points[1] = fp_table[p_plane_a][p_plane_b][1];1239}12401241#endif124212431244