1 2d graphics cis 487/587 bruce r. maxim um-dearborn
TRANSCRIPT
2
Vector Graphics
• Advantages– No jagged lines– Can only draw what is on the screen– Control electron gun directly (very fast)– Store line end points only
• Disadvantages– Best for wire frames– Have to draw everything as lines including circles– Can’t use TV technology
3
Raster Graphics
• Advantages– Cheaper– Can easily draw solid surfaces– Can move blocks and images around– Can control individual pixels
• Disadvantages– Memory intensive– Aliasing problems
4
Jaggies
• Here’s the problem with raster graphics
• A diagonal line does not always pass through the center of the pixels
5
This won’t work
• Compute the slope of the line
• Plot (x0, y0)
• Advance xi = xi + 1
• Advance yi = yi + 1
• Plot (xi, yi)
• Repeat
6
Bresenhem’s Algorithm
1. Starts at x0, y0
2. Plots x0, y0
3. xi = xi + 1
4. When advancing yi decide between plotting
(xi, yi) or (xi,yi - 1)
Note: Algorithm treats separately
m < 1 (<45°) and m > 1 (>45°)
Quadrants II, III, IV derived from I by symmetry
8
Draw_Line( )int Draw_Line(int x0, int y0, // starting position int x1, int y1, // ending position UCHAR color, // color index UCHAR *vb_start, int lpitch) // video buffer & memory pitch{ // function draws a line from xo,yo to x1,y1 using differential error // terms (based on Bresenahams work) int dx, // difference in x's dy, // difference in y's dx2, // dx,dy * 2 dy2, x_inc, // amount in pixel space to move during drawing y_inc, // amount in pixel space to move during drawing error, // the discriminant i.e. error i.e. decision
variable index; // used for looping // pre-compute first pixel address in video buffer vb_start = vb_start + x0 + y0*lpitch;
9
Draw_Line( )
// compute horizontal and vertical deltas
dx = x1-x0;
dy = y1-y0; // test which direction the line is going in i.e. slope angle
if (dx>=0)
{
x_inc = 1;
} // end if line is moving right
else
{
x_inc = -1;
dx = -dx; // need absolute value
} // end else moving left
10
DrawLine( ) // now based on which delta is greater we can draw the line if (dx > dy) { error = dy2 - dx; // initialize error term for (index=0; index <= dx; index++) // draw the line { *vb_start = color; // set the pixel // test if error has overflowed if (error >= 0) { error-=dx2; // move to next line vb_start+=y_inc;
} // end if error overflowed error+=dy2; // adjust the error term vb_start+=x_inc; // move to next pixel } // end for } // end if |slope| <= 1
11
DrawLine( ) else { error = dx2 - dy; // initialize error term for (index=0; index <= dy; index++) // draw the line { *vb_start = color; // set the pixel if (error >= 0) // test if error overflowed { error-=dy2; vb_start+=x_inc; // move to next line } // end if error overflowed error+=dx2; // adjust the error term vb_start+=y_inc; // move to next pixel } // end for } // end else |slope| > 1 return(1); // return success} // end Draw_Line
12
DrawLine( )
// test y component of slope
if (dy>=0)
{
y_inc = lpitch;
} // end if line is moving down
else
{
y_inc = -lpitch;
dy = -dy; // need absolute value
} // end else moving up
// compute (dx,dy) * 2
dx2 = dx << 1;
dy2 = dy << 1;
13
Clipping
• Goal is to only display the part of the image that is really viewable on the portion of the screen used for drawing
• Approaches:– Border– Image space– Object space
14
Border Clipping
• Create a border that is a wide as any movable screen object
• Only draw the object
• Still need to detect when object will be off-screen
• Requires more memory
15
Image Space Clipping
• Image space (pixel level representation of complete image)
• Test each point to see if it is in the region before trying to draw it
• Easy to implement• Works for all objects• Works for sub regions• Requires lots of computation for each pixel
16
Object Space Clipping
• Object space (representation of figures)• Change object to one that does not need to
be clipped (e.g. chop triangle into a trapezoid) • New object passed to graphics engine• Without testing for clipping• More efficient than image space clipping• Lines are easy• Concave objects are tough
17
Cohen Sutherland
• Each region is assigned a bit code• End points P1and P2 • If y < minY or y > maxY or x < minX or x >
maxX then save Sign bits of P1 and P2
P2
18
Cohen Sutherland
• Accept the point if P1 + P2 = 0
• Reject the point if P1 & P2 != 0
Note: It is best to break up the line so that line segment only occupies a single region
19
Draw_Clip_Line( )int Draw_Clip_Line(int x0,int y0, int x1, int y1, UCHAR color, UCHAR *dest_buffer, int lpitch){ // this helper function draws a clipped line int cxs, cys, cxe, cye; // clip and draw each line cxs = x0; cys = y0; cxe = x1; cye = y1; // clip the line if (Clip_Line(cxs,cys,cxe,cye)) // use Bresenham like before
Draw_Line(cxs, cys, cxe,cye,color,dest_buffer,lpitch); return(1); // return success} // end Draw_Clip_Line
20
Clip_Line( )int Clip_Line(int &x1,int &y1,int &x2, int &y2){ // function clips the line using globally defined clipping region // internal clipping codes #define CLIP_CODE_C 0x0000 #define CLIP_CODE_N 0x0008 #define CLIP_CODE_S 0x0004 #define CLIP_CODE_E 0x0002 #define CLIP_CODE_W 0x0001 #define CLIP_CODE_NE 0x000a #define CLIP_CODE_SE 0x0006 #define CLIP_CODE_NW 0x0009 #define CLIP_CODE_SW 0x0005 int xc1=x1, yc1=y1, xc2=x2, yc2=y2; int p1_code=0, p2_code=0;
21
Clip_Line( )// determine codes for p1 and p2if (y1 < min_clip_y)
p1_code|=CLIP_CODE_N;else if (y1 > max_clip_y)
p1_code|=CLIP_CODE_S;if (x1 < min_clip_x)
p1_code|=CLIP_CODE_W;else if (x1 > max_clip_x)
p1_code|=CLIP_CODE_E; if (y2 < min_clip_y)
p2_code|=CLIP_CODE_N;else if (y2 > max_clip_y)
p2_code|=CLIP_CODE_S;if (x2 < min_clip_x)
p2_code|=CLIP_CODE_W;else if (x2 > max_clip_x)
p2_code|=CLIP_CODE_E;
22
Clip_Line( )
// try and trivially reject
if ((p1_code & p2_code))
return(0);
// test for totally visible, if so leave points untouched
if (p1_code==0 && p2_code==0)
return(1);
// determine end clip point for p1
…
// determine end clip point for p2
…
23
Clip_Line( ) // do bounds check if ((xc1 < min_clip_x) || (xc1 > max_clip_x) ||
(yc1 < min_clip_y) || (yc1 > max_clip_y) || (xc2 < min_clip_x) || (xc2 > max_clip_x) || (yc2 < min_clip_y) || (yc2 > max_clip_y) )
{ return(0);
} // end if // store vars back x1 = xc1; y1 = yc1; x2 = xc2; y2 = yc2; return(1);} // end Clip_Line
24
Polygons
• Defined by vertices
• Closed (all line connected)
• Simply draw them one line at a time
• Can be convex or concave
• Attributes:Number of vertices, color, position, list of
vertices (x, y) form
25
Draw_Polygon2D( )if (poly->state) // test if the polygon is visible
{
// loop thru and draw a line from vertices 1 to n
for (int index=0; index < poly->num_verts-1; index++)
{
// draw line from ith to ith+1 vertex
Draw_Clip_Line(poly->vlist[index].x+poly->x0, poly->vlist[index].y+poly->y0,
poly->vlist[index+1].x+poly->x0, poly->vlist[index+1].y+poly->y0,
poly->color, vbuffer, lpitch);
} // end for
// draw line from last vertex to 0th
Draw_Clip_Line(poly->vlist[0].x+poly->x0, poly->vlist[0].y+poly->y0,
poly->vlist[index].x+poly->x0, poly->vlist[index].y+poly->y0,
poly->color, vbuffer, lpitch);
return(1);
} // end if
else
return(0);
26
Moving Objects
• If you move an object do you need to change every vertex coordinate?
• Yes, if you use world coordinates (meaning the screen coordinate system)
• No, if you local coordinates (meaning local to the object itself)– Example: triangle at (4,0) might have local
vertex coordinates (0,1), (-1,-1), (1,-1)
27
Translation (Moving)
• If you want to move a point (x0 , y0) to a new position (xt , yt)
• The transformation would bext = x0 + dx and yt = y0 + dy
• For motion dx and dy are the components of the velocity vectordx = cos v and dy = - sin v
28
Game_Main( )// draw all the asteroidsfor (int curr_index = 0; curr_index < NUM_ASTEROIDS; curr_index++){ // glow asteroids asteroids[curr_index].color = rand()%256; // do the graphics Draw_Polygon2D(&asteroids[curr_index], (UCHAR *)ddsd.lpSurface, ddsd.lPitch); // move the asteroid without matrix operations asteroids[curr_index].x0+=asteroids[curr_index].xv; asteroids[curr_index].y0+=asteroids[curr_index].yv; // test for out of bounds if (asteroids[curr_index].x0 > SCREEN_WIDTH+100) asteroids[curr_index].x0 = - 100; if (asteroids[curr_index].y0 > SCREEN_HEIGHT+100) asteroids[curr_index].y0 = - 100 if (asteroids[curr_index].x0 < -100) asteroids[curr_index].x0 = SCREEN_WIDTH+100; if (asteroids[curr_index].y0 < -100) asteroids[curr_index].y0 = SCREEN_HEIGHT+100;} // end for curr_asteroid
29
Scaling (Changing Size)
• Multiply the coordinates of each vertex by the scale factor
• Everything will expand from the center
• The transformation would bext = x0 * scale and yt = y0 * scale
30
Scale_Polygon2D( )int Scale_Polygon2D(POLYGON2D_PTR poly, float sx, float sy){ // this function scalesthe local coordinates of the polygon // test for valid pointer if (!poly) return(0); // loop and scale each point for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // scale and store result back poly->vlist[curr_vert].x *= sx; poly->vlist[curr_vert].y *= sy; } // end for curr_vert // return success return(1);} // end Scale_Polygon2D
31
Game_Main( )
// do the graphics
Draw_Polygon2D(&asteroid, (UCHAR *)ddsd.lpSurface, ddsd.lPitch);
// test for scale
if (KEYDOWN('A')) // scale up
Scale_Polygon2D(&asteroid, 1.1, 1.1);
else
if (KEYDOWN('S')) // scale down
Scale_Polygon2D(&asteroid, 0.9, 0.9);
// rotate the polygon by 5 degrees
Rotate_Polygon2D(&asteroid, 5);
32
Rotation (Turning)
• Spin and object around it centered around the z-axis
• Rotate each point the same angle– Positive angles are clockwise– Negative angles are counterclockwise– C++ uses radians (not degrees)
• The transformation would bext = x0 * cos() – y0 * sin()
yt = y0 * cos() + x0 * sin()
33
Rotate_Poloygon2D( )int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta){ // function rotates the local coordinates of the polygon – no matrices // test for valid pointer if (!poly) return(0); // loop and rotate each point, very crude, no lookup!!! for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // perform rotation float xr = (float)poly->vlist[curr_vert].x*cos_look[theta] - (float)poly->vlist[curr_vert].y*sin_look[theta]; float yr = (float)poly->vlist[curr_vert].x*sin_look[theta] + (float)poly->vlist[curr_vert].y*cos_look[theta]; // store result back poly->vlist[curr_vert].x = xr; poly->vlist[curr_vert].y = yr; } // end for curr_vert // return success return(1);} // end Rotate_Polygon2D
34
Summary
• Drawing polygons is simply a matter of drawing lines and connecting end points
• Clipping is used to remove images outside viewing area
• Center polygons at (x0 , y0) and use local coordinates rather than world coordinates for the vertices
35
World Coordinates
• When using world coordinates you always need to perform the following steps– Translate to origin– Perform rotation– Translate back to new position
• It’s always a good idea to put sin(x) and cos(x) values in a lookup table to avoid recomputing them
36
Matrix Operations
• Translation | 1 0 0||x y 1| * | 0 1 0| |dx dy 1|
• Scaling
|sx 0 0||x y 1| * | 0 sy 0| | 0 0 1|
37
Matrix Operations
• Rotation |cos(a) -sin(a) 0||x y 1| * |sin(a) cos(a) 0| | 0 0 1|
• (S * R) * T
| sx*cos(a) –sx*sin(a) 0||x y 1| * | sy*sin(a) sy*cos(a) 0| | dx dy 1|
38
Mat_Init_3x2( )
inline int Mat_Init_3X2(MATRIX3X2_PTR ma,
float m00, float m01,
float m10, float m11,
float m20, float m21)
{
// fills a 3x2 matrix with the sent data in row major form
ma->M[0][0] = m00; ma->M[0][1] = m01;
ma->M[1][0] = m10; ma->M[1][1] = m11;
ma->M[2][0] = m20; ma->M[2][1] = m21;
// return success
return(1);
} // end Mat_Init_3X2
39
Mat_Mul1x2_3x2( )int Mat_Mul1X2_3X2(MATRIX1X2_PTR ma, MATRIX3X2_PTR mb, MATRIX1X2_PTR mprod){// function multiplies a 1x2 matrix against a 3x2 matrix - ma*mb and// stores result using a dummy element for the 3rd element of the 1x2 for (int col=0; col<2; col++) { // compute dot product from row of ma and column of mb float sum = 0; // used to hold result for (int index=0; index<2; index++) { sum+=(ma->M[index]*mb->M[index][col]); // add next product pair } // end for index sum += mb->M[index][col]; // add in last element * 1 mprod->M[col] = sum; // insert resulting col element } // end for col return(1);} // end Mat_Mul_1X2_3X2
40
Matrix Translationint Translate_Polygon2D_Mat(POLYGON2D_PTR poly, int dx, int dy){ // function translates center polygon by using a matrix multiply if (!poly) return(0); // test for valid pointer MATRIX3X2 mt; // used to hold translation transform matrix // initialize the matrix with translation values dx dy Mat_Init_3X2(&mt,1,0, 0,1, dx, dy); // create a 1x2 matrix to do the transform MATRIX1X2 p0 = {poly->x0, poly->y0}; MATRIX1X2 p1 = {0,0}; // this will hold result // now translate via a matrix multiply Mat_Mul1X2_3X2(&p0, &mt, &p1); // now copy the result back into polygon poly->x0 = p1.M[0]; poly->y0 = p1.M[1]; // return success return(1);} // end Translate_Polygon2D_Mat
41
Matrix Scalingint Scale_Polygon2D_Mat(POLYGON2D_PTR poly, float sx, float sy){ // this function scales the local coordinates of the polygon if (!poly) return(0); // test for valid pointer MATRIX3X2 ms; // used to hold scaling transform matrix Mat_Init_3X2(&ms, sx, 0, 0, sy, 0, 0); // loop and scale each point for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++) { // scale and store result back MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y}; MATRIX1X2 p1 = {0,0}; // this will hold result Mat_Mul1X2_3X2(&p0, &ms, &p1); // now scale via a matrix multiply poly->vlist[curr_vert].x = p1.M[0]; // copy result back to vertex poly->vlist[curr_vert].y = p1.M[1]; } // end for curr_vert return(1);} // end Scale_Polygon2D_Mat
42
Matrix Rotation
int Rotate_Polygon2D_Mat(POLYGON2D_PTR poly, int theta)
{
// function rotates the local coordinates of the polygon
if (!poly) return(0); // test for valid pointer
// test for negative rotation angle
if (theta < 0)
theta += 360;
MATRIX3X2 mr; // used to hold rotation transform matrix
Mat_Init_3X2(&mr,cos_look[theta],sin_look[theta],
-sin_look[theta],cos_look[theta], 0, 0);
43
Matrix Rotation
// loop and rotate each point, very crude, no lookup!!!
for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
{
// create a 1x2 matrix to do the transform
MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y};
MATRIX1X2 p1 = {0,0}; // this will hold result
// now rotate via a matrix multiply
Mat_Mul1X2_3X2(&p0, &mr, &p1);
// now copy the result back into vertex
poly->vlist[curr_vert].x = p1.M[0];
poly->vlist[curr_vert].y = p1.M[1];
} // end for curr_vert
return(1);
} // end Rotate_Polygon2D_Mat
44
Filling Polygons
• Many systems draw 2D and 3D objects as collections of triangles
• If we can fill a triangle, we are in pretty good shape
• The general idea will be to break up figures into triangles whose base is parallel to the x-axis and the draw lots of horizontal lines
46
Triangle Filling
1. Compute dx/dy for left and right sides (basically 1/slope) call them dxy_left and dxy_right
2. Starting at top vertex (x0 , y0) set xs=xl= x0 and y= y0
3. Add dxy_left to xs and add dxy_right to xl
4. Draw lines (xs,y) to (xl,y)
5. Increment y and go to step 3
47
Drawing Filled Triangles
void Draw_Top_TriFP(int x1,int y1,int x2, int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// function draws a triangle that has a flat top using fixed point math
void Draw_Bottom_TriFP(int x1,int y1, int x2,int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// function draws triangle that has flat bottom using fixed point math
void Draw_Top_Tri(int x1,int y1, int x2, int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// this function draws a triangle that has a flat top
void Draw_Bottom_Tri(int x1,int y1, int x2, int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// this function draws a triangle that has a flat bottom
48
Drawing Filled Triangles
void Draw_TriangleFP_2D(int x1,int y1,int x2, int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// function draws triangle on the destination buffer using fixed point
// it decomposes all triangles into a pair of flat top, flat bottom
void Draw_Triangle_2D(int x1,int y1, int x2, int y2, int x3,int y3,
int color, UCHAR *dest_buffer, int mempitch)
// this function draws a triangle on the destination buffer
// it decomposes all triangles into a pair of flat top, flat bottom
49
Breaking Up Polygons
1. If number of vertices left to process is greater then 3 continue to step 2
2. Take first 3 vertices and create a triangle
3. Split off triangle and recursively process remaining vetices
50
Breaking Up Quadrilaterals
inline void Draw_QuadFP_2D(int x0,int y0,
int x1,int y1,
int x2,int y2,
int x3, int y3,
int color,
UCHAR *dest_buffer, int mempitch)
{
// this function draws a 2D quadrilateral
// simply call the triangle function 2x, let it do all the work
Draw_TriangleFP_2D(x0,y0,x1,y1,x3,y3,color,dest_buffer,mempitch);
Draw_TriangleFP_2D(x1,y1,x2,y2,x3,y3,color,dest_buffer,mempitch);
} // end Draw_QuadFP_2D