Browse Source

display: don't render shadow cameras whose frusta are out of view

This is done by collecting bounding volumes for each camera in a scene, and then only rendering a shadow camera if at least one of those bounding volumes overlaps with the shadow camera bounding volume.

Closes #560
rdb 6 years ago
parent
commit
d11d4e0d85

+ 86 - 27
panda/src/display/graphicsEngine.cxx

@@ -48,6 +48,7 @@
 #include "displayRegionDrawCallbackData.h"
 #include "displayRegionDrawCallbackData.h"
 #include "callbackGraphicsWindow.h"
 #include "callbackGraphicsWindow.h"
 #include "depthTestAttrib.h"
 #include "depthTestAttrib.h"
+#include "unionBoundingVolume.h"
 
 
 #if defined(WIN32)
 #if defined(WIN32)
   #define WINDOWS_LEAN_AND_MEAN
   #define WINDOWS_LEAN_AND_MEAN
@@ -1308,32 +1309,6 @@ do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
   CullTraverser *trav = dr->get_cull_traverser();
   CullTraverser *trav = dr->get_cull_traverser();
   trav->set_cull_handler(cull_handler);
   trav->set_cull_handler(cull_handler);
   trav->set_scene(scene_setup, gsg, dr->get_incomplete_render());
   trav->set_scene(scene_setup, gsg, dr->get_incomplete_render());
-
-  trav->set_view_frustum(nullptr);
-  if (view_frustum_cull) {
-    // If we're to be performing view-frustum culling, determine the bounding
-    // volume associated with the current viewing frustum.
-
-    // First, we have to get the current viewing frustum, which comes from the
-    // lens.
-    PT(BoundingVolume) bv = scene_setup->get_cull_bounds();
-
-    if (bv != nullptr && !bv->is_infinite() &&
-        bv->as_geometric_bounding_volume() != nullptr) {
-      // Transform it into the appropriate coordinate space.
-      PT(GeometricBoundingVolume) local_frustum;
-      local_frustum = bv->make_copy()->as_geometric_bounding_volume();
-      nassertv(!local_frustum.is_null());
-
-      NodePath scene_parent = scene_setup->get_scene_root().get_parent(current_thread);
-      CPT(TransformState) cull_center_transform =
-        scene_setup->get_cull_center().get_transform(scene_parent, current_thread);
-      local_frustum->xform(cull_center_transform->get_mat());
-
-      trav->set_view_frustum(local_frustum);
-    }
-  }
-
   trav->traverse(scene_setup->get_scene_root());
   trav->traverse(scene_setup->get_scene_root());
   trav->end_traverse();
   trav->end_traverse();
 }
 }
@@ -1545,6 +1520,11 @@ cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) {
   typedef pmap<CullKey, DisplayRegion *> AlreadyCulled;
   typedef pmap<CullKey, DisplayRegion *> AlreadyCulled;
   AlreadyCulled already_culled;
   AlreadyCulled already_culled;
 
 
+  // We cull shadow passes last; whether we cull them depends on whether their
+  // respective frusta are in view of a "normal" camera.
+  pvector<PT(SceneSetup)> shadow_passes;
+  pmap<NodePath, UnionBoundingVolume> non_shadow_bounds;
+
   size_t wlist_size = wlist.size();
   size_t wlist_size = wlist.size();
   for (size_t wi = 0; wi < wlist_size; ++wi) {
   for (size_t wi = 0; wi < wlist_size; ++wi) {
     GraphicsOutput *win = wlist[wi];
     GraphicsOutput *win = wlist[wi];
@@ -1571,6 +1551,27 @@ cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) {
             key._lens_index = dr_reader.get_lens_index();
             key._lens_index = dr_reader.get_lens_index();
           }
           }
 
 
+          // If this is a shadow pass, postpone culling it until we've culled
+          // all the other passes, and collected their bounding volumes.
+          Light *light = nullptr;
+          if (!key._camera.is_empty()) {
+            light = key._camera.node()->as_light();
+          }
+          if (light != nullptr) {
+            shadow_passes.push_back(std::move(scene_setup));
+            continue;
+          }
+          else if (!shadow_passes.empty()) {
+            NodePath scene_root = scene_setup->get_scene_root();
+            const GeometricBoundingVolume *gbv = scene_setup->get_view_frustum();
+            UnionBoundingVolume &bounds = non_shadow_bounds[scene_root];
+            if (gbv == nullptr || gbv->is_infinite()) {
+              bounds.set_infinite();
+            } else {
+              bounds.add_component(gbv);
+            }
+          }
+
           AlreadyCulled::iterator aci = already_culled.insert(AlreadyCulled::value_type(std::move(key), nullptr)).first;
           AlreadyCulled::iterator aci = already_culled.insert(AlreadyCulled::value_type(std::move(key), nullptr)).first;
           if ((*aci).second == nullptr) {
           if ((*aci).second == nullptr) {
             // We have not used this camera already in this thread.  Perform
             // We have not used this camera already in this thread.  Perform
@@ -1597,11 +1598,46 @@ cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) {
           }
           }
 
 
           // Save the results for next frame.
           // Save the results for next frame.
-          dr->set_cull_result(std::move(cull_result), MOVE(scene_setup), current_thread);
+          dr->set_cull_result(std::move(cull_result), std::move(scene_setup), current_thread);
         }
         }
       }
       }
     }
     }
   }
   }
