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

    Viewing II -- Projections
    1. Projection Transformations

    2. Purpose -- define a viewing volume
      1. to determine how an object is projected onto the screen,
      2. to define which objects or portions of objects are clipped out of the final image

    3. Perspective Projection
      • The farther an object is from the "camera", the smaller it appears in the final image.
         
        Cube rendered with
        Orthographic Projection
          Cube rendered with
        Perspective Projection

         
        Scene rendered with Orthographic Projection   Scene rendered with Perspective Projection

      • Vanishing Points:
      • A vanishing point is a point in a perspective drawing to which parallel lines appear to converge.

      • One-point perspective : one vanishing point, any objects that are made up of lines either directly parallel with the viewer's line of sight or directly perpendicular to it

      • Two-point perspective: two vanishing points, One point represents one set of parallel lines, the other point represents the other. ( e.g. Looking at a house from the corner, one wall would recede towards one vanishing point, the other wall would recede towards the opposite vanishing point. )

      • Three-point perspective: three vanishing points e.g. looking up at a tall building from the corner, the third vanishing point is high in space

        Examples:
        Perspective Projection distortion: parallel lines appear to converge at a point ( vanishing point ).
        A painting (The Piazza of St. Mark, Venice) done by Canaletto in 1735-45 in one-point perspective.
        Painting in two point perspective by Edward Hopper
        Painting approximately in three point perspective (City Night, 1926) by Georgia O'Keefe.

      • Accomplished through the use of a frustum shaped viewing volume.


        e.g. Viewer is at origin looking in the negative z-axis

      • Objects that fall within the viewing volume are projected toward the apex of the pyramid, where the camera or viewpoint is.
        void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
        Creates a matrix for a perspective-view frustum and multiplies the current matrix by it. The frustum's viewing volume is defined by the parameters: (left, bottom, -near) and (right, top, -near) specify the (x, y, z) coordinates of the lower-left and upper-right corners of the near clipping plane; near and far give the distances from the viewpoint to the near and far clipping planes. They should always be positive.

        Let

          l   =   left     r   =   right
          b   =   bottom     t   =   top
          n   =   near     f   =   far

        The transformation matrix is given by

        ( i.e. corresponds to glFrustum( l, r, b, t, n, f ); )

        Any point (x, y, z) lying inside the frustrum is projected to a point (x', y', z') on the near clipping plane z = -n with
          l ≤ x' ≤ r  and  b ≤ y' ≤ t
        This is the same as

      • Alternatively, may use gluPerspective() to create the viewing volume:

        1. specify the angle of the field of view ( θ ) in the y-direction
        2. specify the aspect ratio of width to height ( x/y )

        • Calculating Field of view

        Therefore

        #define PI 3.14159265389
        
        double calculateAngle(double size, double distance)
        {
            double radtheta, degtheta; 
        
            radtheta = 2.0 * atan2 (size/2.0, distance);
            degtheta = (180.0 * radtheta) / PI;
            return (degtheta);
        }
          

        Example: Suppose all the coordinates in your object satisfy the equations

        -1 ≤ x ≤ 3, 5 ≤ y ≤ 7, and -5 ≤ z ≤ 5.

        and the 'camera' is at ( 8, 9, 10 )

        Then the center of the bounding box is (1, 6, 0).
        The radius r of a bounding sphere is the distance from the center of the box to any corner, , say (3, 7, 5). That is,

        r = ½ size   =

        Thus,

        Distance d   =

        and


        void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);
        Creates a matrix for a symmetric perspective-view frustum and multiplies the current matrix by it. fovy is the angle of the field of view in the y-z plane; its value must be in the range [0.0,180.0]. aspect is the aspect ratio of the frustum, its width divided by its height. near and far values are the distances between the viewpoint and the clipping planes, along the negative z-axis. They should always be positive.

    4. Orthographic Projection

      • the viewing volume is a rectangular parallelepiped ( i.e. a box )
      • the size of the viewing volume does not change from one end to the other
      • used for applications for architectural blueprints and CAD ( computer aided design )

        void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
        Creates a matrix for an orthographic parallel viewing volume and multiplies the current matrix by it. (left, bottom, -near) and (right, top, -near ) are points on the near clipping plane that are mapped to the lower-left and upper-right corners of the viewport window, respectively. (left, bottom, -far) and (right, top, -far) are points on the far clipping plane that are mapped to the same respective corners of the viewport. Both near and far can be positive or negative.

        The corresponding transformation matrix for glOrtho(l, r, b, t, n, f ) is:

    5. Viewing Volume Clipping
        After the projection matrices, any primitives that lie outside the viewing volume are clipped.
    6. Viewport Transformation

    7. Manipulating the Matrix Stacks

      void glPushMatrix(void);
      void glPopMatrix(void);

      void glMatrixMode( GLenum mode );
      Specifies whether the modelview, projection, or texture matrix will be modified. mode: GL_MODELVIEW, GL_PROJECTION, GL_TEXTURE.

      To examine the transformation matrix:

        void glGetIntegerv ( GLenum pname, GLint *params );
        void glGetFloatv ( GLenum pname, GLFloat *params );
        void glGetDoublev ( GLenum pname, GLDouble *params );
        void glGetPointerv ( GLenum pname, GLint **params );

      Example

      /*
       *  cube_mat.cpp
       *  This program demonstrates examining the transformation matrix values.
       *  
       *  A wireframe cube is rendered.
       */
      #include <GL/glut.h>
      #include <stdlib.h>
      #include <iostream>
      
      using namespace std;
      
      void init(void)
      {
         glClearColor (1.0, 1.0, 1.0, 0.0);
         glShadeModel (GL_FLAT);
      }
      
      //print the transformation matrix
      template<class T>
      void print_mat ( T m[][4] )
      {
        cout.precision ( 4 );
        cout << fixed;
        for ( int i = 0; i < 4; ++i ) {
          cout << "\t";
          for ( int j = 0; j < 4; ++j )
            cout <<  m[j][i] << "\t";
          cout << endl;
        }
        cout << endl;
      }
      
      void display(void)
      {
         float p[4][4];
         double pd[4][4];
      
         glClear (GL_COLOR_BUFFER_BIT);
         glColor3f (0.0, 1.0, 0.0);	  	//green color
         glLoadIdentity ();             	// clear the matrix 
         glMatrixMode (GL_PROJECTION);
                 				// viewing transformation  
         glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
         gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
         
         glMatrixMode (GL_MODELVIEW);
         glLoadIdentity();
         glGetFloatv(GL_PROJECTION_MATRIX,&p[0][0]);
         cout << "Projection Matrix:" << endl;
         print_mat ( p );
         
         glGetDoublev(GL_MODELVIEW_MATRIX, &pd[0][0]);
         cout << "Modelview Transformation Matrix:" << endl;
         print_mat ( pd );
         
         glScalef (1.0, 2.0, 1.0);      	// modeling transformation 
      
         glGetFloatv(GL_MODELVIEW_MATRIX,&p[0][0]);
         cout << "Scale Matrix:" << endl;
         print_mat ( p );
         
         glRotatef ( 45, 0, 0, 1 );
         glGetDoublev(GL_MODELVIEW_MATRIX, &pd[0][0]);
         cout << "Scale Rotation Matrix:" << endl;
         print_mat ( pd );
         glutWireCube (1.0);
         
         glFlush ();
      }
      
      void keyboard(unsigned char key, int x, int y)
      {
         switch (key) {
            case 27:
               exit(0);
               break;
         }
      }
      
      int main(int argc, char** argv)
      {
         glutInit(&argc, argv);
         glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
         glutInitWindowSize (500, 500);
         glutInitWindowPosition (100, 100);
         glutCreateWindow (argv[0]);
         init ();
         glutDisplayFunc(display);
         glutKeyboardFunc(keyboard);
         glutMainLoop();
         return 0;
      }
      
      Outputs:
      Projection Matrix:
              1.5000  0.0000  0.0000  0.0000
              0.0000  1.5000  0.0000  0.0000
              0.0000  0.0000  -1.1622 2.5676
              0.0000  0.0000  -1.0000 5.0000
      
      Modelview Transformation Matrix:
              1.0000  0.0000  0.0000  0.0000
              0.0000  1.0000  0.0000  0.0000
              0.0000  0.0000  1.0000  0.0000
              0.0000  0.0000  0.0000  1.0000
      
      Scale Matrix:
              1.0000  0.0000  0.0000  0.0000
              0.0000  2.0000  0.0000  0.0000
              0.0000  0.0000  1.0000  0.0000
              0.0000  0.0000  0.0000  1.0000
      
      Scale Rotation Matrix:
              0.7071  -0.7071 0.0000  0.0000
              1.4142  1.4142  0.0000  0.0000
              0.0000  0.0000  1.0000  0.0000
              0.0000  0.0000  0.0000  1.0000
      

    8. Additional Clipping Planes

    9. In addition to the six clipping planes of the viewing volume (left, right, bottom, top, near, and far), you can define up to six additional clipping planes to further restrict the viewing volume.

    10. Each plane is specified by the coefficients of its equation:
        Ax + By + Cz + D = 0.
      void glClipPlane(GLenum plane, const GLdouble *equation);
      Defines a clipping plane. The equation argument points to the four coefficients of the plane equation, Ax + By + Cz + D = 0. Clip away space of Ax + By + Cz + D < 0.
      plane is GL_CLIP_PLANEi, where i is an integer specifying which of the available clipping planes to define.

      You need to enable each additional clipping plane you define:

        glEnable(GL_CLIP_PLANEi);

      You can disable a plane with

        glDisable(GL_CLIP_PLANEi);

      Example: Clipped Wireframe Sphere
      /*
       *  clip.cpp
       *  This program demonstrates arbitrary clipping planes.
       */
      #include <GL/glut.h>
      
      void init(void)
      {
         glClearColor (0.0, 0.0, 0.0, 0.0);
         glShadeModel (GL_FLAT);
      }
      
      void display(void)
      {
         GLdouble eqn[4] = {0.0, 1.0, 0.0, 0.0};
         GLdouble eqn2[4] = {1.0, 0.0, 0.0, 0.0};
      
         glClear(GL_COLOR_BUFFER_BIT);
      
         glColor3f (1.0, 1.0, 1.0);
         glPushMatrix();
         glTranslatef (0.0, 0.0, -5.0);
      /*    clip lower half -- y < 0          */
         glClipPlane (GL_CLIP_PLANE0, eqn);
         glEnable (GL_CLIP_PLANE0);
      /*    clip left half -- x < 0           */
         glClipPlane (GL_CLIP_PLANE1, eqn2);
         glEnable (GL_CLIP_PLANE1);
      
         glRotatef (90.0, 1.0, 0.0, 0.0);	//make z-axis vertical
         /* 
         poles along z-axis, 20 longitudinal slices (passing through poles)
         16 latitude cuts ( parallel to equator ) 
         */
         glutWireSphere(1.0, 20, 16);		
         glPopMatrix();
      
         glFlush ();
      }
      
      void reshape (int w, int h)
      {
         glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
         glMatrixMode (GL_PROJECTION);
         glLoadIdentity ();
         gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
         glMatrixMode (GL_MODELVIEW);
      }
      
      int main(int argc, char** argv)
      {
         glutInit(&argc, argv);
         glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
         glutInitWindowSize (500, 500); 
         glutInitWindowPosition (100, 100);
         glutCreateWindow (argv[0]);
         init ();
         glutDisplayFunc(display); 
         glutReshapeFunc(reshape);
         glutMainLoop();
         return 0;
      }
          

    11. Examples of Composing Several Transformations

      Note again: M2M1 ≠ M1M2

      Note that the equivalent order of operation on P is : scale, rotate, translate

      Suppose we want to rotate an object about a 'z' axis through ( -1, 0, 0 ). We can achieve this by moving everything by 1, do the rotation, and move things back by 1.

      Question: Which of the following is correct?

      Example: Building an Articulated Robot Arm

    12. Scale a cube as a segment of the robot arm
    13. Call the appropriate modeling transformations to orient each segment
    14. As a rotating axis always passes through origin ( 0, 0, 0 ), if we want to rotate an object about another axis (e.g. an axis passing through a cube edge), we first move the local coordinate system ( object ) to one edge of the cube ( pivot point for rotating ) using glTranslatef(); after rotation, we move the system ( object ) back with glTranslatef():

      glTranslatef (-1.0, 0.0, 0.0);
      glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0);
      glTranslatef (1.0, 0.0, 0.0);
      glPushMatrix();
      glScalef (2.0, 0.4, 1.0);
      glutWireCube (1.0);
      glPopMatrix();
      

    15. The second segment is built by moving the local system to the next pivot point:

      glTranslatef (1.0, 0.0, 0.0);	 
      glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
      glTranslatef (1.0, 0.0, 0.0);  
      glPushMatrix();
      glScalef (2.0, 0.4, 1.0);
      glutWireCube (1.0);
      glPopMatrix();
          

    16. The complete program:

      
      //robot.cpp
      #include <GL/glut.h>
      #include <stdlib.h>
      
      static int shoulder = 0, elbow = 0;
      
      void init(void) 
      {
         glClearColor (0.0, 0.0, 0.0, 0.0);
         glShadeModel (GL_FLAT);
      }
      
       
      /*
        glutWireCube ( 1.0 )  
        produces a cube: -0.5 to 0.5
      */
      void display(void)
      {
         glClear (GL_COLOR_BUFFER_BIT);
         glPushMatrix();				  //Save M0
         glTranslatef (-1.0, 0.0, 0.0);		  //M1 = T-1
         glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0); //M2 = T-1Rs
         glTranslatef (1.0, 0.0, 0.0);		  //M3 = T-1RsT+1
         glPushMatrix();				  //Save M3
         glScalef (2.0, 0.4, 1.0);			  //M4 = T-1RsT+1S
         glutWireCube (1.0);				  //P' =  T-1RsT+1S P
         glPopMatrix();				  //Restore M3 = T-1RsT+1
         glTranslatef (1.0, 0.0, 0.0);		  //M5 = T-1RsT+1T+1
         glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);	  //M6 = T-1RsT+1T+1Re
         glTranslatef (1.0, 0.0, 0.0);		  //M7 = T-1RsT+1T+1ReT+1
         glPushMatrix();				  //Save M7
         glScalef (2.0, 0.4, 1.0);			  //M8 = T-1RsT+1T+1ReT+1S
         glutWireCube (1.0);				  //P' = T-1RsT+1T+1ReT+1S P
         glPopMatrix();				  //Restore M7 
      
         glPopMatrix();				  //Restore M0
         glutSwapBuffers();
      }
      
      void reshape (int w, int h)
      {
         glViewport (0, 0, (GLsizei) w, (GLsizei) h); 
         glMatrixMode (GL_PROJECTION);
         glLoadIdentity ();
         gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
         glMatrixMode(GL_MODELVIEW);
         glLoadIdentity();
         glTranslatef (0.0, 0.0, -5.0);
      }
      void keyboard (unsigned char key, int x, int y)
      {
         switch (key) {
            case 's':
               shoulder = (shoulder + 5) % 360;
               glutPostRedisplay();
               break;
            case 'S':
               shoulder = (shoulder - 5) % 360;
               glutPostRedisplay();
               break;
            case 'e':
               elbow = (elbow + 5) % 360;
               glutPostRedisplay();
               break;
            case 'E':
               elbow = (elbow - 5) % 360;
               glutPostRedisplay();
               break;
            case 27:
               exit(0);
               break;
            default:
               break;
         }
      }
      
      int main(int argc, char** argv)
      {
         glutInit(&argc, argv);
         glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
         glutInitWindowSize (500, 500); 
         glutInitWindowPosition (100, 100);
         glutCreateWindow (argv[0]);
         init ();
         glutDisplayFunc(display); 
         glutReshapeFunc(reshape);
         glutKeyboardFunc(keyboard);
         glutMainLoop();
         return 0;
      }
         

      Robot Arm with fingers:

    17. Generalized Homogeneous Coordinates

      Previously, 4th component is either 0 or 1; works well with affine transformations, involving matrix multiplications

      Trouble with perspective transformation:

      It involves a division by the z coordinate! But matrix multiplications of affine transformations only have addition and multiplications! How can we combine this perspective transformation with the affine transformations ( translation, rotation, scaling )?

      The trick: Consider two sets of coordinates to represent the same point if one is a non-zero multiple of the other --> extending the homogeneous coordinate representation:

    18. the point ( x, y, z, w ) represents the same point as does ( αx, αy, αz, αw )
    19. thus ( x, y, z, 1 ) and ( x/w, y/w, z/w, 1/w ) are equivalent; w ≠ 0
    20. e.g. the point (1, 2, 3 ) has the representations ( 1, 2, 3, 1 ), (2, 4, 6, 2), (0.03, 0.06, 0.09, 0.03 ), (-1, -2, -3, -1) , and so on
    21. So, representations ( -d. x/z, -d. y/z, -d, 1 ) and ( x, y, z, -z /d ) represent the same point ( -d. x/z, -d. y/z, -d )
    22. To convert from homogenous coordinates to ordinary coordinates:
      • divide all components by the fourth component
      • discard the fourth component
      e.g. ( 3, 6, 2, 3 ) → ( 1, 2, 2/3, 1 ) → ( 1, 2, 2/3 )
    23. To convert from ordinary coordinates to homogeneous coordinates
      • append a 1 as the fourth component
      e.g. ( 2, 3, 4 ) → ( 2, 3, 4, 1 )
    24. What is the perspective matrix that brings point ( x, y, z ) to ( -d. x/z, -d. y/z, -d )?

    25. one problem remains: the matrix above is singular; i.e. noninvertible. Why?

      Solution: we don't care about the z coordinate after the transformation; all we care about are the x and y coordinates where the point is rendered on the screen

      The point ( x, y, z ) now maps to ( -d. x/z, -d. y/z, 1/z-d ), which will be projected onto the screen at ( -d. x/z, -d. y/z, 0 )
      The third component has been peeled off for depth testing

      The matrix is nonsingular: