Lab 2: Points, Primitives and 2.5D Art


Highlights of this lab:

This lab is an introduction to Fundamental OpenGL Functions

Assignment:

After the lab lecture, you have one week to:

On-line OpenGL Manual


Seminar Notes

A. Lab 1 Follow-up

OpenGL is an operating system and hardware platform independent graphics library designed to be easily portable yet rapidly executable. Unlike Direct3D, which is only available on PC, Xbox 360 and Windows Mobile OS, OpenGL is available on a wide variety of hardware platforms and operating systems including Unix/X11 (Linux, Irix, BSD, Solaris), Mac OS X, Microsoft Windows 95, 98, 2000, NT, XP, Vista, and 7. Variations of it are available as a graphics library on most game consoles, including Nintendo DS, Wii, and Playstation 3. The embedded version, OpenGLES, is available on many hand held devices including iPhone OS and Android OS devices. A Javascript version of OpenGLES 2.0 called WebGL is an official part of the HTML 5 spec.

The basic steps to use OpenGL in a program are:

What are DCs and RCs?

What is a pixel format?

B. Basic OpenGL Rendering Functions

Your graphics hardware has limited ability to represent geometry. Most hardware only understands triangle rendering primitives. Everything else is built up using triangles. OpenGL adds to this a bit, but still has only a few built in rendering primitives. Below is a diagram showing OpenGLs different primitive types or modes:

Drawing with one of these types is controlled by a glBegin / glEnd pair.

If you have a Windows PC you can try my OpenGL Shapes Demo. It may help you understand how OpenGL primitives are drawn to the screen. (Shape Demo Instructions)

Vertices

Basic OpenGL rendering primitives are made up of lists of vertices. There are several types of vertex function calls. Some common ones are listed below:

 glVertex2d   glVertex2f   glVertex2i   glVertex2s
 glVertex3d   glVertex3f   glVertex3i   glVertex3s
 glVertex4d   glVertex4f   glVertex4i   glVertex4s
 glVertex2dv  glVertex2fv  glVertex2iv  glVertex2sv
 glVertex3dv  glVertex3fv  glVertex3iv  glVertex3sv
 glVertex4dv  glVertex4fv  glVertex4iv  glVertex4sv

The postfix specifies the format of parameters used by each function.

The first digit is the number of coordinates in the point specification:
The second digit represents the type of the parameters:

If there is a v at the end this means the coordinates are in an array or equivalent memory block.

To guarantee that the different types have the right number of bits on each platform, OpenGL provides its own types that you are encouraged but not required to use:

    Floating Point:   Most often used for 3D or 4D points
	GLdouble    GLfloat	

    Signed Integer:   Most often used for 2D points
	GLbyte      GLshort     GLint

    Unsigned Integer: Most often used for colours
	GLubyte     GLushort    GLuint 

Example: three 2D points - one drawn with regular parameters, one with an array and one with a pointer to memory


    GLdouble P2[2] = {1.0, 0.0};
    GLfloat *P3;

    /* allocate memory for P3 */
    P3 = new GLfloat[2];

    /* set values for P3. */
    P3[0] = 0.0f; 
    P3[1] = 1.0f;

    glBegin(GL_POINTS);
        glVertex2i(0, 0);
        glVertex2dv(P2);
        glVertex2fv(&P3[0]); /* This is equivalent. */
    glEnd();

Refer to the manual entry on glVertex functions for details.

Points

Points are drawn in GL_POINTS mode. A new point is drawn for every glVertex* call.

eg:


    glBegin(GL_POINTS);
        glVertex2f( 0.0f,  2.0f); //note 2D form 
        glVertex2f( 1.0f,  2.0f);
        glVertex2f( 0.0f, -2.0f);
        glVertex2f(-1.0f,  0.0f);
    glEnd();

You can draw points in diffent sizes using the glPointSize function.

Lines

Three different line primitives can be created:

Polygons

The following code constructs a filled in parallelogram on the x-y plane:

    glBegin(GL_POLYGON);
        glVertex2f( 0.0f,  2.0f); //note 2D form
        glVertex2f( 1.0f,  2.0f);
        glVertex2f( 0.0f, -2.0f);
        glVertex2f(-1.0f,  0.0f);
    glEnd();
As mentioned above, there are other types of points. Using 3D vertices is similar:
    glBegin(GL_POLYGON);
        glVertex3f( 0.0f,  2.0f, 0.0f);
        glVertex3f( 1.0f,  2.0f, 0.0f);
        glVertex3f( 0.0f, -2.0f, 0.0f);
        glVertex3f(-1.0f,  0.0f, 0.0f);
    glEnd();

