81 lines
4.9 KiB
Markdown
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.
|