364 lines
16 KiB
Markdown
364 lines
16 KiB
Markdown
---
|
|
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
|
author: Michael Abrash
|
|
date: '1997-07-01'
|
|
isbn: '1576101746'
|
|
publisher: The Coriolis Group
|
|
category: 'Web and Software Development: Game Development,Web and Software Development:
|
|
Graphics and Multimedia Development'
|
|
chapter: '56'
|
|
pages: 1053-1059
|
|
---
|
|
|
|
### Fast Texture Mapping: An Implementation {#Heading6}
|
|
|
|
As you might expect, I've implemented DDA texture mapping in X-Sharp,
|
|
and the changes are reflected in the X-Sharp archive in this chapter's
|
|
subdirectory on the listings disk. Listing 56.1 shows the new header
|
|
file entries, and Listing 56.2 shows the actual texture-mapped polygon
|
|
drawer. The set-pixel routine that Listing 56.2 calls is a slight
|
|
modification of the Mode X set-pixel routine from Chapter 47. In
|
|
addition, INITBALL.C has been modified to create three texture-mapped
|
|
polygons and define the texture bitmaps, and modifications have been
|
|
made to allow the user to flip the axis of rotation. You will of course
|
|
need the complete X-Sharp library to see texture mapping in action, but
|
|
Listings 56.1 and 56.2 are the actual texture mapping code in its
|
|
entirety.
|
|
|
|
> 
|
|
> Here's a major tip: DDA texture mapping looks best on fast-moving
|
|
> surfaces, where the eye doesn't have time to pick nits with the shearing
|
|
> and aliasing that's an inevi table by-product of such a crude approach.
|
|
> Compile DEMO1 from the X-Sharp archive in this chapter's subdirectory of
|
|
> the listings disk, and run it. The initial display looks okay, but
|
|
> certainly not great, because the rotational speed is so slow. Now press
|
|
> the S key a few times to speed up the rotation and flip between
|
|
> different rotation axes. I think you'll be amazed at how much better DDA
|
|
> texture mapping looks at high speed. This technique would be great for
|
|
> mapping textures onto hurtling asteroids or jets, but would come up
|
|
> short for slow, finely detailed movements.
|
|
|
|
**LISTING 56.1 L56-1.C**
|
|
|
|
```c
|
|
/* New header file entries related to texture-mapped polygons */
|
|
|
|
/* Draws the polygon described by the point list PointList with a bitmap
|
|
texture mapped onto it */
|
|
#define DRAW_TEXTURED_POLYGON(PointList,NumPoints,TexVerts,TexMap) \
|
|
Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \
|
|
DrawTexturedPolygon(&Polygon, TexVerts, TexMap);
|
|
#define FIXED_TO_INT(FixedVal) ((int) (FixedVal >> 16))
|
|
#define ROUND_FIXED_TO_INT(FixedVal) \
|
|
((int) ((FixedVal + DOUBLE_TO_FIXED(0.5)) >> 16))
|
|
/* Retrieves specified pixel from specified image bitmap of specified width. */
|
|
#define GET_IMAGE_PIXEL(TexMapBits, TexMapWidth, X, Y) \
|
|
TexMapBits[(Y * TexMapWidth) + X]
|
|
/* Masks to mark shading types in Face structure */
|
|
#define NO_SHADING 0x0000
|
|
#define AMBIENT_SHADING 0x0001
|
|
#define DIFFUSE_SHADING 0x0002
|
|
#define TEXTURE_MAPPED_SHADING 0x0004
|
|
/* Describes a texture map */
|
|
typedef struct {
|
|
int TexMapWidth; /* texture map width in bytes */
|
|
char *TexMapBits; /* pointer to texture bitmap */
|
|
} TextureMap;
|
|
|
|
/* Structure describing one face of an object (one polygon) */
|
|
typedef struct {
|
|
int * VertNums; /* pointer to list of indexes of this polygon's vertices
|
|
in the object's vertex list. The first two indexes
|
|
must select end and start points, respectively, of this
|
|
polygon's unit normal vector. Second point should also
|
|
be an active polygon vertex */
|
|
int NumVerts; /* # of verts in face, not including the initial
|
|
vertex, which must be the end of a unit normal vector
|
|
that starts at the second index in VertNums */
|
|
int ColorIndex; /* direct palette index; used only for non-shaded faces */
|
|
ModelColor FullColor; /* polygon's color */
|
|
int ShadingType; /* none, ambient, diffuse, texture mapped, etc. */
|
|
TextureMap * TexMap; /* pointer to bitmap for texture mapping, if any */
|
|
Point * TexVerts; /* pointer to list of this polygon's vertices, in
|
|
TextureMap coordinates. Index n must map to index
|
|
n + 1 in VertNums, (the + 1 is to skip over the unit
|
|
normal endpoint in VertNums) */
|
|
} Face;
|
|
extern void DrawTexturedPolygon(PointListHeader *, Point *, TextureMap *);
|
|
```
|
|
|
|
**LISTING 56.2 L56-2.C**
|
|
|
|
```c
|
|
/* Draws a bitmap, mapped to a convex polygon (draws a texture-mapped polygon).
|
|
"Convex" means that every horizontal line drawn through the polygon at any
|
|
point would cross exactly two active edges (neither horizontal lines nor
|
|
zero-length edges count as active edges; both are acceptable anywhere in
|
|
the polygon), and that the right & left edges never cross. Nonconvex
|
|
polygons won't be drawn properly. Can't fail. */
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include "polygon.h"
|
|
/* Describes the current location and stepping, in both the source and
|
|
the destination, of an edge */
|
|
typedef struct {
|
|
int Direction; /* through edge list; 1 for a right edge (forward
|
|
through vertex list), -1 for a left edge (backward
|
|
through vertex list) */
|
|
int RemainingScans; /* height left to scan out in dest */
|
|
int CurrentEnd; /* vertex # of end of current edge */
|
|
Fixedpoint SourceX; /* current X location in source for this edge */
|
|
Fixedpoint SourceY; /* current Y location in source for this edge */
|
|
Fixedpoint SourceStepX;/* X step in source for Y step in dest of 1 */
|
|
Fixedpoint SourceStepY;/* Y step in source for Y step in dest of 1 */
|
|
/* variables used for all-integer Bresenham's-type
|
|
X stepping through the dest, needed for precise
|
|
pixel placement to avoid gaps */
|
|
int DestX; /* current X location in dest for this edge */
|
|
int DestXIntStep; /* whole part of dest X step per scan-line Y step */
|
|
int DestXDirection; /* -1 or 1 to indicate way X steps (left/right) */
|
|
int DestXErrTerm; /* current error term for dest X stepping */
|
|
int DestXAdjUp; /* amount to add to error term per scan line move */
|
|
int DestXAdjDown; /* amount to subtract from error term when the
|
|
error term turns over */
|
|
} EdgeScan;
|
|
int StepEdge(EdgeScan *);
|
|
int SetUpEdge(EdgeScan *, int);
|
|
void ScanOutLine(EdgeScan *, EdgeScan *);
|
|
int GetImagePixel(char *, int, int, int);
|
|
/* Statics to save time that would otherwise pass them to subroutines. */
|
|
static int MaxVert, NumVerts, DestY;
|
|
static Point * VertexPtr;
|
|
static Point * TexVertsPtr;
|
|
static char * TexMapBits;
|
|
static int TexMapWidth;
|
|
/* Draws a texture-mapped polygon, given a list of destination polygon
|
|
vertices, a list of corresponding source texture polygon vertices, and a
|
|
pointer to the source texture's descriptor. */
|
|
void DrawTexturedPolygon(PointListHeader * Polygon, Point * TexVerts,
|
|
TextureMap * TexMap)
|
|
{
|
|
int MinY, MaxY, MinVert, i;
|
|
EdgeScan LeftEdge, RightEdge;
|
|
NumVerts = Polygon->Length;
|
|
VertexPtr = Polygon->PointPtr;
|
|
TexVertsPtr = TexVerts;
|
|
TexMapBits = TexMap->TexMapBits;
|
|
TexMapWidth = TexMap->TexMapWidth;
|
|
/* Nothing to draw if less than 3 vertices */
|
|
if (NumVerts < 3) {
|
|
return;
|
|
}
|
|
/* Scan through the destination polygon vertices and find the top of the
|
|
left and right edges, taking advantage of our knowledge that vertices run
|
|
in a clockwise direction (else this polygon wouldn't be visible due to
|
|
backface removal) */
|
|
MinY = 32767;
|
|
MaxY = -32768;
|
|
for (i=0; i<NumVerts; i++) {
|
|
if (VertexPtr[i].Y < MinY) {
|
|
MinY = VertexPtr[i].Y;
|
|
MinVert = i;
|
|
}
|
|
if (VertexPtr[i].Y > MaxY) {
|
|
MaxY = VertexPtr[i].Y;
|
|
MaxVert = i;
|
|
}
|
|
}
|
|
/* Reject flat (0-pixel-high) polygons */
|
|
if (MinY >= MaxY) {
|
|
return;
|
|
}
|
|
/* The destination Y coordinate is not edge specific; it applies to
|
|
both edges, since we always step Y by 1 */
|
|
DestY = MinY;
|
|
/* Set up to scan the initial left and right edges of the source and
|
|
destination polygons. We always step the destination polygon edges
|
|
by one in Y, so calculate the corresponding destination X step for
|
|
each edge, and then the corresponding source image X and Y steps */
|
|
LeftEdge.Direction = -1; /* set up left edge first */
|
|
SetUpEdge(&LeftEdge, MinVert);
|
|
RightEdge.Direction = 1; /* set up right edge */
|
|
SetUpEdge(&RightEdge, MinVert);
|
|
/* Step down destination edges one scan line at a time. At each scan
|
|
line, find the corresponding edge points in the source image. Scan
|
|
between the edge points in the source, drawing the corresponding
|
|
pixels across the current scan line in the destination polygon. (We
|
|
know which way the left and right edges run through the vertex list
|
|
because visible (non-backface-culled) polygons always have the vertices
|
|
in clockwise order as seen from the viewpoint) */
|
|
for (;;) {
|
|
/* Done if off bottom of clip rectangle */
|
|
if (DestY >= ClipMaxY) {
|
|
return;
|
|
}
|
|
/* Draw only if inside Y bounds of clip rectangle */
|
|
if (DestY >= ClipMinY) {
|
|
/* Draw the scan line between the two current edges */
|
|
ScanOutLine(&LeftEdge, &RightEdge);
|
|
}
|
|
/* Advance the source and destination polygon edges, ending if we've
|
|
scanned all the way to the bottom of the polygon */
|
|
if (!StepEdge(&LeftEdge)) {
|
|
break;
|
|
}
|
|
if (!StepEdge(&RightEdge)) {
|
|
break;
|
|
}
|
|
DestY++;
|
|
}
|
|
}
|
|
/* Steps an edge one scan line in the destination, and the corresponding
|
|
distance in the source. If an edge runs out, starts a new edge if there
|
|
is one. Returns 1 for success, or 0 if there are no more edges to scan. */
|
|
int StepEdge(EdgeScan * Edge)
|
|
{
|
|
/* Count off the scan line we stepped last time; if this edge is
|
|
finished, try to start another one */
|
|
if (--Edge->RemainingScans == 0) {
|
|
/* Set up the next edge; done if there is no next edge */
|
|
if (SetUpEdge(Edge, Edge->CurrentEnd) == 0) {
|
|
return(0); /* no more edges; done drawing polygon */
|
|
}
|
|
return(1); /* all set to draw the new edge */
|
|
}
|
|
/* Step the current source edge */
|
|
Edge->SourceX += Edge->SourceStepX;
|
|
Edge->SourceY += Edge->SourceStepY;
|
|
/* Step dest X with Bresenham-style variables, to get precise dest pixel
|
|
placement and avoid gaps */
|
|
Edge->DestX += Edge->DestXIntStep; /* whole pixel step */
|
|
/* Do error term stuff for fractional pixel X step handling */
|
|
if ((Edge->DestXErrTerm += Edge->DestXAdjUp) > 0) {
|
|
Edge->DestX += Edge->DestXDirection;
|
|
Edge->DestXErrTerm -= Edge->DestXAdjDown;
|
|
}
|
|
return(1);
|
|
}
|
|
/* Sets up an edge to be scanned; the edge starts at StartVert and proceeds
|
|
in direction Edge->Direction through the vertex list. Edge->Direction must
|
|
be set prior to call; -1 to scan a left edge (backward through the vertex
|
|
list), 1 to scan a right edge (forward through the vertex list).
|
|
Automatically skips over 0-height edges. Returns 1 for success, or 0 if
|
|
there are no more edges to scan. */
|
|
int SetUpEdge(EdgeScan * Edge, int StartVert)
|
|
{
|
|
int NextVert, DestXWidth;
|
|
Fixedpoint DestYHeight;
|
|
for (;;) {
|
|
/* Done if this edge starts at the bottom vertex */
|
|
if (StartVert == MaxVert) {
|
|
return(0);
|
|
}
|
|
/* Advance to the next vertex, wrapping if we run off the start or end
|
|
of the vertex list */
|
|
NextVert = StartVert + Edge->Direction;
|
|
if (NextVert >= NumVerts) {
|
|
NextVert = 0;
|
|
} else if (NextVert < 0) {
|
|
NextVert = NumVerts - 1;
|
|
}
|
|
/* Calculate the variables for this edge and done if this is not a
|
|
zero-height edge */
|
|
if ((Edge->RemainingScans =
|
|
VertexPtr[NextVert].Y - VertexPtr[StartVert].Y) != 0) {
|
|
DestYHeight = INT_TO_FIXED(Edge->RemainingScans);
|
|
Edge->CurrentEnd = NextVert;
|
|
Edge->SourceX = INT_TO_FIXED(TexVertsPtr[StartVert].X);
|
|
Edge->SourceY = INT_TO_FIXED(TexVertsPtr[StartVert].Y);
|
|
Edge->SourceStepX = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].X) -
|
|
Edge->SourceX, DestYHeight);
|
|
Edge->SourceStepY = FixedDiv(INT_TO_FIXED(TexVertsPtr[NextVert].Y) -
|
|
Edge->SourceY, DestYHeight);
|
|
/* Set up Bresenham-style variables for dest X stepping */
|
|
Edge->DestX = VertexPtr[StartVert].X;
|
|
if ((DestXWidth =
|
|
(VertexPtr[NextVert].X - VertexPtr[StartVert].X)) < 0) {
|
|
/* Set up for drawing right to left */
|
|
Edge->DestXDirection = -1;
|
|
DestXWidth = -DestXWidth;
|
|
Edge->DestXErrTerm = 1 - Edge->RemainingScans;
|
|
Edge->DestXIntStep = -(DestXWidth / Edge->RemainingScans);
|
|
} else {
|
|
/* Set up for drawing left to right */
|
|
Edge->DestXDirection = 1;
|
|
Edge->DestXErrTerm = 0;
|
|
Edge->DestXIntStep = DestXWidth / Edge->RemainingScans;
|
|
}
|
|
Edge->DestXAdjUp = DestXWidth % Edge->RemainingScans;
|
|
Edge->DestXAdjDown = Edge->RemainingScans;
|
|
return(1); /* success */
|
|
}
|
|
StartVert = NextVert; /* keep looking for a non-0-height edge */
|
|
}
|
|
}
|
|
/* Texture-map-draw the scan line between two edges. */
|
|
void ScanOutLine(EdgeScan * LeftEdge, EdgeScan * RightEdge)
|
|
{
|
|
Fixedpoint SourceX = LeftEdge->SourceX;
|
|
Fixedpoint SourceY = LeftEdge->SourceY;
|
|
int DestX = LeftEdge->DestX;
|
|
int DestXMax = RightEdge->DestX;
|
|
Fixedpoint DestWidth;
|
|
Fixedpoint SourceXStep, SourceYStep;
|
|
/* Nothing to do if fully X clipped */
|
|
if ((DestXMax <= ClipMinX) || (DestX >= ClipMaxX)) {
|
|
return;
|
|
}
|
|
if ((DestXMax - DestX) <= 0) {
|
|
return; /* nothing to draw */
|
|
}
|
|
/* 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) */
|
|
SourceXStep = FixedDiv(RightEdge->SourceX - SourceX, DestWidth);
|
|
SourceYStep = FixedDiv(RightEdge->SourceY - SourceY, DestWidth);
|
|
/* Clip right edge if necessary */
|
|
if (DestXMax > ClipMaxX) {
|
|
DestXMax = ClipMaxX;
|
|
}
|
|
/* Clip left edge if necssary */
|
|
if (DestX < ClipMinX) {
|
|
SourceX += SourceXStep * (ClipMinX - DestX);
|
|
SourceY += SourceYStep * (ClipMinX - DestX);
|
|
DestX = ClipMinX;
|
|
}
|
|
/* Scan across the destination scan line, updating the source image
|
|
position accordingly */
|
|
for (; DestX<DestXMax; DestX++) {
|
|
/* Get currently mapped pixel out of image and draw it to screen */
|
|
WritePixelX(DestX, DestY,
|
|
GET_IMAGE_PIXEL(TexMapBits, TexMapWidth,
|
|
FIXED_TO_INT(SourceX), FIXED_TO_INT(SourceY)) );
|
|
/* Point to the next source pixel */
|
|
SourceX += SourceXStep;
|
|
SourceY += SourceYStep;
|
|
}
|
|
}
|
|
```
|
|
|
|
No matter how you slice it, DDA texture mapping beats boring,
|
|
single-color polygons nine ways to Sunday. The big downside is that it's
|
|
much slower than a normal polygon fill; move the ball close to the
|
|
screen in DEMO1, and watch things slow down when one of those big
|
|
texture maps comes around. Of course, that's partly because the code is
|
|
all in C; some well-chosen optimizations would work wonders. In the next
|
|
chapter we'll discuss texture mapping further, crank up the speed of our
|
|
texture mapper, and attend to some rough spots that remain in the DDA
|
|
texture mapping implementation, most notably in the area of exactly
|
|
which texture pixels map to which destination pixels as a polygon
|
|
rotates.
|
|
|
|
And, in case you're curious, yes, there is a bear in DEMO1. I wouldn't
|
|
say he looks much like a Pooh-type bear, but he's a bear nonetheless. He
|
|
does tend to look a little startled when you flip the ball around so
|
|
that he's zipping by on his head, but, heck, you would too in the same
|
|
situation. And remember, when you buy the next VGA megahit, *Bears in
|
|
Space*, you saw it here first.
|