CS 420 Computer Graphics
Lecture Notes
Dr. Tong Yu, Sept. 2005
    1. Introduction
    2. Line Drawing
    3. Drawing Objects
    4. More Drawing Tools
    5. Normal Vectors and Polygonal Models of Surfaces
    6. Viewing I -- Affine Transformations
    7. Viewing II -- Projections
    8. Color
    9. Lighting
    10. Blending, antialiasing, fog ..
    11. Display Lists, Bitmaps and Images
    Appendix. Games and SDL

    Line Drawing
    1. Bresenham's Line Algorithm

    2. Assume: y = mx + b
       

    3. Given two endpoints ( x0, y0 ), ( xn, yn ) we can choose the start point ( xk, yk )
    4. We want to decide what's the next pixel to draw; we have eight choices. ( Why? )
    5. Assume 0 ≤ m ≤ 1 and x0 < xn
      • Our choices are either ( xk + 1, yk ) or ( xk + 1, yk + 1 )

      • Let
        dlower = y - yk
          = m ( xk + 1 ) + b - yk
        and
        dupper = ( yk + 1 ) - y
          = yk + 1 - m ( xk + 1 ) - b
      • To determine which one is closer to the line path:
        dlower - dupper = 2m ( xk + 1 ) - 2 yk + 2b - 1

        Let m = ( yn - y0 ) / ( xn - x0 ) = Δy / Δx

        Define the decision parameter as

        pk = Δx ( dlower - dupper )
          = 2Δy.xk - 2Δx.yk + c     ----- ( 1 )
        where c = 2Δy + Δx ( 2b - 1 ) is a constant ( independent of pixel position )

        Therefore,

        pk+1 - pk = 2Δy ( xk+1 - xk ) - 2Δx ( yk+1 - yk ) But, xk+1 = xk + 1 So, pk+1 = pk + 2Δy - 2Δx ( yk+1 - yk )     ---- ( 2 ) with p0 = 2Δy - Δx

        In ( 2 ) , yk+1 - yk is either 0 or 1, depending on the sign of pk. From ( 1 ), as Δx >0, if the pixel at yk is closer to the line path than at yk + 1 (i.e. dlower < dupper ), pk is negative and we choose the next pixel to be the lower pixel; otherwise we choose the upper pixel.

      Brensenham's Line Drawing algorithm for |m| < 1

      1. Input two endpoints ( x0, y0 ), ( xn, yn ) where ( x0, y0 ) is the left endpoint.
      2. Plot ( x0, y0 ) as first point.
      3. Calculate Δx, Δy and p0 = 2Δy - Δx
      4. Start with k = 0, perform the test:
        If pk < 0, the next point to plot is ( xk +1, yk ) and pk+1 = pk + 2 Δy
        otherwise, the next point to plot is ( xk +1, yk + 1) and pk+1 = pk + 2 Δy - 2 Δx
      5. Perform step 4     Δx - 1 times.

    6. Sample Implementation:
      //bline.cpp : Bresenham Line algorithm, works only for |m| < 1
      
      #include <GL/glut.h>
      #include <stdio.h>
      #include <math.h>
      
      void init(void)
      {
        glClearColor(1.0,1.0,1.0,0.0);
        glMatrixMode(GL_PROJECTION);
        gluOrtho2D(0.0,400.0,0.0,400.0);
      }
      
      void setPixel(GLint x,GLint y)
      {
        glBegin(GL_POINTS);
           glVertex2i(x,y);
        glEnd();
      }
      
      void line()
      {
        int x0 = 50, y0=50, xn = 300, yn = 150, x, y;
        int	dx, dy,		//deltas
      	pk,		//decision parameter
        	k;		//looping variable
      
        glClear(GL_COLOR_BUFFER_BIT);
        glColor3f( 1 ,0, 0);  
        setPixel(x0, y0);	//plot first point
        
        // difference between starting and ending points
        dx = xn - x0;
        dy = yn - y0;
        pk = 2 * dy - dx; 
        x = x0;	y = y0; 
        
        for ( k = 0; k < dx-1; ++k ) {
          if ( pk < 0 ) {
            pk = pk + 2 * dy;			//calculate next pk
      					//next pixel: (x+1, y )
          } else {
            					//next pixel: (x+1, y+1)
            pk = pk + 2*dy - 2*dx;		//calculate next pk
            ++y;
          }
          ++x;
          setPixel( x, y );
        }
      
        glFlush();
      }
      
      int main(int argc,char **argv){
          glutInit(&argc,argv);
          glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
          glutInitWindowPosition(0,0);
          glutInitWindowSize(500,500);
          glutCreateWindow("Bresenham Line");
          init();
          glutDisplayFunc( line );
          glutMainLoop();
          return 0;
      }
      
        
    7. Bresenham's Circle Algorithm

    8. Only has to consider one eigth of a circle, other parts being symmetric.
    9. x2 + y2 = r2

    10. Compute the relative error for each of the possible choices ( xk+1, yk ) and ( xk+1, yk - 1 )

    11. Error for P1:

        E1(k) = [yk2 - y2]
          = yk2 + ( xk + 1 )2 - r2

    12. Error for P2:

        E2(k) = y2 - (yk - 1)2
          = r2 - ( xk + 1 )2 - (yk - 1)2

    13. Consider

        dk = ( E1(k) - E2(k) ) / 2 = yk2 + xk2 + 2xk - yk + c
      where c = 3/2 - r2 is a constant.

    14. If dk < 0 ( i.e. P1 nearer to curve ), pick P1 as next pixel:
        yk+1 = yk, and
        dk+1 = yk2 + (xk + 1)2 + 2( xk + 1 ) - yk + c
          = yk2 + xk 2 + 2xk + 1 + 2xk + 2 - yk + c
          = dk + 2xk + 3

    15. If dk > 0 ( i.e. P2 nearer to curve ), pick P2 as next pixel:
        yk+1 = yk - 1, and
        dk+1 = (yk - 1) 2 + (xk + 1)2 + 2( xk + 1 ) - yk + c
          = yk2 - 2yk + 1 + xk2 + 2xk + 1 + 2xk + 2 - yk + c
          = dk + 2 (xk - yk) + 5

    16. The initial point is ( 0, r ), so we can set d0 = 3/2 - r

    17. Sample Implementation:
      //bcircle.cpp : Bresenham Circle algorithm
      
      #include <GL/glut.h>
      #include <stdio.h>
      #include <math.h>
      
      void init(void)
      {
        glClearColor(1.0,1.0,1.0,0.0);
        glMatrixMode(GL_PROJECTION);
        gluOrtho2D(0.0,200.0,0.0,200.0);
      }
      
      void setPixel(GLint x,GLint y)
      {
        glBegin(GL_POINTS);
           glVertex2i(x,y);
        glEnd();
      }
      
      void Circle(){
      	
        int xCenter=100,yCenter=100,r=50;
        int x=0,y=r;
        int d = 3/2 - r;				// = 1 - r
        glClear(GL_COLOR_BUFFER_BIT);
        glColor3f( 1 ,0, 0);  
        while(x<=y){
          setPixel(xCenter+x,yCenter+y);
          setPixel(xCenter+y,yCenter+x);		//find other points by symmetry
          setPixel(xCenter-x,yCenter+y);
          setPixel(xCenter+y,yCenter-x);
          setPixel(xCenter-x,yCenter-y);
          setPixel(xCenter-y,yCenter-x);
          setPixel(xCenter+x,yCenter-y);
          setPixel(xCenter-y,yCenter+x);
      
          if (d<0)
       	d += (2*x)+3;
          else {
      	d += (2*(x-y))+5;
      	y -= 1;
          }
          x++;
        }
      
        glFlush();
      }
      
      int main(int argc,char **argv){
          glutInit(&argc,argv);
          glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
          glutInitWindowPosition(0,0);
          glutInitWindowSize(500,500);
          glutCreateWindow("Bresenham Circle");
          init();
          glutDisplayFunc(Circle);
          glutMainLoop();
          return 0;
      }
      
    18. SDL Library
      /*
        draws a line from current point to new point using Bresenham algorithm
        surf is the SDL_Surface of the class
      */
      void Surface:: lineTo( int x1, int y1 )
      {
          Uint16 *buffer;
          int drawpos;
          int xspan, yspan;
          int xinc, yinc, x0, y0;
          int sum;
          int i;
      
          /* If we need to lock this surface before drawing pixels, do so. */
          if (SDL_MUSTLOCK( surf )) {
      	if (SDL_LockSurface(surf) < 0) {
      	    printf("Error locking surface: %s\n", SDL_GetError());
      	    abort();
      	}
          }
      
          /* Get the surface's data pointer. */
          buffer = (Uint16 *)surf->pixels;
      	
          x0 = CP.x;	y0 = CP.y;
         //here's the Brensenham's algorithm, it draws from ( x0, y0 ) to ( x1, y1 )
      
          /* Calculate the x and y spans of the line. */
          xspan = x1-x0+1;
          yspan = y1-y0+1;
      	
          /* Figure out the correct increment for the major axis.
             Account for negative spans (x1 < x0, for instance). */
          if (xspan < 0) {
      	xinc = -1;
      	xspan = -xspan;
          } else xinc = 1;
      
          if (yspan < 0) {
      	yinc = -surf->pitch/2;
      	yspan = -yspan;
          } else yinc = surf->pitch/2;
      	
          i = 0;
          sum = 0;
      	
          /* This is our current offset into the buffer. We use this
             variable so that we don't have to calculate the offset at
             each step; we simply increment this by the correct amount.
      
             Instead of adding 1 to the x coordinate, we add one to drawpos.
             Instead of adding 1 to the y coordinate, we add the surface's
             pitch (scanline width) to drawpos. */
          drawpos = surf->pitch/2 * y0 + x0;
      	
          /* Our loop will be different depending on the
             major axis. */
          if (xspan < yspan) {
      
      	/* Loop through each pixel along the major axis. */
      	for (i = 0; i < yspan; i++) {
      
      	    /* Draw the pixel. */
      	    buffer[drawpos] = color;
      
      	    /* Update the incremental division. */
      	    sum += xspan;
      
      	    /* If we've reached the dividend, advance
      	       and reset. */
      	    if (sum >= yspan) {
      		drawpos += xinc;
      		sum -= yspan;
      	    }
      
      	    /* Increment the drawing position. */
      	    drawpos += yinc;
      	}
          } else {
      	/* See comments above. This code is equivalent. */
      	for (i = 0; i < xspan; i++) {
      
      	    buffer[drawpos] = color;
      			
      	    sum += yspan;
      
      	    if (sum >= xspan) {
      		drawpos += yinc;
      		sum -= xspan;
      	    }
      
      	    drawpos += xinc;
      	}
          }
          CP.set ( x1, y1 );		//set new CP position
          /* Unlock the surface. */
          SDL_UnlockSurface(surf);
      }
      

    19. Plotting Functions

      Suppose we wish to learn the behavior of some mathematical functions f(x) as x varies. For example, how does

      f(x) = e-|x| cos ( 2π x ) behave when x varies between 0 and 4? We may plot the function using a code like:
      for ( double x = 0; x < 4.0; x += 0.005 )
        surf.lineTo ( (int) x, (int) f(x) );
      

      Problem!! What ?
    20. The picture produced will be very tiny, because the total span of x is between 0 and 4, covering only four pixels of the screen
    21. Therefore, we need to scale and position the values to be plotted so that they cover the screen window appropriately
    22. Scaling x
        sx = x * VWIDTH / 4.0;
      sx = 0, when x = 0; sx = VWIDTH, when x = 4.0
    23. Scaling and translating y
      In the example, -1 ≤ y ≤ 1; we want sy = 0, when y = 1, and sy = VHEIGHT, when y = -1; so we set
        sy = ( -y + 1 ) * VHEIGHT / 2.0;
    24. The scaling and translation can be expressed in the form
        sx = a * x + c
        sy = b * y + d
      or in matrix form
        sx
        sy
        1
        = a   0   c
        0   b   d
        0   0   1
        x
        y
        1
      In our example a = VWIDTH / 4.0, c = 0; b = -VHEIGHT / 2.0, d = VHEIGHT / 2.0

    25. Implementation
        double f ( double x )
        {
          double y = exp ( -fabs ( x ) ) * cos ( 2 * 3.1415926 * x );
        
          return y;
        }
        

          int sx, sy;
          double x, y, a, b, c, d;
          a = VWIDTH / 4.0;
          c = 0;
          b  = -VHEIGHT / 2.0;
          d  = VHEIGHT / 2.0;
        
        
          x = 0.0;                              //initial position
          y = f ( 0.0 );
          sx = (int) ( a * x + c );
          sy = (int) ( b * y + d );
          surf.moveTo ( sx, sy );
        
          for ( x = 0; x < 4.0; x += 0.005 ) {
            y =  f ( x );
            sx = (int) ( a * x + c );
            sy = (int) ( b * y + d );
            surf.lineTo ( sx, sy );
          }
        

    26. Turtle Graphics

      Turtle graphics is a style of computer drawing based on preserved state (position and orientation) and a small number of operations against that state (forward, turn, pen up & down).
      The state was called the turtle and programs taught the turtle how to draw.
      Easy for kids to pick up.

      You can apply turtle graphics to draw fractals

        to draw-a-box 
      	forward 10
      	turn 90
      	forward 10
      	turn 90
      	forward 10
      	turn 90
      	forward 10
      	turn 90
      
        to draw-a-window
      	draw-a-box
      	turn 90
      	draw-a-box
      	turn 90
      	draw-a-box
      	turn 90
      	draw-a-box
        	turn 90
        
       

      A coding example: drawing a hook

      void Surface::forward ( float dist, int isVisible )
      {
        const float RadPerDeg = 0.017453393;          //radians per degree
        float x = CP.getX() + dist * cos ( RadPerDeg * CD );
        float y = CP.getY() + dist * sin ( RadPerDeg * CD );
        if ( isVisible )
          lineTo( x, y );
        else
          moveTo ( x, y );
      }//forward
        
      //L is length of short side
      void draw_hook( float L )
      {
        cvs.forward( 3*L, 1 );
        cvs.turn( 90 );
        cvs.forward( L, 1 );
        cvs.turn( 90 );
        cvs.forward( L, 1 );
        cvs.turn( 90 );
      }
       
      void display(void)
      {
        cvs.clearScreen();
       
        cvs.moveTo(0.0, 0.0); //starts at center
        cvs.turnTo ( 0.0 );   //points horizontally
        draw_hook ( 0.5 );
      }
        

      Some Programming Examples

      Class exercise:

      Use turtle graphics to draw a star pattern.