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:
parent
78425b245e
commit
d11d4e0d85
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue