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

81 lines
4.9 KiB
Markdown

By the same token, Listing 67.1 is quite a bit more complicated than the
earlier code. The earlier code's HSR consisted of a z-sort of objects,
followed by the drawing of the objects in back-to-front order, one
polygon at a time. Apart from the simple object sorter, all that was
needed was backface culling and a polygon rasterizer.
Listing 67.1 replaces this simple pipeline with a three-stage HSR
process. After backface culling, the edges of each of the polygons in
the scene are added to the global edge list, by way of
**AddPolygonEdges()**. After all edges have been added, the edges are
turned into spans by **ScanEdges()**, with each pixel on the screen
being covered by one and only one span (that is, there's no overdraw).
Once all the spans have been generated, they're drawn by
**DrawSpans()**, and rasterization is complete.
There's nothing tricky about **AddPolygonEdges()**, and **DrawSpans()**,
as implemented in Listing 67.1, is very straightforward as well. In an
implementation that supported texture mapping, however, all the spans
wouldn't be put on one global span list and drawn at once, as is done in
Listing 67.1, because that would result in drawing spans from all the
surfaces in no particular order. (A surface is a drawing object that's
originally described by a polygon, but in **ScanEdges()** there is no
polygon in the classic sense of a set of vertices bounding an area, but
rather just a set of edges and a surface that describes how to draw the
spans outlined by those edges.) That would mean constantly skipping from
one texture to another, which in turn would hurt processor cache
coherency a great deal, and would also incur considerable overhead in
setting up gradient and perspective calculations each time a surface was
drawn. In Quake, we have a linked list of spans hanging off each
surface, and draw all the spans for one surface before moving on to the
next surface.
The core of Listing 67.1, and the most complex aspect of 1/z-sorted
spans, is **ScanEdges()**, where the global edge list is converted into
a set of spans describing the nearest surface at each pixel. This
process is actually pretty simple, though, if you think of it as
follows:
For each scan line, there is a set of active edges, which are those
edges that intersect the scan line. A good part of **ScanEdges()** is
dedicated to adding any edges that first appear on the current scan line
(scan lines are processed from the top scan line on the screen to the
bottom), removing edges that reach their bottom on the current scan
line, and x-sorting the active edges so that the active edges for the
next scan can be processed from left to right. All this is per-scan-line
maintenance, and is basically just linked list insertion, deletion, and
sorting.
The heart of the action is the loop in **ScanEdges()** that processes
the edges on the current scan line from left to right, generating spans
as needed. The best way to think of this loop is as a surface event
processor, where each edge is an event with an associated surface. Each
leading edge is an event marking the start of its surface on that scan
line; if the surface is nearer than the current nearest surface, then a
span ends for the nearest surface, and a span starts for the new
surface. Each trailing edge is an event marking the end of its surface;
if its surface is currently nearest, then a span ends for that surface,
and a span starts for the next-nearest surface (the surface with the
next-largest 1/z at the coordinate where the edge intersects the scan
line). One handy aspect of this event-oriented processing is that
leading and trailing edges do not need to be explicitly paired, because
they are implicitly paired by pointing to the same surface. This saves
the memory and time that would otherwise be needed to track edge pairs.
One more element is required in order for **ScanEdges()** to work
efficiently. Each time a leading or trailing edge occurs, it must be
determined whether its surface is nearest (at a larger 1/z value than
any currently active surface). In addition, for leading edges, the
currently topmost surface must be known, and for trailing edges, it may
be necessary to know the currently next-to-topmost surface. The easiest
way to accomplish this is with a *surface stack*; that is, a linked list
of all currently active surfaces, starting with the nearest surface and
progressing toward the farthest surface, which, as described below, is
always the background surface. (The operation of this sort of edge
event-based stack was described and illustrated in Chapter 66.) Each
leading edge causes its surface to be 1/z-sorted into the surface stack,
with a span emitted if necessary. Each trailing edge causes its surface
to be removed from the surface stack, again with a span emitted if
necessary. As you can see from Listing 67.1, it takes a fair bit of code
to implement this, but all that's really going on is a surface stack
driven by edge events.