Combine multi-page chapters into single file
This commit is contained in:
parent
209ba6d8be
commit
1b6f8970e1
421 changed files with 53162 additions and 56308 deletions
364
56-03.md
364
56-03.md
|
|
@ -1,364 +0,0 @@
|
|||
---
|
||||
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.
|
||||
Loading…
Reference in a new issue