From d11d4e0d85a18597811d18ea33a250d2f7bf6c7f Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 19 Feb 2019 21:59:04 +0100 Subject: [PATCH] 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 --- panda/src/display/graphicsEngine.cxx | 113 ++++++++++++++++++++------- panda/src/pgraph/cullTraverser.I | 2 + panda/src/pgraph/cullTraverser.cxx | 2 + panda/src/pgraph/sceneSetup.I | 17 ++++ panda/src/pgraph/sceneSetup.h | 5 ++ 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 80973ae071..671f12c6aa 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -48,6 +48,7 @@ #include "displayRegionDrawCallbackData.h" #include "callbackGraphicsWindow.h" #include "depthTestAttrib.h" +#include "unionBoundingVolume.h" #if defined(WIN32) #define WINDOWS_LEAN_AND_MEAN @@ -1308,32 +1309,6 @@ do_cull(CullHandler *cull_handler, SceneSetup *scene_setup, CullTraverser *trav = dr->get_cull_traverser(); trav->set_cull_handler(cull_handler); 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->end_traverse(); } @@ -1545,6 +1520,11 @@ cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) { typedef pmap AlreadyCulled; 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 shadow_passes; + pmap non_shadow_bounds; + size_t wlist_size = wlist.size(); for (size_t wi = 0; wi < wlist_size; ++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(); } + // 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; if ((*aci).second == nullptr) { // 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. - 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); 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; } diff --git a/panda/src/pgraph/cullTraverser.I b/panda/src/pgraph/cullTraverser.I index bb7bb35e36..98bc781b50 100644 --- a/panda/src/pgraph/cullTraverser.I +++ b/panda/src/pgraph/cullTraverser.I @@ -116,6 +116,8 @@ get_camera_mask() const { /** * Specifies the bounding volume that corresponds to the view frustum. Any * primitives that fall entirely outside of this volume are not drawn. + * + * Nowadays, this gets set automatically by set_scene(). */ INLINE void CullTraverser:: set_view_frustum(GeometricBoundingVolume *view_frustum) { diff --git a/panda/src/pgraph/cullTraverser.cxx b/panda/src/pgraph/cullTraverser.cxx index 0ecf27666a..ae0b7e8014 100644 --- a/panda/src/pgraph/cullTraverser.cxx +++ b/panda/src/pgraph/cullTraverser.cxx @@ -97,6 +97,8 @@ set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsg, _camera_mask = camera->get_camera_mask(); _effective_incomplete_render = _gsg->get_incomplete_render() && dr_incomplete_render; + + _view_frustum = scene_setup->get_view_frustum(); } /** diff --git a/panda/src/pgraph/sceneSetup.I b/panda/src/pgraph/sceneSetup.I index cf420ee132..a81271cd02 100644 --- a/panda/src/pgraph/sceneSetup.I +++ b/panda/src/pgraph/sceneSetup.I @@ -182,6 +182,23 @@ get_cull_bounds() const { 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 * were set at the top of the scene graph. diff --git a/panda/src/pgraph/sceneSetup.h b/panda/src/pgraph/sceneSetup.h index d2ad0c0edb..b6622eaee7 100644 --- a/panda/src/pgraph/sceneSetup.h +++ b/panda/src/pgraph/sceneSetup.h @@ -22,6 +22,7 @@ #include "transformState.h" #include "lens.h" #include "pointerTo.h" +#include "geometricBoundingVolume.h" class DisplayRegion; @@ -59,6 +60,9 @@ PUBLISHED: INLINE const NodePath &get_cull_center() 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 const RenderState *get_initial_state() const; @@ -88,6 +92,7 @@ private: CPT(TransformState) _world_transform; CPT(TransformState) _cs_transform; CPT(TransformState) _cs_world_transform; + PT(GeometricBoundingVolume) _view_frustum; public: static TypeHandle get_class_type() {