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

139 lines
7.4 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.

### The Beam Tree {#Heading8}
John's original Quake design was to draw front-to-back, using a second
BSP tree to keep track of what parts of the screen were already drawn
and which were still empty and therefore drawable by the remaining
polygons. Logically, you can think of this BSP tree as being a 2-D
region describing solid and empty areas of the screen, as shown in
Figure 64.4, but in fact it is a 3-D tree, of the sort known as a *beam
tree.* A beam tree is a collection of 3-D wedges (beams), bounded by
planes, projecting out from some center point, in this case the
viewpoint, as shown in Figure 64.5.
In John's design, the beam tree started out consisting of a single beam
describing the frustum; everything outside that beam was marked solid
(so nothing would draw there), and the inside of the beam was marked
empty. As each new polygon was reached while walking the world BSP tree
front-to-back, that polygon was converted to a beam by running planes
from its edges through the viewpoint, and any part of the beam that
intersected empty beams in the beam tree was considered drawable and
added to the beam tree as a solid beam. This continued until either
there were no more polygons or the beam tree became entirely solid. Once
the beam tree was completed, the visible portions of the polygons that
had contributed to the beam tree were drawn.
![](images/64-04.jpg)\
**Figure 64.4**  *Partitioning the screen into 2-D regions.*
![](images/64-05.jpg)\
**Figure 64.5**  *Beams as wedges projecting from the viewpoint to
polygon edges.*
The advantage to working with a 3-D beam tree, rather than a 2-D region,
is that determining which side of a beam plane a polygon vertex is on
involves only checking the sign of the dot product of the ray to the
vertex and the plane normal, because all beam planes run through the
origin (the viewpoint). Also, because a beam plane is completely
described by a single normal, generating a beam from a polygon edge
requires only a cross-product of the edge and a ray from the edge to the
viewpoint. Finally, bounding spheres of BSP nodes can be used to do the
aforementioned bulk culling to the frustum.
The early-out feature of the beam tree—stopping when the beam tree
becomes solid—seems appealing, because it appears to cap worst-case
performance. Unfortunately, there are still scenes where it's possible
to see all the way to the sky or the back wall of the world, so in the
worst case, all polygons in the frustum will still have to be tested
against the beam tree. Similar problems can arise from tiny cracks due
to numeric precision limitations. Beam-tree clipping is fairly
time-consuming, and in scenes with long view distances, such as views
across the top of a level, the total cost of beam processing slowed
Quake's frame rate to a crawl. So, in the end, the beam-tree approach
proved to suffer from much the same malady as the painter's algorithm:
The worst case was much worse than the average case, and it didn't scale
well with increasing level complexity.
### 3-D Engine du Jour {#Heading9}
Once the beam tree was working, John relentlessly worked at speeding up
the 3-D engine, always trying to improve the design, rather than
tweaking the implementation. At least once a week, and often every day,
he would walk into my office and say "Last night I couldn't get to
sleep, so I was thinking..." and I'd know that I was about to get my
mind stretched yet again. John tried many ways to improve the beam tree,
with some success, but more interesting was the profusion of wildly
different approaches that he generated, some of which were merely
discussed, others of which were implemented in overnight or weekend-long
bursts of coding, in both cases ultimately discarded or further evolved
when they turned out not to meet the design criteria well enough. Here
are some of those approaches, presented in minimal detail in the hopes
that, like Tom Wilson with the Paradise FIFO, your imagination will be
sparked.
#### Subdividing Raycast {#Heading10}
Rays are cast in an 8x8 screen-pixel grid; this is a highly efficient
operation because the first intersection with a surface can be found by
simply clipping the ray into the BSP tree, starting at the viewpoint,
until a solid leaf is reached. If adjacent rays don't hit the same
surface, then a ray is cast halfway between, and so on until all
adjacent rays either hit the same surface or are on adjacent pixels;
then the block around each ray is drawn from the polygon that was hit.
This scales very well, being limited by the number of pixels, with no
overdraw. The problem is dropouts; it's quite possible for small
polygons to fall between rays and vanish.
#### Vertex-Free Surfaces {#Heading11}
The world is represented by a set of surface planes. The polygons are
implicit in the plane intersections, and are extracted from the planes
as a final step before drawing. This makes for fast clipping and a very
small data set (planes are far more compact than polygons), but it's
time-consuming to extract polygons from planes.
#### The Draw-Buffer {#Heading12}
Like a z-buffer, but with 1 bit per pixel, indicating whether the pixel
has been drawn yet. This eliminates overdraw, but at the cost of an
inner-loop buffer test, extra writes and cache misses, and, worst of
all, considerable complexity. Variations include testing the draw-buffer
a byte at a time and completely skipping fully-occluded bytes, or
branching off each draw-buffer byte to one of 256 unrolled inner loops
for drawing 0-8 pixels, in the process possibly taking advantage of the
ability of the x86 to do the perspective floating-point divide in
parallel while 8 pixels are processed.
#### Span-Based Drawing {#Heading13}
Polygons are rasterized into spans, which are added to a global span
list and clipped against that list so that only the nearest span at each
pixel remains. Little sorting is needed with front-to-back walking,
because if there's any overlap, the span already in the list is nearer.
This eliminates overdraw, but at the cost of a lot of span arithmetic;
also, every polygon still has to be turned into spans.
#### Portals {#Heading14}
The holes where polygons are missing on surfaces are tracked, because
it's only through such portals that line-of-sight can extend. Drawing
goes front-to-back, and when a portal is encountered, polygons and
portals behind it are clipped to its limits, until no polygons or
portals remain visible. Applied recursively, this allows drawing only
the visible portions of visible polygons, but at the cost of a
considerable amount of portal clipping.
### Breakthrough! {#Heading15}
In the end, John decided that the beam tree was a sort of second-order
structure, reflecting information already implicitly contained in the
world BSP tree, so he tackled the problem of extracting visibility
information directly from the world BSP tree. He spent a week on this,
as a byproduct devising a perfect DOOM (2-D) visibility architecture,
whereby a single, linear walk of a DOOM BSP tree produces zero-overdraw
2-D visibility. Doing the same in 3-D turned out to be a much more
complex problem, though, and by the end of the week John was frustrated
by the increasing complexity and persistent glitches in the visibility
code. Although the direct-BSP approach was getting closer to working, it
was taking more and more tweaking, and a simple, clean design didn't
seem to be falling out. When I left work one Friday, John was preparing
to try to get the direct-BSP approach working properly over the weekend.