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

110 lines
6.3 KiB
Markdown

Chapter 67\
Sorted Spans in Action {#Heading1}
-----------------------
### Implementing Independent Span Sorting for Rendering without Overdraw {#Heading2}
In Chapter 66, we dove headlong into the intricacies of hidden surface
removal by way of z-sorted (actually, 1/z-sorted) spans. At the end of
that chapter, I noted that we were currently using 1/z-sorted spans in
Quake, but it was unclear whether we'd switch back to BSP order. Well,
some time after that writing, it's become clear: We're back to sorting
spans by BSP order.
In Robert A. Heinlein's wonderful story "The Man Who Sold the Moon," the
chief engineer of the Moon rocket project tries to figure out how to get
a payload of three astronauts to the Moon and back. He starts out with a
four-stage rocket design, but finds that it won't do the job, so he adds
a fifth stage. The fifth stage helps, but not quite enough, "Because,"
he explains, "I've had to add in too much dead weight, that's why." (The
dead weight is the control and safety equipment that goes with the fifth
stage.) He then tries adding yet another stage, only to find that the
sixth stage actually results in a net slowdown. In the end, he has to
give up on the three-person design and build a one-person spacecraft
instead.
1/z-sorted spans in Quake turned out pretty much the same way, as we'll
see in a moment. First, though, I'd like to note up front that this
chapter is very technical and builds heavily on material I covered
earlier in this section of the book; if you haven't already read
Chapters 59 through 66, you really should. Make no mistake about it,
this is commercial-quality stuff; in fact, the code in this chapter uses
the same sorting technique as the test version of Quake, QTEST1.ZIP,
that id Software placed on the Internet in early March 1996. This
material is the Real McCoy, true reports from the leading edge, and I
trust that you'll be patient if careful rereading and some occasional
catch-up reading of earlier chapters are required to absorb everything
contained herein. Besides, the ultimate reference for any design is
working code, which you'll find, in part, in Listing 67.1, and in its
entirety in the file DDJZSORT.ZIP on the CD-ROM.
### Quake and Sorted Spans {#Heading3}
As you'll recall from Chapter 66, Quake uses sorted spans to get zero
overdraw while rendering the world, thereby both improving overall
performance and leveling frame rates by speeding up scenes that would
otherwise experience heavy overdraw. Our original design used spans
sorted by BSP order; because we traverse the world BSP tree from
front-to-back relative to the viewpoint, the order in which BSP nodes
are visited is a guaranteed front-to-back sorting order. We simply gave
each node an increasing BSP sequence number as it was visited, set each
polygon's sort key to the BSP sequence number of the node (BSP splitting
plane) it lay on, and used those sort keys when generating spans.
(In a change from earlier designs, polygons now are stored on nodes,
rather than leaves, which are the convex subspaces carved out by the BSP
tree. Visits to potentially visible leaves are used only to mark that
the polygons that touch those leaves are visible and need to be drawn,
and each marked-visible polygon is then drawn after everything in front
of its node has been drawn. This results in less BSP splitting of
polygons, which is A Good Thing, as explained below.)
This worked flawlessly for the world, but had a couple of downsides.
First, it didn't address the issue of sorting small, moving BSP models
such as doors; those models could be clipped into the world BSP tree's
leaves and assigned sort keys corresponding to the leaves into which
they fell, but there was still the question of how to sort multiple BSP
models in the same world leaf against each other. Second, strict BSP
order requires that polygons be split so that every polygon falls
entirely within a single leaf. This can be stretched by putting polygons
on nodes, allowing for larger polygons on average, but even then,
polygons still need to be split so that every polygon falls within the
bounding volume for the node on which it lies. The end result, in either
case, is more and smaller polygons than if BSP order weren't used—and
that, in turn, means lower performance, because more polygons must be
clipped, transformed, and projected, more sorting must be done, and more
spans must be drawn.
We figured that if only we could avoid those BSP splits, Quake would get
a lot faster. Accordingly, we switched from sorting on BSP order to
sorting on 1/z, and left our polygons unsplit. Things did get faster at
first, but not as much as we had expected, for two reasons.
First, as the world BSP tree is descended, we clip each node's bounding
box in turn to see if it's inside or outside each plane of the view
frustum. The clipping results can be remembered, and often allow the
avoidance of some or all clipping for the node's polygons. For example,
all polygons in a node that has a trivially accepted bounding box are
likewise guaranteed to be unclipped and in the frustum, since they all
lie within the node's volume and need no further clipping. This
efficient clipping mechanism vanished as soon as we stepped out of BSP
order, because a polygon was no longer necessarily confined to its
node's volume.
Second, sorting on 1/z isn't as cheap as sorting on BSP order, because
floating-point calculations and comparisons are involved, rather than
integer compares. So Quake got faster but, like Heinlein's fifth rocket
stage, there was clear evidence of diminishing returns.
That wasn't the bad part; after all, even a small speed increase is A
Good Thing. The real problem was that our initial 1/z sorting proved to
be unreliable. We first ran into problems when two forward-facing
polygons started at a common edge, because it was hard to tell which one
was really in front (as discussed below), and we had to do additional
floating-point calculations to resolve these cases. This fixed the
problems for a while, but then odd cases started popping up where just
the right combination of polygon alignments caused new sorting errors.
We tinkered with those too, adding more code and incurring additional
slowdowns in the process. Finally, we had everything working smoothly
again, although by this point Quake was back to pretty much the same
speed it had been with BSP sorting.