render order documents

This commit is contained in:
David Rose 2004-09-01 16:27:55 +00:00
parent 184ed3e96f
commit eead112d48
3 changed files with 209 additions and 168 deletions

View File

@ -1,168 +0,0 @@
NOTE: As of April 2002, we have rewritten the primary scene graph
interface to Panda, which invalidates almost all of the contents of
this document. We hope to be providing an updated document soon. In
the meantime, this document remains, and may be useful for historical
purposes.
Panda has two primary modes of rendering: with or without a separate
"Cull" traversal.
In the simplest case, Panda renders directly, without a separate Cull
traversal. In this case, the scene graph is traversed with a simple
depth-first, left-to-right in-order traversal, and GeomNodes are sent
to the graphics engine as they are encountered.
When operating in this mode, the only way to control render order is
to adjust the order of nodes within the hierarchy. It is possible to
do this by either building the hierarchy in a specific order (each
reparenting operation in the scene graph appends the new node to the
end of its parent's children list), or more explicitly, by setting a
sort order on each arc as it is created or moved (the NodePath
reparenting methods support an optional sort parameter, and the
NodeRelation class has a set_sort() method). Normally the sort order
on each arc is zero, but it may be explicitly set to any integer. A
node's list of children will always be kept in order from lowest to
highest sort order, and where siblings have an equal sort order, they
will be arranged in the order in which they were added.
More commonly, Panda is operated using a Cull traversal. This
traversal makes a complete pass through the scene graph before
rendering anything, collecting together all the GeomNodes that are to
be rendered and arranging them in a suitable order before passing them
to the graphics engine. It is somewhat inappropriately named;
although it does do view-frustum culling, so does the simpler direct
traversal; it should more properly be called the State Sorting
traversal.
When the Cull traversal is in use, the hierarchy order is irrelevant.
Instead, the Cull traversal uses a binning system to support user
control of the order in which things are rendered.
As the Cull traversal encounters GeomNodes, it assigns each one to a
particular bin, identified by name. These bins are selected by
setting a GeomBinTransition above the arc in question, or by calling
NodePath::set_bin().
After all the GeomNodes have been identified, the various bins are
sorted in order according to each bin's sort index, which is specified
by GeomBin::set_sort(). This is an arbitary integer assigned to each
bin, and the lower-number bins are drawn first. Each bin is then
responsible for drawing its contents--the set of GeomNodes assigned to
it--in whatever order it likes. The various kinds of bins render
their GeomNodes in different ways:
GeomBinStateSorted -- collects together all GeomNodes that share a
common state and renders them at once, before switching to the
next group of GeomNode with a common state. Attempts to minimize
the state changes between groups of GeomNodes. The goal of this
bin is to minimize the number of state changes sent to the
graphics engine, and so reduce rendering overhead.
GeomBinBackToFront -- renders everything in order from the furthest
away to the closest. This is generally necessary for correct
transparent and semitransparent rendering. The ordering is based
on the center of each GeomNode's bounding volume, relative to the
camera plane.
GeomBinNormal -- assigns each GeomNode to one of two sub-bins:
transparent geometry is assigned to a GeomBinBackToFront, while
opaque geometry is assigned to a GeomBinStateSorted. This is the
kind of bin that 'default' is defined to be; it is the bin that
all GeomNodes are assigned to when no other bin is explicitly
specified.
GeomBinUnsorted -- renders everything in no particular order.
GeomBinFixed -- renders everything according to a user-specified
order, potentially per GeomNode. Each GeomBinTransition that
specifies a GeomBinFixed bin may also include an optional sort
order (this is an optional second parameter to the
GeomBinTransition constructor, as well as to NodePath::set_bin());
the GeomBinFixed will render low-number nodes before high-number
nodes.
If no bin is explicitly specified, each GeomNode is assigned to a bin
named 'default', which is of type GeomBinNormal; this bin is created
at startup and contains two sub-bins, one for transparent geometry and
one for nontransparent geometry. The nontransparent bin is rendered
first, with its contents in state-sorted order, followed by the
transparent bin, with its contents in order from back to front. This
usually provides correct behavior for transparent and semitransparent
objects, which must generally be rendered after everything behind them
has already been rendered.
However, this sometimes fails, particularly with large, flat polygons
stacked closely in front of one another. In cases like these it may
be necessary to explicitly specify an ordering.
There is another predefined bin available called 'fixed'. Nothing
will ever be rendered in 'fixed' (or any other bin, other than
'default') unless it is explicitly assigned to it. The 'fixed' bin is
of type GeomBinFixed, and renders its objects according to a fixed
ordering, specified as the second parameter to the GeomBinTransition
constructor, or to NodePath::set_bin(). There is also another
predefined bin called 'background', which is another bin of type
GeomBinFixed.
The order of all the predefined bins (and their predefined sort
orders) is as follows:
10 - 'background' : GeomBinFixed
20 - 'opaque' : GeomBinNormal (opaque sub-bin of 'default')
30 - 'transparent' : GeomBinNormal (transparent sub-bin of 'default')
40 - 'fixed' : GeomBinFixed
50 - 'unsorted' : GeomBinUnsorted
Thus, the 'fixed' bin can be used for things that must be rendered
correctly relative to each other, but should render after all other
things in the scene graph, while the 'background' bin can be used for
things that must render before other transparent things in the scene
graph (it's particularly useful for large, flat polygons on the
horizon).
Other bins may easily be defined, either at run time or via a line in
a Configrc file. It is also possible to redefine any of the
predefined bins by defining a new bin with the same name.
To define a bin via the Configrc file, add a line beginning with
"cull-bin" and consisting of three space-separated fields: the name of
the bin, the bin sort order, and the type of bin. For example, to
create an bin called 'shadow' to render shadows in no particular
order, but before any other transparent objects are rendered, you may
add the line:
cull-bin shadow 25 unsorted
The valid bin types are normal, unsorted, state-sorted, fixed, or
back-to-front.
To define a bin at run time, you simply create a bin of the
appropriate type using its constructor, and then assign it to the
current render traverser via GeomBin::set_traverser(). To do this,
you must get a pointer to the current traverser via
GraphicsStateGuardian::get_render_traverser(). This will either be a
DirectRenderTraverser or a CullTraverser.
A GeomBin may only be assigned to a CullTraverser. If the current
render traverser is not a CullTraverser, then Panda is operating
without a Cull traversal, and you cannot meaningfully assign things to
GeomBins anyway.
For example, the following Python code creates the same GeomBin
defined above:
shadowBin = GeomBinUnsorted('shadow')
shadowBin.setSort(15)
try:
shadowBin.setTraverser(win.getGsg().getRenderTraverser())
except:
pass
The try .. except block is a good idea to protect against the case in
which getRenderTraverser() does not return a CullTraverser.

View File

@ -0,0 +1,126 @@
HOW TO CONTROL RENDER ORDER
In most simple scenes, you can naively attach geometry to the scene
graph and let Panda decide the order in which objects should be
rendered. Generally, it will do a good enough job, but there are
occasions in which it is necessary to step in and take control of the
process.
To do this well, you need to understand the implications of render
order. In a typical OpenGL- or DirectX-style Z-buffered system, the
order in which primitives are sent to the graphics hardware is
theoretically unimportant, but in practice there are many important
reasons for rendering one object before another.
Firstly, state sorting is one important optimization. This means
choosing to render things that have similar state (texture, color,
etc.) all at the same time, to minimize the number of times the
graphics hardware has to be told to change state in a particular
frame. This sort of optimization is particularly important for very
high-end graphics hardware, which achieves its advertised theoretical
polygon throughput only in the absence of any state changes; for many
such advanced cards, each state change request will completely flush
the register cache and force a restart of the pipeline.
Secondly, some hardware has a different optimization requirement, and
may benefit from drawing nearer things before farther things, so that
the Z-buffer algorithm can effectively short-circuit some of the
advanced shading features in the graphics card for pixels that would
be obscured anyway. This sort of hardware will draw things fastest
when the scene is sorted in order from the nearest object to the
farthest object, or "front-to-back" ordering.
Finally, regardless of the rendering optimizations described above, a
particular sorting order is required to render transparency properly
(in the absence of specialized hardware support that few graphics
cards provide). Transparent and semitransparent objects are normally
rendered by blending their semitransparent parts with what has already
been drawn to the framebuffer, which means that it is important that
everything that will appear behind a semitransparent object must have
already been drawn before the object itself is drawn. This implies
that all semitransparent objects must be drawn in order from farthest
away to nearest, or in "back-to-front" ordering, and furthermore that
the opaque objects should all be drawn before any of the
semitransparent objects.
Panda achieves these sometimes conflicting sorting requirements
through the use of bins.
CULL BINS
The CullBinManager is a global object that maintains a list of all of
the cull bins in the world, and their properties. Initially, there
are five default bins, and they will be rendered in the following
order:
Bin Name Sort Type
-------------- ---- ----------------
"background" 10 BT_fixed
"opaque" 20 BT_state_sorted
"transparent" 30 BT_back_to_front
"fixed" 40 BT_fixed
"unsorted" 50 BT_unsorted
When Panda traverses the scene graph each frame for rendering, it
assigns each Geom it encounters into one of the bins defined in the
CullBinManager. (The above lists only the default bins. Additional
bins may be created as needed, using either the
CullBinManager::add_bin() method, or the Config.prc "cull-bin"
variable.)
You may assign a node or nodes to an explicit bin using the
NodePath::set_bin() interface. set_bin() requires two parameters, the
bin name and an integer sort parameter; the sort parameter is only
meaningful if the bin type is BT_fixed (more on this below), but it
must always be specified regardless.
If a node is not explicitly assigned to a particular bin, then Panda
will assign it into either the "opaque" or the "transparent" bin,
according to whether it has transparency enabled or not. (Note that
the reverse is not true: explicitly assigning an object into the
"transparent" bin does not automatically enable transparency for the
object.)
When the entire scene has been traversed and all objects have been
assigned to bins, then the bins are rendered in order according to
their sort parameter. Within each bin, the contents are sorted
according to the bin type.
The following bin types may be specified:
BT_fixed
Render all of the objects in the bin in a fixed order specified by
the user. This is according to the second parameter of the
NodePath::set_bin() method; objects with a lower value are drawn
first.
BT_state_sorted
Collects together objects that share similar state and renders
them together, in an attempt to minimize state transitions in the
scene. Note: at the moment, this mode is not actually implemented
in Panda, and defaults to the same behavior as BT_unsorted. This
does limit the performance of Panda for extremely complex scenes
on very high-end graphics cards, but has little impact on most
consumer-level cards.
BT_back_to_front
Sorts each Geom according to the center of its bounding volume, in
linear distance from the camera plane, so that farther objects are
drawn first. That is, in Panda's default right-handed Z-up
coordinate system, objects with large positive Y are drawn before
objects with smaller positive Y.
BT_front_to_back
The reverse of back_to_front, this sorts so that nearer objects
are drawn first.
BT_unsorted
Objects are drawn in the order in which they appear in the scene
graph, in a depth-first traversal from top to bottom and then from
left to right.

View File

@ -0,0 +1,83 @@
HOW TO FIX TRANSPARENCY ISSUES
Usually transparency works as expected in Panda automatically, but
sometimes it just seems to go awry, where a semitransparent object in
the foreground seems to partially obscure a semitransparent object
behind it. This is especially likely to happen with large flat
polygon cutouts, or when a transparent object is contained within
another transparent object, or when parts of a transparent object can
be seen behind other parts of the same object.
The fundamental problem is that correct transparency, in the absence
of special hardware support involving extra framebuffer bits, requires
drawing everything in order from farthest away to nearest. This means
sorting each polygon--actually, each pixel, for true correctness--into
back-to-front order before drawing the scene.
It is, of course, too expensive to split up every transparent object
into individual pixels or polygons for sorting individually, so Panda
sorts objects at the Geom level, according to the center of the
bounding volume. This works well 95% of the time.
You run into problems with large flat polygons, though, since these
tend to have parts that are far away from the center of their bounding
volume. The bounding-volume sorting is especially likely to go awry
when you have two or more large flats close behind the other, and you
view them from slightly off-axis. (Try drawing a picture, of the two
flats as seen from the top, and imagine yourself viewing them from
different directions. Also imagine where the center of the bounding
volumes is.)
Now, there are a number of solutions to this sort of problem. No one
solution is right for every situation.
First, the easiest thing to do is to use M_dual transparency. This is
a special transparency mode in which the completely invisible parts of
the object aren't drawn into the Z-buffer at all, so that they don't
have any chance of obscuring things behind them. This only works well
if the flats are typical cutouts, where there is a big solid part
(alpha == 1.0) and a big transparent part (alpha == 0.0), and not a
lot of semitransparent parts (0.0 < alpha < 1.0). It is also a
slightly more expensive rendering mode than the default of M_alpha, so
it's not enabled by default in Panda. But egg-palettize will turn it
on automatically for a particular model if it detects textures that
appear to be cutouts of the appropriate nature, which is another
reason to use egg-palettize if you are not already.
Second, an easy thing to do is to chop up one or both competing models
into smaller pieces, each of which can be sorted independently by
Panda. For instance, you can split one big polygon into a grid of
little polygons, and the sorting is more likely to be accurate for
each piece (because the center of the bounding volume is closer to the
pixels). You can draw a picture to see how this works. In order to
do this properly, you can't just make it one big mesh of small
polygons, since Panda will make a mesh into a single Geom of
tristrips; instead, it needs to be separate meshes, so that each one
will become its own Geom. Obviously, this is slightly more expensive
too, since you are introducing additional vertices and adding more
objects to the sort list; so you don't want to go too crazy with the
smallness of your polygons.
A third option is simply to disable the depth write on your
transparent objects. This is most effective when you are trying to
represent something that is barely visible, like glass or a soap
bubble. Doing this doesn't improve the likelihood of correct sorting,
but it will tend to make the artifacts of an incorrect sorting less
obvious. You can achieve this by using the transparency option
"blend_no_occlude" in an egg file, or by explicitly disabling the
depth write on a loaded model with node_path.set_depth_write(false).
You should be careful only to disable depth write on the transparent
pieces, and not on the opaque parts.
A final option is to make explicit sorting requests to Panda. This is
often the last resort because it is more difficult, but it does have
the advantage of not adding additional performance penalties to your
scene. It only works well when the transparent objects can be sorted
reliably with respect to everything else behind them. For instance,
clouds in the sky can reliably be drawn before almost everything else
in the scene, except the sky itself. Similarly, a big flat that is up
against an opaque wall can reliably be drawn after all of the opaque
objects, but before any other transparent object, regardless of where
the camera happens to be placed in the scene. See
howto.control_render_order.txt for more information about explicitly
controlling the rendering order.