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
This commit is contained in:
rdb 2019-02-19 21:59:04 +01:00
parent 78425b245e
commit d11d4e0d85
5 changed files with 112 additions and 27 deletions

View File

@ -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<CullKey, DisplayRegion *> 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<PT(SceneSetup)> shadow_passes;
pmap<NodePath, UnionBoundingVolume> 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;
}

View File

@ -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) {

View File

@ -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();
}
/**

View File

@ -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.

View File

@ -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() {