pgraph view-frustum cull

This commit is contained in:
David Rose 2002-03-02 00:11:24 +00:00
parent 11142324c1
commit 3200a5f929
8 changed files with 292 additions and 103 deletions

View File

@ -58,6 +58,16 @@ const bool pipe_spec_is_remote = config_display.Defined("pipe-machine")
const bool compare_state_by_pointer =
config_display.GetBool("compare-state-by-pointer", true);
// This is normally true to enable the cull traversal to perform
// state-sorting and alpha-sorting. Turn this false to disable these
// features and likely improve cull performance at the expense of draw
// (and at the expense of correct alpha).
const bool cull_sorting = config_display.GetBool("cull-sorting", true);
// This is normally true; set it false to disable view-frustum culling
// (primarily useful for debugging).
const bool qpview_frustum_cull = config_display.GetBool("view-frustum-cull", true);
const float gsg_clear_r = config_display.GetFloat("gsg-clear-r", 0.0);
const float gsg_clear_g = config_display.GetFloat("gsg-clear-g", 0.0);
const float gsg_clear_b = config_display.GetFloat("gsg-clear-b", 0.0);

View File

@ -36,6 +36,8 @@ extern const bool pipe_spec_is_file;
extern const bool pipe_spec_is_remote;
extern const bool compare_state_by_pointer;
extern const bool cull_sorting;
extern const bool qpview_frustum_cull;
extern const float gsg_clear_r;
extern const float gsg_clear_g;

View File

