abrash-black-book/59-02.md
2013-12-30 20:26:41 +11:00

113 lines
6.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.
![](images/59-01.jpg)\
**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?
![](images/59-02.jpg)\
**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.)