--- title: Michael Abrash's Graphics Programming Black Book, Special Edition author: Michael Abrash date: '1997-07-01' identifier: - scheme: ISBN text: 1576101746 publisher: The Coriolis Group category: 'Web and Software Development: Game Development,Web and Software Development: Graphics and Multimedia Development' chapter: '57' pages: 1061-1075 --- ## Chapter 57 -- 10,000 Freshly Sheared Sheep on the Screen ### The Critical Role of Experience in Implementing Fast, Smooth Texture Mapping I recently spent an hour or so learning how to shear a sheep. Among other things, I learned—in great detail—about the importance of selecting the proper comb for your shears, heard about the man who holds the world's record for sheep sheared in a day (more than 600, if memory serves), and discovered, Lord help me, the many and varied ways in which the New Zealand Sheep Shearing Board improves the approved sheep-shearing method every year. The fellow giving the presentation did his best, but let's face it, sheep just aren't very interesting. If you have children, you'll know why I was there; if you don't, there's no use explaining. The chap doing the shearing did say one thing that stuck with me, although it may not sound particularly profound. (Actually, it sounds pretty silly, but bear with me.) He said, "You don't get really good at sheep shearing for 10 years, or 10,000 sheep." I'll buy that. In fact, to extend that morsel of wisdom to the greater, non-ovine-centric universe, it actually takes a good chunk of experience before you get good at anything worthwhile—especially graphics, for a couple of reasons. First, performance matters a lot in graphics, and performance programming is largely a matter of experience. You can't speed up PC graphics simply by looking in a book for a better algorithm; you have to understand the code C compilers generate, assembly language optimization, VGA hardware, and the performance implications of various graphics-programming approaches and algorithms. Second, computer graphics is a matter of illusion, of convincing the eye to see what you want it to see, and that's very much a black art based on experience. #### Visual Quality: A Black Hole ... Er, Art Pleasing the eye with realtime computer animation is something less than a science, at least at the PC level, where there's a limited color palette and no time for antialiasing; in fact, sometimes it can be more than a little frustrating. As you may recall, in the previous chapter I implemented texture mapping in X-Sharp. There was plenty of experience involved there, some of which I didn't mention. My first implementation was disappointing; the texture maps shimmied and sheared badly, like a loosely affiliated flock of pixels, each marching to its own drummer. Then, I added a control key to speed up the rotation; what a difference! The aliasing problems were still there, but with the faster rotation, the pixels moved too quickly for the eye to pick up on the aliasing; the rotating texture maps, and the rotating ball as a whole, crossed the threshold into being accepted by the eye as a viewed object, rather than simply a collection of pixels. The obvious lesson here is that adequate speed is important to convincing animation. There's another, less obvious side to this lesson, though. I'd been running the texture-mapping demo on a 20 MHz 386 with a slow VGA when I discovered the beneficial effects of greater animation speed. When, some time later, I ran the demo on a 33 MHz 486 with a fast VGA, I found that the faster rotation was too fast! The ball spun so rapidly that the eye couldn't blend successive images together into continuous motion, much like watching a badly flickering movie. > ![](images/i.jpg) > So the second lesson is that either too little or too much speed can > destroy the illusion. Unless you're antialiasing, you need to tune the > shifting of your images so that they're in the "sweet spot" of apparent > motion, in which the eye is willing to ignore the jumping and aliasing, > and blend the images together into continuous motion. Only experience > can give you a feel for that sweet spot. #### Fixed-Point Arithmetic, Redux In the previous chapter I added texture mapping to X-Sharp, but lacked space to explain some of its finer points. I'll pick up the thread now and cover some of those points here, and discuss the visual and performance enhancements that previous chapter's code needed—and which are now present in the version of X-Sharp in this chapter's subdirectory on the CD-ROM. Back in Chapter 38, I spent a good bit of time explaining exactly which pixels were inside a polygon and which were outside, and how to draw those pixels accordingly. This was important, I said, because only with a precise, consistent way of defining inside and outside would it be possible to draw adjacent polygons without either overlap or gaps between them. As a corollary, I added that only an all-integer, edge-stepping approach would do for polygon filling. Fixed-point arithmetic, although alluring for speed and ease of use, would be unacceptable because round-off error would result in imprecise pixel placement. More than a year then passed between the time I wrote that statement and the time I implemented X-Sharp's texture mapper, during which time my long-term memory apparently suffered at least partial failure. When I went to implement texture mapping for the previous chapter, I decided that since transformed destination vertices can fall at fractional pixel locations, the cleanest way to do the texture mapping would be to use fixed-point coordinates for both the source texture and the destination screen polygon. That way, there would be a minimum of distortion as the polygon rotated and moved. Theoretically, that made sense; but there was one small problem: gaps between polygons. Yes, folks, I had ignored the voice of experience (my own voice, at that) at my own peril. You can be assured I will not forget this particular lesson again: Fixed-point arithmetic is not precise. That's not to say that it's impossible to use fixed-point for drawing polygons; if all adjacent edges share common start and end vertices and common edges are always stepped in the same direction, all polygons should share the same fixed-point imprecision, and edges should fit properly (although polygons may not include exactly the right pixels). What you absolutely cannot do is mix fixed-point and all-integer polygon-filling approaches when drawing, as shown in Figure 57.1. Consequently, I ended up using an all-integer approach in X-Sharp for stepping through the destination polygon. However, I kept the fixed-point approach, which is faster and much simpler, for stepping through the source. Why was it all right to mix approaches in this case? Precise pixel placement only matters when drawing; otherwise, we can get gaps, which are very visible. When selecting a pixel to copy from the source texture, however, the worst that happens is that we pick the source pixel next to the one we really want, causing the mapped texture to appear to have shifted by one pixel at the corresponding destination pixel; given all the aliasing and shearing already going on in the texture-mapping process, a one-pixel mapping error is insignificant. Experience again: It's the difference between knowing which flaws (like small texture shifts) can reasonably be ignored, and which (like those that produce gaps between polygons) must be avoided at all costs. #### Texture Mapping: Orientation Independence The double-DDA texture-mapping code presented in the previous chapter worked adequately, but there were two things about it that left me less than satisfied. One flaw was performance; I'll address that shortly. The other flaw was the way textures shifted noticeably as the orientations of the polygons onto which they were mapped changed. The previous chapter's code followed the standard polygon inside/outside rule for determining which pixels in the source texture map were to be mapped: Pixels that mapped exactly to the left and top destination edges were considered to be inside, and pixels that mapped exactly to the right and bottom destination edges were considered to be outside. That's fine for filling polygons, but when copying texture maps, it causes different edges of the texture map to be omitted, depending on the destination orientation, because different edges of the texture map correspond to the right and bottom destination edges, depending on the current rotation. Also, the previous chapter's code truncated to get integer source coordinates. This, together with the orientation problem, meant that when a texture turned upside down, it slowed one new row and one new column of pixels from the next row and column of the texture map. This asymmetry was quite visible, and not at all the desired effect. ![**Figure 57.1**  *Gaps caused by mixing fixed-point and all-integer math.*](images/57-01.jpg) Listing 57.1 is one solution to these problems. This code, which replaces the equivalently named function presented in the previous chapter (and, of course, is present in the X-Sharp archive in this chapter's subdirectory of the listings disk), makes no attempt to follow the standard polygon inside/outside rules when mapping the source. Instead, it advances a half-step into the texture map before drawing the first pixel, so pixels along all edges are half included. Rounding rather than truncation to texture-map coordinates is also performed. The result is that the texture map stays pretty much centered within the destination polygon as the destination rotates, with a much-reduced level of orientation-dependent asymmetry. **LISTING 57.1 L57-1.C** ```c /* Texture-map-draw the scan line between two edges. Uses approach of pre-stepping 1/2 pixel into the source image and rounding to the nearest source pixel at each step, so that texture maps will appear reasonably similar at all angles. */ void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge) { Fixedpoint SourceX; Fixedpoint SourceY; int DestX = LeftEdge->DestX; int DestXMax = RightEdge->DestX; Fixedpoint DestWidth; Fixedpoint SourceStepX, SourceStepY; /* Nothing to do if fully X clipped */ if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) { return; } if ((DestXMax - DestX) <= 0) { return; /* nothing to draw */ } SourceX = LeftEdge->SourceX; SourceY = LeftEdge->SourceY; /* Width of destination scan line, for scaling. Note: because this is an integer-based scaling, it can have a total error of as much as nearly one pixel. For more precise scaling, also maintain a fixed-point DestX in each edge, and use it for scaling. If this is done, it will also be necessary to nudge the source start coordinates to the right by an amount corresponding to the distance from the the real (fixed-point) DestX and the first pixel (at an integer X) to be drawn). */ DestWidth = INT-TO-FIXED(DestXMax - DestX); /* Calculate source steps that correspond to each dest X step (across the scan line) */ SourceStepX = FixedDiv(RightEdge->SourceX - SourceX, DestWidth); SourceStepY = FixedDiv(RightEdge->SourceY - SourceY, DestWidth); /* Advance 1/2 step in the stepping direction, to space scanned pixels evenly between the left and right edges. (There's a slight inaccuracy in dividing negative numbers by 2 by shifting rather than dividing, but the inaccuracy is in the least significant bit, and we'll just live with it.) */ SourceX += SourceStepX >> 1; SourceY += SourceStepY >> 1; /* Clip right edge if necssary */ if (DestXMax > ClipMaxX) DestXMax = ClipMaxX; /* Clip left edge if necssary */ if (DestX < ClipMinX) { SourceX += FixedMul(SourceStepX, INT-TO-FIXED(ClipMinX - DestX)); SourceY += FixedMul(SourceStepY, INT-TO-FIXED(ClipMinX - DestX)); DestX = ClipMinX; } /* Scan across the destination scan line, updating the source image position accordingly */ for (; DestX