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

113 lines
7.5 KiB
Markdown

### Passages: The Last-Minute Change that Didn't Happen {#Heading4}
Earlier, I mentioned that we almost changed 3-D engines again in the
last month of Quake's development. Here's what happened: One of the
alternatives to the PVS is the use of *portals*, where the focus is on
the places where polygons don't exist along leaf faces, rather than the
more usual focus on the polygons themselves. These "empty" places are
themselves polygons, called portals, that describe all the places that
visibility can pass from one leaf to another. Portals are used by the
PVS generator to determine visibility, and are used in other 3-D engines
as the primary mechanism for determining leaf or sector visibility. For
example, portals can be projected to screenspace, then used as a 2-D
clipping region to restrict drawing of more distant polygons to only
those that are visible through the portal. Or, as in Quake's
preprocessor, visibility boundary planes can be constructed from one
portal to the next, and 3-D clipping to those planes can be used to
determine visible polygons or leaves. Used either way, portals can
support more changeable worlds than the PVS, because, unlike the PVS,
the portals themselves can easily be changed on the fly.
The problem with portal-based visibility is that it tends to perform at
its worst in complex scenes, which can have many, many portals. Since
those are the most expensive scenes to draw, as well, portals tend to
worsen the worst case. However, late in Quake's development, John
realized that the approach of storing portals themselves in the world
database could readily be improved upon. (To be clear, Quake wasn't
using portals at that point, and didn't end up using them.) Since the
aforementioned sets of 3-D visibility clipping planes *between*
portals—which he named *passages*—were what actually got used for
visibility, if he stored those, instead of generating them dynamically
from the portals, he would be able to do visibility much faster than
with standard portals. This would give a significantly tighter polygon
set than the PVS, because it would be based on visibility through the
passages from the viewpoint, rather than the PVS's approach of
visibility from anywhere in the leaf, and that would be a considerable
help, because the level designers were running right up against
performance limits, partly because of the PVS's relatively loose polygon
set. John immediately decided that passages-based visibility was a
sufficiently superior approach that if it worked out, he would switch
Quake to it, even at that late stage, and within a weekend, he had
implemented it and had it working—only to find that, like portals, it
improved best cases but worsened worst cases, and overall wasn't a win
for Quake. In truth, given how close we were to shipping, John was as
much thankful as disappointed that passages didn't work out, but the
possibilities were too great for us not to have taken a shot at it.
So why even bother mentioning this? Partly to show that not every
interesting idea pans out; I tend to discuss those that *did* pan out,
and it's instructive to point out that many ideas don't. That doesn't
mean you shouldn't try promising ideas, though. First, some do pan out,
and you'll never know which unless you try. Second, an idea that doesn't
work out in one case can still be filed away for another case. It's
quite likely that passages will be useful in a different context in a
future engine.
The more approaches you try, the larger your toolkit and the broader
your understanding will be when you tackle your next project.
### Drawing the World {#Heading5}
Everything described so far is a preprocessing step. When Quake is
actually running, the world is drawn as follows: First, the PVS for the
view leaf is decompressed, and each leaf flagged as visible is marked as
being in the current frame's PVS. (The marking is done by storing the
current frame's number in the leaf; this avoids having to clear the PVS
marking each frame.) All the parent nodes of each leaf in the PVS are
also marked; this information could have been stored as additional PVS
flags, but to save space is bubbled up the BSP from each visible leaf.
After the PVS is marked, the BSP is walked front-to-back. At each node,
the bounding box of the node's subspace is clipped against the view
frustum; if the bounding box is fully clipped, then that node and all
its children are ignored. Likewise, if the node is not in the PVS for
the current viewpoint leaf, the node and all its children are ignored.
If the bounding box is partially clipped or not clipped at all, that
information is passed to the children so that any unnecessary clip tests
can be avoided. The children in front of the node are then processed
recursively. When a leaf is reached, polygons that touch that leaf are
marked as potentially drawable. When recursion in front of a node is
finished, all polygons on the front side of the node that are marked as
potentially drawable are added to the edge list, and then the children
on the back side of that node are similarly processed recursively.
The edge list is a special, intermediate step between polygons and
drawing. Each polygon is clipped, transformed, and projected, and its
non-horizontal edges are added to a global list of potentially drawable
edges. After all the potentially drawable edges in the world have been
added, the global edge list is scanned out all at once, and all the
visible spans (the nearest spans, as determined by sorting on BSP-walk
order) in the world are emitted into span lists linked off the
respective surface descriptors (for now, you can think of a surface as
being the same as a polygon). Taken together, these spans cover every
pixel on the screen once and only once, resulting in zero overdraw;
surfaces that are completely hidden by nearer surfaces generate no spans
at all. The spans are then drawn; all the spans for one surface are
drawn, and then all the spans for the next, so that there's texture
coherency between spans, which is very helpful for processor cache
coherency, and also to reduce setup overhead.
The primary purpose of the edge list is to make Quake's performance as
level—that is, as consistent—as possible. Compared to simply drawing all
potentially drawable polygons front-to-back, the edge list certainly
slows down the best case, that is, when there's no overdraw. However, by
eliminating overdraw, the worst case is helped considerably; in Quake,
there's a ratio of perhaps 4:1 between worst and best case drawing time,
versus the 10:1 or more that can happen with straight polygon drawing.
Leveling is very important, because cases where a game slows down to the
point of being unplayable dictate game and level design, and the fewer
constraints placed on design, the better.
------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *A corollary is that best case performance can be seductively misleading; it's a great feeling to see a scene running at 30 or even 60 frames per second, but if the bulk of the game runs at 15 fps, those best cases are just going to make the rest of the game look worse.*
------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------