113 lines
6.2 KiB
Markdown
113 lines
6.2 KiB
Markdown
Back-to-front or front-to-back traversal in itself wouldn't be so
|
||
impressive—there are many ways to do that—were it not for one additional
|
||
detail: The traversal can always be performed in linear time, as we'll
|
||
see later on. For instance, you can traverse, a polygon list
|
||
back-to-front from any viewpoint simply by walking through the
|
||
corresponding BSP tree once, visiting each node one and only one time,
|
||
and performing only one relatively inexpensive test at each node.
|
||
|
||
It's hard to get cheaper sorting than linear time, and BSP-based
|
||
rendering stacks up well against alternatives such as z-buffering,
|
||
octrees, z-scan sorting, and polygon sorting. Better yet, a scene
|
||
database represented as a BSP tree can be clipped to the view pyramid
|
||
very efficiently; huge chunks of a BSP tree can be lopped off when
|
||
clipping to the view pyramid, because if the entire area or volume of a
|
||
node lies entirely outside the view volume, then *all* nodes and leaves
|
||
that are children of that node must likewise be outside the view volume,
|
||
for reasons that will become clear as we delve into the workings of BSP
|
||
trees.
|
||
|
||
\
|
||
**Figure 59.1** *The painter's algorithm.*
|
||
|
||
#### Limitations of BSP Trees {#Heading5}
|
||
|
||
Powerful as they are, BSP trees aren't perfect. By far the greatest
|
||
limitation of BSP trees is that they're time-consuming to build, enough
|
||
so that, for all practical purposes, BSP trees must be precalculated,
|
||
and cannot be built dynamically at runtime. In fact, a BSP-tree compiler
|
||
that attempts to perform some optimization (limiting the number of
|
||
surfaces that need to be split, for example) can easily take minutes or
|
||
even hours to process large world databases.
|
||
|
||
A fixed world database is fine for walkthrough or flythrough
|
||
applications (where the viewpoint moves through a static scene), but not
|
||
much use for games or virtual reality, where objects constantly move
|
||
relative to one another. Consequently, various workarounds have been
|
||
developed to allow moving objects to appear in BSP tree-based scenes.
|
||
DOOM, for example, uses 2-D sprites mixed into BSP-based 3-D scenes;
|
||
note, though, that this approach requires maintaining z information so
|
||
that sprites can be drawn and occluded properly. Alternatively, movable
|
||
objects could be represented as separate BSP trees and merged anew into
|
||
the world BSP tree with each move. Dynamic merging may or may not be
|
||
fast enough, depending on the scene, but merging BSP trees tends to be
|
||
quicker than building them, because the BSP trees being merged are
|
||
already spatially sorted.
|
||
|
||
Another possibility would be to generate a per-pixel z-buffer for each
|
||
frame as it's rendered, to allow dynamically changing objects to be
|
||
drawn into the BSP-based world. In this scheme, the BSP tree would allow
|
||
fast traversal and clipping of the complex, static world, and the
|
||
z-buffer would handle the relatively localized visibility determination
|
||
involving moving objects. The drawback of this is the need for a
|
||
memory-hungry z-buffer; a typical 640x480 z-buffer requires a fairly
|
||
appalling 600K, with equally appalling cache-miss implications for
|
||
performance.
|
||
|
||
Yet another possibility would be to build the world so that each dynamic
|
||
object falls entirely within a single subspace of the static BSP tree,
|
||
rather than straddling splitting lines or planes. In this case, dynamic
|
||
objects can be treated as points, which are then just sorted into the
|
||
BSP tree on the fly as they move.
|
||
|
||
The only other drawbacks of BSP trees that I know of are the memory
|
||
required to store the tree, which amounts to a few pointers per node,
|
||
and the relative complexity of debugging BSP-tree compilation and usage;
|
||
debugging a large data set being processed by recursive code (which BSP
|
||
code tends to be) can be quite a challenge. Tools like the BSP compiler
|
||
I'll present in the next chapter, which visually depicts the process of
|
||
spatial subdivision as a BSP tree is constructed, help a great deal with
|
||
BSP debugging.
|
||
|
||
### Building a BSP Tree {#Heading6}
|
||
|
||
Now that we know a good bit about what a BSP tree is, how it helps in
|
||
visible surface determination, and what its strengths and weaknesses
|
||
are, let's take a look at how a BSP tree actually works to provide
|
||
front-to-back or back-to-front ordering. This chapter's discussion will
|
||
be at a conceptual level, with plenty of figures; in the next chapter
|
||
we'll get into mechanisms and implementation details.
|
||
|
||
I'm going to discuss only 2-D BSP trees from here on out, because
|
||
they're much easier to draw and to grasp than their 3-D counterparts.
|
||
Don't worry, though; the principles of 2-D BSP trees using line segments
|
||
generalize directly to 3-D BSP trees using polygons. Also, 2-D BSP trees
|
||
are quite powerful in their own right, as evidenced by DOOM, which is
|
||
built around 2-D BSP trees.
|
||
|
||
First, let's construct a simple BSP tree. Figure 59.2 shows a set of
|
||
four lines that will constitute our sample world. I'll refer to these as
|
||
walls, because that's one easily-visualized context in which a 2-D BSP
|
||
tree would be useful in a game. Think of Figure 59.2 as depicting
|
||
vertical walls viewed from directly above, so they're lines for the
|
||
purpose of the BSP tree. Note that each wall has a front side, denoted
|
||
by a normal (perpendicular) vector, and a back side. To make a BSP tree
|
||
for this sample set, we need to split the world in two, then each part
|
||
into two again, and so on, until each wall resides in its own unique
|
||
subspace. An obvious question, then, is how should we carve up the world
|
||
of Figure 59.2?
|
||
|
||
\
|
||
**Figure 59.2** *A sample set of walls, viewed from above.*
|
||
|
||
There are infinitely valid ways to carve up Figure 59.2, but the
|
||
simplest is just to carve along the lines of the walls themselves, with
|
||
each node containing one wall. This is not necessarily optimal, in the
|
||
sense of producing the smallest tree, but it has the virtue of
|
||
generating the splitting lines without expensive analysis. It also saves
|
||
on data storage, because the data for the walls can do double duty in
|
||
describing the splitting lines as well. (Putting one wall on each
|
||
splitting line doesn't actually create a unique subspace for each wall,
|
||
but it does create a unique subspace *boundary* for each wall; as we'll
|
||
see, that spatial organization provides for the same unambiguous
|
||
visibility ordering as a unique subspace would.)
|