93 lines
5.4 KiB
Markdown
93 lines
5.4 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](67-03.html) [Table of Contents](index.html) [Next](67-05.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
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.
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](67-03.html) [Table of Contents](index.html) [Next](67-05.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|