+
+  // Now cull all the shadow passes if their cull bounds are in view.
+  // We don't bother checking the AlreadyCulled list, because we know there is
+  // only one output per GSG+light combination.
+  for (PT(SceneSetup) &scene_setup : shadow_passes) {
+    DisplayRegion *dr = scene_setup->get_display_region();
+    GraphicsOutput *win = dr->get_window();
+    PStatTimer timer(win->get_cull_window_pcollector(), current_thread);
+
+    PT(CullResult) cull_result;
+
+    // Are the cull bounds in view of another camera?
+    GeometricBoundingVolume *frustum = scene_setup->get_view_frustum();
+    if (frustum == nullptr ||
+        non_shadow_bounds[scene_setup->get_scene_root()].contains(frustum)) {
+      GraphicsStateGuardian *gsg = win->get_gsg();
+      cull_result = dr->get_cull_result(current_thread);
+      if (cull_result != nullptr) {
+        cull_result = cull_result->make_next();
+      } else {
+        // This DisplayRegion has no cull results; draw it.
+        cull_result = new CullResult(gsg, dr->get_draw_region_pcollector());
+      }
+      cull_to_bins(win, gsg, dr, scene_setup, cull_result, current_thread);
+    }
+    else if (display_cat.is_spam()) {
+      display_cat.spam()
+        << *scene_setup->get_camera_node()
+        << " frustum is not in view, skipping shadow pass\n";
+    }
+
+    // Even save the results if null, to tell the draw pass that we don't want
+    // to draw this at all.
+    dr->set_cull_result(std::move(cull_result), std::move(scene_setup), current_thread);
+  }
 }
 }
 
 
 /**
 /**
@@ -2008,6 +2044,29 @@ setup_scene(GraphicsStateGuardian *gsg, DisplayRegionPipelineReader *dr) {
   CPT(TransformState) cs_world_transform = cs_transform->compose(world_transform);
   CPT(TransformState) cs_world_transform = cs_transform->compose(world_transform);
   scene_setup->set_cs_world_transform(cs_world_transform);
   scene_setup->set_cs_world_transform(cs_world_transform);
 
 
+  if (view_frustum_cull) {
+    // If we're to be performing view-frustum culling, determine the bounding
+    // volume associated with the current viewing frustum.
+
+    // First, we have to get the current viewing frustum, which comes from the
+    // lens.
+    PT(BoundingVolume) bv = scene_setup->get_cull_bounds();
+
+    if (bv != nullptr && !bv->is_infinite() &&
+        bv->as_geometric_bounding_volume() != nullptr) {
+      // Transform it into the appropriate coordinate space.
+      PT(GeometricBoundingVolume) local_frustum;
+      local_frustum = bv->make_copy()->as_geometric_bounding_volume();
+      nassertr(!local_frustum.is_null(), nullptr);
+
+      CPT(TransformState) cull_center_transform =
+        scene_setup->get_cull_center().get_transform(scene_parent, current_thread);
+      local_frustum->xform(cull_center_transform->get_mat());
+
+      scene_setup->set_view_frustum(local_frustum);
+    }
+  }
+
   return scene_setup;
   return scene_setup;
 }
 }
 
 

+ 2 - 0
panda/src/pgraph/cullTraverser.I

@@ -116,6 +116,8 @@ get_camera_mask() const {
 /**
 /**
  * Specifies the bounding volume that corresponds to the view frustum.  Any
  * Specifies the bounding volume that corresponds to the view frustum.  Any
  * primitives that fall entirely outside of this volume are not drawn.
  * primitives that fall entirely outside of this volume are not drawn.
+ *
+ * Nowadays, this gets set automatically by set_scene().
  */
  */
 INLINE void CullTraverser::
 INLINE void CullTraverser::
 set_view_frustum(GeometricBoundingVolume *view_frustum) {
 set_view_frustum(GeometricBoundingVolume *view_frustum) {

+ 2 - 0
panda/src/pgraph/cullTraverser.cxx

@@ -97,6 +97,8 @@ set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsg,
   _camera_mask = camera->get_camera_mask();
   _camera_mask = camera->get_camera_mask();
 
 
   _effective_incomplete_render = _gsg->get_incomplete_render() && dr_incomplete_render;
   _effective_incomplete_render = _gsg->get_incomplete_render() && dr_incomplete_render;
+
+  _view_frustum = scene_setup->get_view_frustum();
 }
 }
 
 
 /**
 /**

+ 17 - 0
panda/src/pgraph/sceneSetup.I

@@ -182,6 +182,23 @@ get_cull_bounds() const {
   return _lens->make_bounds();
   return _lens->make_bounds();
 }
 }
 
 
+/**
+ * Returns the camera's cull bounds transformed to world space, or null if
+ * view frustum culling is disabled.
+ */
+INLINE void SceneSetup::
+set_view_frustum(PT(GeometricBoundingVolume) view_frustum) {
+  _view_frustum = std::move(view_frustum);
+}
+
+/**
+ * Returns the initial state as set by a previous call to set_initial_state().
+ */
+INLINE GeometricBoundingVolume *SceneSetup::
+get_view_frustum() const {
+  return _view_frustum;
+}
+
 /**
 /**
  * Sets the initial state which is applied to all nodes in the scene, as if it
  * Sets the initial state which is applied to all nodes in the scene, as if it
  * were set at the top of the scene graph.
  * were set at the top of the scene graph.

+ 5 - 0
panda/src/pgraph/sceneSetup.h

@@ -22,6 +22,7 @@
 #include "transformState.h"
 #include "transformState.h"
 #include "lens.h"
 #include "lens.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
+#include "geometricBoundingVolume.h"
 
 
 class DisplayRegion;
 class DisplayRegion;
 
 
@@ -59,6 +60,9 @@ PUBLISHED:
   INLINE const NodePath &get_cull_center() const;
   INLINE const NodePath &get_cull_center() const;
   INLINE PT(BoundingVolume) get_cull_bounds() const;
   INLINE PT(BoundingVolume) get_cull_bounds() const;
 
 
+  INLINE void set_view_frustum(PT(GeometricBoundingVolume) view_frustum);
+  INLINE GeometricBoundingVolume *get_view_frustum() const;
+
   INLINE void set_initial_state(const RenderState *initial_state);
   INLINE void set_initial_state(const RenderState *initial_state);
   INLINE const RenderState *get_initial_state() const;
   INLINE const RenderState *get_initial_state() const;
 
 
@@ -88,6 +92,7 @@ private:
   CPT(TransformState) _world_transform;
   CPT(TransformState) _world_transform;
   CPT(TransformState) _cs_transform;
   CPT(TransformState) _cs_transform;
   CPT(TransformState) _cs_world_transform;
   CPT(TransformState) _cs_world_transform;
+  PT(GeometricBoundingVolume) _view_frustum;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {