Exercise 2: Shape Primitives
This exercise will provide an introduction to the various 2D and 3D shape primitives used in OpenGL and GLUT.
Preliminaries
Please create a directory ~/cgi/ex2 and copy /vol/www/ee/Teaching/Courses/CGI/exercise2/* to ~/cgi/ex2. You should now be ready to start.
1. Primitives: Point/Line/Triangle
(i) Open the file 'draw.c'
which is a basic program for drawing 2D shapes
- the program is setup to draw
a three points of size of 1 pixel
(ii) Compile and run the program
make -f make_draw
./draw
You should see a window containing three
points
(iii) Modify the program to draw a line between pairs of points given by
GL_LINES
the line width can be modified with glLineWidth()
(iv) Next draw a closed loop of lines using
GL_LINE_LOOP
- this should show the outline of a triangle
(v) Now modify the program to draw a single coloured triangle using GL_TRIANGLES
(vi) Add one additional vertices to draw two triangles using GL_TRIANGLE_STRIP
glVertex2f(0.5,0.5);
(vii) Next draw the same polgygon using GL_QUADS and GL_POLYGON
(vii) Change the colour of each of the vertices so that they are different
for each vertex using
glColor3f(r,g,b) to specify individual colours for vertices
All shapes in OpenGL are drawn using the primitives (point/line/triangle) covered in this section.
2. Vertex Arrays
The primitives shapes in the previous section were created by specifiying individual vertices
between the glBegin() and glEnd()
statements. This is inefficient for large numbers of polygons as it
requires one function call glVertex2f()
for each vertex plus an addition function call for each vertex
property glColor3f() etc. The use of repeated function calls can dominate
drawing time for large shapes. OpenGL has a mechanism called 'vertex
arrays' which allow specification of vertices and
related data such as colour in arrays which can be accessed directly
with a small number of function calls. In this exercise you will modify
the primitive drawing program
to use vertex arrays.
Three modifications are required to use vertex arrays:
(i) Enable the use of a vertex array by adding the
following command in the GL initialisation:
glEnableClientState(GL_VERTEX_ARRAY);
(ii) Specify data for the vertex array by declaring a static array of
vertices at the start of the program
static GLfloat vertices[]=
{-0.5,-0.5,
0.5,-0.5,
0.0,0.5,
0.5,0.5};
(iii) Next, specify how the data is stored in the array using the command
glVertexPointer(2,GL_FLOAT,0,vertices);
in the display function.
The vertex pointer function is specified as follows:
glVertexPointer(GLint size,GLenum type,GLsizei stride,GLvoid
*pointer);
size - number of coordiantes for each vertex
type - specifies the data
type
stride - byte offset
between consecutive vertices
pointer - pointer to the
vertex array
(iv) To draw a triangle using the vertex array there are a number of methods
Try implementing each
method to verify that they produce the same result.
Method 1: Dereference a single
array element
To draw a single
triangle:
glBegin(GL_TRIANGLES);
glArrayElement(0);
glArrayElement(1);
glArrayElement(2);
glEnd();
Note: when the first array element is dereferenced the entire array is sent from the client to
the
server for rendering.
Method 2: Dereference a list of
array element
This method uses a second array of indicies to define one or more primitives:
At the start of the program define a
static array
static GLubyte triangle1[] = {0,1,2};
In the display function
glDrawElements(GL_TRIANGLES,3,GL_UNSIGNED_BYTE,triangle1);
Method 3: Dereference a
sequence of array elements
This
functions constucts the entire sequence of primitives
from all enabled arrays
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
(v) Now create a second array to store the vertex colours and enable vertex
colouring following steps
(i)-(iii) above. Use
functions:
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(....);
You should now be able to draw the coloured triangles
with methods 1-3 above. Note using
the vertex arrays you do not require function calls to glVertex*() & glColor*()
for each vertex.
3.. 3D Shape Primitives
GLUT provides several routines for drawing common 3D shapes.
(i) Modify the draw.c program to draw a cube using the following function:
glutWireCube(0.5);
The cube is created at the origin with aligned with the
axis.
Rotate the cube so that you can see more than one side.
glRotatef(45,0.0,1.0,1.0);
(ii) Draw a solid shaded version of the cube using the command
glutSolidCube(0.5);
Note the edges are not visible as there is no scene lighting.
(iii) Other 3D shape primitives can be drawn using the GLUT functions:
glutWireSphere(0.5,10,10); - the parameters 10,10 are
horizontal/vertical slices.
glutWireTorus(0.5,0.75,10,10);
- the first 2 parameters specify inner and outer radius
glutWireIcosahedron();
glutWireOctahedron();
glutWireTetrahedron();
glutWireDodecahedron();
glutWireCone(0.5,0.75,10,10);
glutWireTeapot(0.5);
- the famous Utah teapot
Create a scene with multiple 3D shapes of different
colours.
Shapes can be rotated and translated from the origin
using the functions:
glRotatef(angle,
x,y,z); - rotate by angle about axis (x,y,z)
glTranslatef(x,y,z); - translate by (x,y,z)
Remember to reset the model transformation state to
identity between each shape otherwise the
translation and rotation are cummulative.
Use:
glLoadIdentity();
4. Building a Sphere
This exercise illustrates the representation of 3D shapes using simple
primitives such as triangles.
A sphere can be approximated to arbitrarily well using smaller and smaller
triangles.
(i) Open the program 'draw_sphere.c'.
This program models a sphere of unit radius centered at the origin by triangulating the surface.
Starting with a tetrahedra
with 4 vertices on the unit sphere each triangular face of the
tetrahedra is subdivided into 4
triangles. The triangle is subdivided by computing the mid-point
of each edge.
Take a look at the function subdivide to ensure that you understand how it works.
Compile and run the program with 'nlevel=1':
make -f make_draw_sphere
./draw_sphere
Try changing the number of levels 'nlevel' to 0,2,4,6 to see the change in shape.
(ii) To create the tesellation of a sphere (centered on the origin) we can normalise the distance
of vertices from the origin be one unit. The
normalised vertices will then lie on the unit sphere.
Add a function normalise()
to normalise the vertices to unit length by dividing by the
distance of the vertex from the origin. In other words, the
vector length = sqrt(x*x + y*y +z*z).
Implement the normalise routine, normalise the vertex
lengths in the subdivision routine
then recompile and run the program.
Try changing the number of levels 'nlevel'
to 0,2,4,6 to see the change in shape.
(iii) Now, change the program to inteactively
increase/decrease the number of levels
by clicking on the right/left mouse buttons (see
exercise 1.4). Use the GLUT functions
to process the mouse input, change nlevel and
(iv) Viewing the wireframe
model it is difficult to distinguish triangles for the front and back of the
model. Modify the program to not display
triangles facing away from the triangle by enabling
back face culling in initgl():
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);