The following draws a rectangle:

    glRectf(0f, 0f, 1f, 1f); // x0, y0, x1, y1: two opposite corners
                             // of the rectangle.
This is equivalent to:
    glBegin(GL_QUADS);
        glVertex2f(0.0f, 0.0f); //note 2D form
        glVertex2f(1.0f, 0.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    glEnd();

Specifying Colors

Each vertex can have a colour. That colour is the one specified by the most recent glColor*() command.

Just like glVertex*() commands, glColor*() commands come in many forms. They are similar to the glVertex forms - they allow for different

The glColor3f() function takes three floating-point values for the red, green and blue color to select. A value of 0 means zero intensity; a value of 1.0 is full intensity, and any value in between is a partial intensity.

The glColor3ub() function is particularly useful because its colour channel values, 0 to 255, map to the typical color values in 24 or 32-bit colour graphics hardware. It is also useful because the colour picker in MSPaint supplies RGB values that are in the exact same range.

As you can see, the range of colors varies considerably based on data type. The exact color ranges for different types are specified in this table in the Redbook.

Most primitives are made up of more than one vertex. Primitives can be flat shaded, which means that only one colour is used for each completed primitive, or smooth shaded, which means that the colours for each vertex are blended smoothly across the primitive. Shading mode can be changed with glShadeModel(). The vertex colour used on flat shaded primitives varies depending on the primitive. The vertex colour used in flat shaded primitives is in this table and the paragraph above it in the OpenGL Redbook.

Setting Up 2D Rendering

Clearing the rendering window

The colour buffer and depth buffer are usually cleared each time you begin drawing to the OpenGL window. The values you use to clear with rarely change, so they are often set in the initialisation step with the glClearColor and glClearDepth functions:


    glClearColor(0.0f, 0.0f, 0.0f, 1.0f ); //clear colour is black
    glClearDepth(1.0f); //Clear to maximum distance

The actual clearing happens just before you draw. In your main draw routine, you specify which buffers to clear with the glClear function:

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

Depth

When you are drawing in 2D you can still draw things at different distances. Without depth testing these distances have no effect: things are simply drawn to the screen in the order you specify them, kind of like a paint program. This is fine if you are drawing a static image and have full control of the order in which things are drawn, but suppose you are writing an art program like Illustrator where objects can be moved in front of or behind other objects, or a 2.5D game where the character can walk behind or in front of the same scene element depending on a distance coordinate, as in the classic Sierra and Lucasarts adventure games. To solve this type of problem you assign each element in your scene a distance and turn on depth testing.

In OpenGL features are enabled and disabled with the glEnable and glDisable functions. To enable depth testing be sure to request a depth buffer in your pixel format. Instructions for doing this in GLUT are in the lab exercise. Then you can use use glEnable(GL_DEPTH_TEST);and glDisable(GL_DEPTH_TEST); to turn it on and off when necessary. Usually it is turned on in the init section and left on.

By default, the depth test will only draw things that are closer than what was drawn at that location before. So if object2 is drawn after object1at the same distance, only object1 will be visible where they overlap. The function used for the depth test can be changed with the glDepthFunc() command.

As an example consider this drawing code, designed to make a simple face. Try using this as a sample scene for this week's lab exercise. Run it with and without depth testing in both perspective and orthographic projection modes.

    glBegin(GL_QUADS);
    
    //Red
    glColor3f(1.9f, 0.0f, 0.0f);

    //mouth
    glVertex3f( 1.0f, -0.5f,  1.0f);
    glVertex3f( 1.0f, -1.0f,  1.0f);
    glVertex3f(-1.0f, -1.0f,  1.0f);
    glVertex3f(-1.0f, -0.5f,  1.0f);
  
    //Cyan or aqua 
    glColor3f( 0.0f,  1.0f,  1.0f);

    //left iris
    glVertex3f(-0.5f,  0.5f,  3.0f);
    glVertex3f(-0.5f,  1.0f,  3.0f);
    glVertex3f(-1.0f,  1.0f,  3.0f);
    glVertex3f(-1.0f,  0.5f,  3.0f);

    //right iris
    glVertex3f( 0.5f,  0.5f,  3.0f);
    glVertex3f( 0.5f,  1.0f,  3.0f);
    glVertex3f( 1.0f,  1.0f,  3.0f);
    glVertex3f( 1.0f,  0.5f,  3.0f);

    //White
    glColor3f( 1.0f,  1.0f,  1.0f);

    //left white
    glVertex3f(-0.4f,  0.4f,  1.0f);
    glVertex3f(-0.4f,  1.1f,  1.0f);
    glVertex3f(-1.1f,  1.1f,  1.0f);
    glVertex3f(-1.1f,  0.4f,  1.0f);

    //right white
    glVertex3f( 0.4f,  0.4f,  1.0f);
    glVertex3f( 0.4f,  1.1f,  1.0f);
    glVertex3f( 1.1f,  1.1f,  1.0f);
    glVertex3f( 1.1f,  0.4f,  1.0f);

  
    //Flesh Tone
    glColor3f( 1.0f,  0.7f,  0.5f);

    //face
    glVertex3f(  2.0f, -2.0f,  0.0f);
    glVertex3f(  2.0f,  2.0f,  0.0f); 
    glVertex3f( -2.0f,  2.0f,  0.0f);
    glVertex3f( -2.0f, -2.0f,  0.0f);
  
    glEnd();

 

The Camera

In this lab you will be trying to draw 2D objects. When you draw in 2D (or you are doing 3D CAD work) you should use a special geometry transformation that does not cause shape or size distortion. This transformation is called orthographic projection. In the last lab we wanted a 3D effect with foreshortening so we used perspective projection. Perspective transformation makes it hard to place things precisely on the screen. Shapes are distorted toward the edges and corners, and their apparent size varies with their distance from the camera. With orthographic projection you can precisely control how coordinates map to the drawing area, and objects render the same way regardless of distance.

To enable orthographic projection you can use gluOrtho2D(), or glOrtho(). There's a sample call to gluOrtho2D in the lab demo code. There is also a gluPerspective call in there so you can see how it differs from the orthographic functions.

The sample call in the notes centers the origin. y values range from -3.5 to 3.5, and the range of x values depends on the shape of the screen. You might find it more natural to map the range of values directly to the window's size. This can have unexpected consequences.

 


Assignment

Goals of this assignment:

For starters:

It is good to get a feeling for where you can put points on the scene.

The following instructions are meant to get you started from a fresh GLUT project using the template provided on the lab schedule:

  1. Add code init as indicated by the comments:
        
        //Set clear color to black
        //Set clear depth to maximum depth
        //Disable or enable the depth test as desired
        
  2. In main ensure that GLUT_DEPTH is included in the glutInitDisplayMode call.
  3. Add this code to the reshape function:
    
        GLdouble aspect_ratio;
        glViewport(0, 0, w, h);
    
        //Set up projection transformation
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        // compute the aspect ratio
        // this will help your drawing keep its shape when the window is resized
        aspect_ratio = (GLdouble)w/(GLdouble)h;
        
        // select the viewing volume
        gluPerspective( 40.0f, aspect_ratio, 0.1f, 20.0f );
        //glOrtho(-3.5f*aspect_ratio, 3.5f*aspect_ratio, -3.5f, 3.5f, 0.1f, 20.0f);
        
        // switch back to the modelview matrix and reset it
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        
        // reset our viewing distance (only necessary for Perspective view)
        glTranslatef( 0.0f, 0.0f, -10.0f );
    
  4. Now, you can use a line loop to draw the outline of the space that you can work in. Add this code to the top of the display function.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        glColor3f(1.0f, 0.0f, 0.0f); //draw in red
        glBegin(GL_LINE_LOOP);
        glVertex2f(4.0f,3.0f);
        glVertex2f(4.0f,-3.0f);
        glVertex2f(-4.0f,-3.0f);
        glVertex2f(-4.0f,3.0f);
        glEnd();
    
    Note: these values may vary slightly depending on the machine you are using.
  5. The approximate range of values if you is:

Note: you will be looking directly down the z axis. The positive z axis is pointing towards you.

Marking scheme and details of assignment:

(17 marks total)

Samples of previous work. Your work will end up in the gallery, so pay attention to artistic impression.

Deliverables

  1. The code from the GLUT OpenGL project from last week with the changes suggested in the marking scheme above. You may modify one of the other projects if you wish.
  2. BONUS: Use Alpha Blending on a polygon such that you can see part of your scene through it. (See the guy with glasses in the gallery).

Hints and Tips

You can find sample code at: http://www.sgi.com/products/software/opengl/examples/redbook/. Notice that this is C code. Either change the file extension to cpp or C project (in XCode this is a standard tool). On Windows if you get this error when you compile the sample code:

error C2381: 'exit' : redefinition; __declspec(noreturn) differs
try removing:
#include <stdlib.h>

Pay particular attention to:

You might want to look up glPointSize in the Online OpenGL Manual


Some Definitions

Taken from page 46 and 47 and 64 of Fosner's book, OpenGL Programming for Windows 95 and Windows NT