@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////
#include "graphicsEngine.h"
#include "config_display.h"
#include "pipeline.h"
#include "drawCullHandler.h"
#include "binCullHandler.h"
@ -79,8 +80,11 @@ remove_window(GraphicsWindow *window) {
////////////////////////////////////////////////////////////////////
void GraphicsEngine::
render_frame() {
// cull_and_draw_together();
cull_bin_draw();
if (cull_sorting) {
cull_bin_draw();
} else {
cull_and_draw_together();
}
// **** This doesn't belong here; it really belongs in the Pipeline,
// but here it is for now.
@ -120,68 +124,18 @@ cull_and_draw_together() {
////////////////////////////////////////////////////////////////////
void GraphicsEngine::
cull_and_draw_together(GraphicsWindow *win, DisplayRegion *dr) {
const NodeChain &camera = dr->get_qpcamera();
if (camera.is_empty()) {
// No camera, no draw.
return;
}
qpCamera *camera_node;
DCAST_INTO_V(camera_node, camera.node());
if (!camera_node->is_active()) {
// Camera inactive, no draw.
return;
}
Lens *lens = camera_node->get_lens();
if (lens == (Lens *)NULL) {
// No lens, no draw.
return;
}
NodeChain scene = camera_node->get_scene();
if (scene.is_empty()) {
// No scene, no draw.
return;
}
GraphicsStateGuardian *gsg = win->get_gsg();
nassertv(gsg != (GraphicsStateGuardian *)NULL);
if (!gsg->set_lens(lens)) {
// The lens is inappropriate somehow.
display_cat.error()
<< gsg->get_type() << " cannot render with " << lens->get_type()
<< "\n";
return;
if (set_gsg_lens(gsg, dr)) {
DisplayRegionStack old_dr = gsg->push_display_region(dr);
gsg->prepare_display_region();
DrawCullHandler cull_handler(gsg);
do_cull(&cull_handler, dr->get_qpcamera(), gsg);
gsg->pop_display_region(old_dr);
}
DrawCullHandler cull_handler(gsg);
qpCullTraverser trav;
trav.set_cull_handler(&cull_handler);
// The world transform is computed from the camera's position; we
// then might need to adjust it into the GSG's internal coordinate
// system.
trav.set_camera_transform(scene.get_rel_transform(camera));
CPT(TransformState) render_transform = camera.get_rel_transform(scene);
CoordinateSystem external_cs = gsg->get_coordinate_system();
CoordinateSystem internal_cs = gsg->get_internal_coordinate_system();
if (internal_cs != CS_default && internal_cs != external_cs) {
CPT(TransformState) cs_transform =
TransformState::make_mat(LMatrix4f::convert_mat(external_cs, internal_cs));
render_transform = cs_transform->compose(render_transform);
}
trav.set_render_transform(render_transform);
DisplayRegionStack old_dr = gsg->push_display_region(dr);
gsg->prepare_display_region();
trav.traverse(scene.node());
gsg->pop_display_region(old_dr);
}
////////////////////////////////////////////////////////////////////
@ -217,7 +171,34 @@ cull_bin_draw() {
////////////////////////////////////////////////////////////////////
void GraphicsEngine::
cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) {
const NodeChain &camera = dr->get_qpcamera();
GraphicsStateGuardian *gsg = win->get_gsg();
nassertv(gsg != (GraphicsStateGuardian *)NULL);
PT(CullResult) cull_result = dr->_cull_result;
if (cull_result == (CullResult *)NULL) {
cull_result = new CullResult(gsg);
}
BinCullHandler cull_handler(cull_result);
do_cull(&cull_handler, dr->get_qpcamera(), gsg);
cull_result->finish_cull();
// Save the results for next frame.
dr->_cull_result = cull_result->make_next();
// Now draw.
do_draw(cull_result, gsg, dr);
}
////////////////////////////////////////////////////////////////////
// Function: GraphicsEngine::do_cull
// Access: Private
// Description: Fires off a cull traversal using the indicated camera.
////////////////////////////////////////////////////////////////////
void GraphicsEngine::
do_cull(CullHandler *cull_handler, const NodeChain &camera,
GraphicsStateGuardian *gsg) {
if (camera.is_empty()) {
// No camera, no draw.
return;
@ -243,32 +224,21 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) {
return;
}
GraphicsStateGuardian *gsg = win->get_gsg();
nassertv(gsg != (GraphicsStateGuardian *)NULL);
if (!gsg->set_lens(lens)) {
// The lens is inappropriate somehow.
display_cat.error()
<< gsg->get_type() << " cannot render with " << lens->get_type()
<< "\n";
return;
}
PT(CullResult) cull_result = dr->_cull_result;
if (cull_result == (CullResult *)NULL) {
cull_result = new CullResult(gsg);
}
BinCullHandler cull_handler(cull_result);
qpCullTraverser trav;
trav.set_cull_handler(&cull_handler);
trav.set_cull_handler(cull_handler);
// The world transform is computed from the camera's position; we
// then might need to adjust it into the GSG's internal coordinate
// system.
trav.set_camera_transform(scene.get_rel_transform(camera));
// We will need both the camera transform (the net transform from
// the scene to the camera) and the world transform (the camera
// transform inverse, or the net transform from the camera to the
// scene).
CPT(TransformState) camera_transform = scene.get_rel_transform(camera);
CPT(TransformState) world_transform = camera.get_rel_transform(scene);
// The render transform is the same as the world transform, except
// it is converted into the GSG's internal coordinate system. This
// is the transform that the GSG will apply to all of its vertices.
CPT(TransformState) render_transform = world_transform;
CPT(TransformState) render_transform = camera.get_rel_transform(scene);
CoordinateSystem external_cs = gsg->get_coordinate_system();
CoordinateSystem internal_cs = gsg->get_internal_coordinate_system();
if (internal_cs != CS_default && internal_cs != external_cs) {
@ -276,17 +246,80 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) {
TransformState::make_mat(LMatrix4f::convert_mat(external_cs, internal_cs));
render_transform = cs_transform->compose(render_transform);
}
trav.set_camera_transform(scene.get_rel_transform(camera));
trav.set_render_transform(render_transform);
if (qpview_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 = lens->make_bounds();
if (bv != (BoundingVolume *)NULL &&
bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
// Transform it into the appropriate coordinate space.
PT(GeometricBoundingVolume) local_frustum;
local_frustum = DCAST(GeometricBoundingVolume, bv->make_copy());
local_frustum->xform(camera_transform->get_mat());
trav.set_view_frustum(local_frustum);
}
}
trav.traverse(scene.node());
cull_result->finish_cull();
// Save the results for next frame.
dr->_cull_result = cull_result->make_next();
// Now draw.
DisplayRegionStack old_dr = gsg->push_display_region(dr);
gsg->prepare_display_region();
cull_result->draw();
gsg->pop_display_region(old_dr);
}
////////////////////////////////////////////////////////////////////
// Function: GraphicsEngine::do_draw
// Access: Private
// Description: Draws the previously-culled scene.
////////////////////////////////////////////////////////////////////
void GraphicsEngine::
do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg,
DisplayRegion *dr) {
if (set_gsg_lens(gsg, dr)) {
DisplayRegionStack old_dr = gsg->push_display_region(dr);
gsg->prepare_display_region();
cull_result->draw();
gsg->pop_display_region(old_dr);
}
}
////////////////////////////////////////////////////////////////////
// Function: GraphicsEngine::set_gsg_lens
// Access: Private
// Description: Sets up the GSG to draw with the lens from the
// indicated DisplayRegion. Returns true if the lens is
// acceptable, false otherwise.
////////////////////////////////////////////////////////////////////
bool GraphicsEngine::
set_gsg_lens(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
const NodeChain &camera = dr->get_qpcamera();
if (camera.is_empty()) {
// No camera, no draw.
return false;
}
qpCamera *camera_node;
DCAST_INTO_R(camera_node, camera.node(), false);
Lens *lens = camera_node->get_lens();
if (lens == (Lens *)NULL) {
// No lens, no draw.
return false;
}
if (!gsg->set_lens(lens)) {
// The lens is inappropriate somehow.
display_cat.error()
<< gsg->get_type() << " cannot render with " << lens->get_type()
<< "\n";
return false;
}
return true;
}

View File

@ -57,6 +57,13 @@ private:
void cull_bin_draw();
void cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr);
void do_cull(CullHandler *cull_handler, const NodeChain &camera,
GraphicsStateGuardian *gsg);
void do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg,
DisplayRegion *dr);
bool set_gsg_lens(GraphicsStateGuardian *gsg, DisplayRegion *dr);
Pipeline *_pipeline;
typedef pset<PT(GraphicsWindow)> Windows;

View File

@ -47,6 +47,11 @@ ConfigureFn(config_pgraph) {
init_libpgraph();
}
// Set this true to cause culling to be performed by rendering the
// object in red wireframe, rather than actually culling it. This
// helps make culling errors obvious.
const bool qpfake_view_frustum_cull = config_pgraph.GetBool("fake-view-frustum-cull", false);
////////////////////////////////////////////////////////////////////
// Function: init_libpgraph

View File

@ -26,6 +26,8 @@
ConfigureDecl(config_pgraph, EXPCL_PANDA, EXPTP_PANDA);
NotifyCategoryDecl(pgraph, EXPCL_PANDA, EXPTP_PANDA);
extern const bool qpfake_view_frustum_cull;
extern EXPCL_PANDA void init_libpgraph();
#endif

View File

@ -23,6 +23,9 @@
#include "cullHandler.h"
#include "dcast.h"
#include "qpgeomNode.h"
#include "colorAttrib.h"
#include "textureAttrib.h"
#include "config_pgraph.h"
////////////////////////////////////////////////////////////////////
// Function: qpCullTraverser::Constructor
@ -74,6 +77,39 @@ set_render_transform(const TransformState *render_transform) {
_render_transform = render_transform;
}
////////////////////////////////////////////////////////////////////
// Function: qpCullTraverser::set_view_frustum
// Access: Public
// Description: Specifies the bounding volume that corresponds to the
// viewing frustum. Any primitives that fall entirely
// outside of this volume are not drawn.
////////////////////////////////////////////////////////////////////
void qpCullTraverser::
set_view_frustum(GeometricBoundingVolume *view_frustum) {
_view_frustum = view_frustum;
}
////////////////////////////////////////////////////////////////////
// Function: qpCullTraverser::set_guard_band
// Access: Public
// Description: Specifies the bounding volume to use for detecting
// guard band clipping. This is a render optimization
// for certain cards that support this feature; the
// guard band is a 2-d area than the frame buffer.
// If a primitive will appear entirely within the guard
// band after perspective transform, it may be drawn
// correctly with clipping disabled, for a small
// performance gain.
//
// This is the bounding volume that corresponds to the
// 2-d guard band. If a primitive is entirely within
// this area, clipping will be disabled on the GSG.
////////////////////////////////////////////////////////////////////
void qpCullTraverser::
set_guard_band(GeometricBoundingVolume *guard_band) {
_guard_band = guard_band;
}
////////////////////////////////////////////////////////////////////
// Function: qpCullTraverser::set_cull_handler
// Access: Public
@ -95,7 +131,7 @@ traverse(PandaNode *root) {
nassertv(_cull_handler != (CullHandler *)NULL);
r_traverse(root, _render_transform, TransformState::make_identity(),
_initial_state, 0);
_initial_state, _view_frustum, _guard_band);
}
////////////////////////////////////////////////////////////////////
@ -107,12 +143,93 @@ void qpCullTraverser::
r_traverse(PandaNode *node,
const TransformState *render_transform,
const TransformState *net_transform,
const RenderState *state, int flags) {
CPT(TransformState) next_render_transform =
render_transform->compose(node->get_transform());
CPT(TransformState) next_net_transform =
net_transform->compose(node->get_transform());
CPT(RenderState) next_state = state->compose(node->get_state());
const RenderState *state,
GeometricBoundingVolume *view_frustum,
GeometricBoundingVolume *guard_band) {
CPT(RenderState) next_state = state;
if (view_frustum != (GeometricBoundingVolume *)NULL) {
// If we have a viewing frustum, check to see if the node's
// bounding volume falls within it.
const BoundingVolume &node_volume = node->get_bound();
nassertv(node_volume.is_of_type(GeometricBoundingVolume::get_class_type()));
const GeometricBoundingVolume *node_gbv =
DCAST(GeometricBoundingVolume, &node_volume);
int result = view_frustum->contains(node_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// No intersection at all. Cull.
if (!qpfake_view_frustum_cull) {
return;
}
// If we have fake view-frustum culling enabled, instead of
// actually culling an object we simply force it to be drawn in
// red wireframe.
view_frustum = (GeometricBoundingVolume *)NULL;
CPT(RenderState) fake_effect = RenderState::make
(ColorAttrib::make_flat(Colorf(1.0f, 0.0f, 0.0f, 1.0f)),
TextureAttrib::make_off(),
1000);
next_state = next_state->compose(fake_effect);
} else if ((result & BoundingVolume::IF_all) != 0) {
// The node and its descendants are completely enclosed within
// the frustum. No need to cull further.
view_frustum = (GeometricBoundingVolume *)NULL;
} else {
if (node->is_final()) {
// The bounding volume is partially, but not completely,
// within the viewing frustum. Normally we'd keep testing
// child bounded volumes as we continue down. But this node
// has the "final" flag, so the user is claiming that there is
// some important reason we should consider everything visible
// at this point. So be it.
view_frustum = (GeometricBoundingVolume *)NULL;
}
}
}
CPT(TransformState) next_render_transform = render_transform;
CPT(TransformState) next_net_transform = net_transform;
PT(GeometricBoundingVolume) next_view_frustum = view_frustum;
PT(GeometricBoundingVolume) next_guard_band = guard_band;
const TransformState *transform = node->get_transform();
if (!transform->is_identity()) {
next_render_transform = render_transform->compose(transform);
next_net_transform = net_transform->compose(transform);
if ((view_frustum != (GeometricBoundingVolume *)NULL) ||
(guard_band != (GeometricBoundingVolume *)NULL)) {
// We need to move the viewing frustums into the node's
// coordinate space by applying the node's inverse transform.
if (transform->is_singular()) {
// But we can't invert a singular transform! Instead of
// trying, we'll just give up on frustum culling from this
// point down.
view_frustum = (GeometricBoundingVolume *)NULL;
guard_band = (GeometricBoundingVolume *)NULL;
} else {
CPT(TransformState) inv_transform =
transform->invert_compose(TransformState::make_identity());
if (view_frustum != (GeometricBoundingVolume *)NULL) {
next_view_frustum = DCAST(GeometricBoundingVolume, view_frustum->make_copy());
next_view_frustum->xform(inv_transform->get_mat());
}
if (guard_band != (GeometricBoundingVolume *)NULL) {
next_guard_band = DCAST(GeometricBoundingVolume, guard_band->make_copy());
next_guard_band->xform(inv_transform->get_mat());
}
}
}
}
next_state = next_state->compose(node->get_state());
const BillboardAttrib *billboard = state->get_billboard();
if (billboard != (const BillboardAttrib *)NULL) {
@ -121,6 +238,11 @@ r_traverse(PandaNode *node,
billboard->do_billboard(net_transform, _camera_transform);
next_render_transform = next_render_transform->compose(billboard_transform);
next_net_transform = next_net_transform->compose(billboard_transform);
// We can't reliably cull within a billboard, because the geometry
// might get rotated out of its bounding volume. So once we get
// within a billboard, we consider it all visible.
next_view_frustum = (GeometricBoundingVolume *)NULL;
}
if (node->is_geom_node()) {
@ -140,6 +262,7 @@ r_traverse(PandaNode *node,
PandaNode::Children cr = node->get_children();
int num_children = cr.get_num_children();
for (int i = 0; i < num_children; i++) {
r_traverse(cr.get_child(i), next_render_transform, next_net_transform, next_state, flags);
r_traverse(cr.get_child(i), next_render_transform, next_net_transform,
next_state, next_view_frustum, next_guard_band);
}
}

View File

@ -23,6 +23,7 @@
#include "renderState.h"
#include "transformState.h"
#include "geometricBoundingVolume.h"
#include "pointerTo.h"
class PandaNode;
@ -43,6 +44,8 @@ public:
void set_initial_state(const RenderState *initial_state);
void set_camera_transform(const TransformState *camera_transform);
void set_render_transform(const TransformState *render_transform);
void set_view_frustum(GeometricBoundingVolume *view_frustum);
void set_guard_band(GeometricBoundingVolume *guard_band);
void set_cull_handler(CullHandler *cull_handler);
void traverse(PandaNode *root);
@ -50,11 +53,15 @@ public:
private:
void r_traverse(PandaNode *node, const TransformState *render_transform,
const TransformState *net_transform,
const RenderState *state, int flags);
const RenderState *state,
GeometricBoundingVolume *view_frustum,
GeometricBoundingVolume *guard_band);
CPT(RenderState) _initial_state;
CPT(TransformState) _camera_transform;
CPT(TransformState) _render_transform;
PT(GeometricBoundingVolume) _view_frustum;
PT(GeometricBoundingVolume) _guard_band;
CullHandler *_cull_handler;
};