1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-01 16:38:31 +00:00

Shadow volume culling and tighter shadow caster culling

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)
This commit is contained in:
lawnjelly
2019-11-02 20:00:24 +00:00
parent e236747b31
commit 8ca631a466
6 changed files with 1416 additions and 18 deletions

View File

@@ -43,6 +43,8 @@
#include "portals/portal_renderer.h"
#include "servers/arvr/arvr_interface.h"
class VisualServerLightCuller;
class VisualServerScene {
public:
enum {
@@ -484,16 +486,60 @@ public:
RID instance;
uint64_t last_version;
List<Instance *>::Element *D; // directional light in scenario
bool shadow_dirty;
List<PairInfo> geometries;
Instance *baked_light;
int32_t previous_room_id_hint;
private:
// Instead of a single dirty flag, we maintain a count
// so that we can detect lights that are being made dirty
// each frame, and switch on tighter caster culling.
int32_t shadow_dirty_count;
uint32_t light_update_frame_id;
bool light_intersects_multiple_cameras;
uint32_t light_intersects_multiple_cameras_timeout_frame_id;
public:
bool is_shadow_dirty() const { return shadow_dirty_count != 0; }
void make_shadow_dirty() { shadow_dirty_count = light_intersects_multiple_cameras ? 1 : 2; }
void detect_light_intersects_multiple_cameras(uint32_t p_frame_id) {
// We need to detect the case where shadow updates are occurring
// more than once per frame. In this case, we need to turn off
// tighter caster culling, so situation reverts to one full shadow update
// per frame (light_intersects_multiple_cameras is set).
if (p_frame_id == light_update_frame_id) {
light_intersects_multiple_cameras = true;
light_intersects_multiple_cameras_timeout_frame_id = p_frame_id + 60;
} else {
// When shadow_volume_intersects_multiple_cameras is set, we
// want to detect the situation this is no longer the case, via a timeout.
// The system can go back to tighter caster culling in this situation.
// Having a long-ish timeout prevents rapid cycling.
if (light_intersects_multiple_cameras && (p_frame_id >= light_intersects_multiple_cameras_timeout_frame_id)) {
light_intersects_multiple_cameras = false;
light_intersects_multiple_cameras_timeout_frame_id = UINT32_MAX;
}
}
light_update_frame_id = p_frame_id;
}
void decrement_shadow_dirty() {
shadow_dirty_count--;
DEV_ASSERT(shadow_dirty_count >= 0);
}
// Shadow updates can either full (everything in the shadow volume)
// or closely culled to the camera frustum.
bool is_shadow_update_full() const { return shadow_dirty_count == 0; }
InstanceLightData() {
shadow_dirty = true;
shadow_dirty_count = 1;
light_update_frame_id = UINT32_MAX;
light_intersects_multiple_cameras_timeout_frame_id = UINT32_MAX;
light_intersects_multiple_cameras = false;
D = nullptr;
last_version = 0;
baked_light = nullptr;
@@ -623,6 +669,7 @@ public:
RID light_instance_cull_result[MAX_LIGHTS_CULLED];
int light_cull_count;
int directional_light_count;
VisualServerLightCuller *light_culler;
RID reflection_probe_instance_cull_result[MAX_REFLECTION_PROBES_CULLED];
int reflection_probe_cull_count;