OpenGL: Cross Platform 3D Graphics
Highlights of this lab:
This lab is an introduction to
OpenGL
programming. You will see:
After the lab lecture, you
have one week to:
- Read the remaining contents
of this lab material.
- Complete your first simple
cross platform OpenGL graphics program.
- Be able to compile and run
your program.
- Define GLUT.
- Create the same program on a Mac or PC
using platform specific tools.
Online OpenGL Manual
Seminar Notes
A. General OpenGL Architecture
OpenGL is the definitive cross-platform 3D library. Its programming
style is the same on all platforms and if you modularise properly
porting between them should not be too difficult. Getting to the point
where you can begin writing OpenGL code is a bit trickier as the
process differs from operating system to operating system. Fortunately
there are cross platform libraries that can make your code extremely
simple to port. Regardless of how you choose to set up :
- The Application
module is developed by you, the programmer
- Windowing API
functions specifically developed
to support OpenGL are used to set up, shut down, and handle event
signals for an OpenGL drawing area called a Rendering Context (RC) in
a window or component . These functions may be defined by the
operating system, a third party widget set, or by a cross platform
OpenGL
library.
- OS Defined Libraries:
- Windows: WGL functions
- XWindows (Linux, most
UNIXs): GLX functions
- Mac OS X: NSOpenGL classes (usually)
- Cross Platform Widget Sets with OpenGL support
- Cross Platform OpenGL Libraries
- SDL
- GLUT (pronounced like the glut in gluttony)
- This is the one used be this class and the textbook. We will use two different implementations, both based on an older original. The original is GLUT 3.7, the alternatives are freeglut (PC, Linux) and Apple GLUT (Mac OS X).
- GLFW
- OpenGL API
functions defined in gl.h and provided by the active
OpenGL driver (specified by the RC) are used to draw.
- The OpenGL API exposed by the OS's display management system may be out of date. GLEW is one of several utilities you can use to easily expose new features using the OpenGL API extension mechanism.
- If you are in compatibility mode, you can use GLU (GL Utilities) to get simplified commands for common tasks, and special drawing functions for certain shapes or
text.
- OpenGL Drivers
implement the details of OpenGL functions either by passing data
directly to 3D hardware, performing computations on the main CPU or
some combination of the two.
- Whether you will render
with dedicated hardware or with a software
implementation is determined by what OpenGL features
you request with the Windowing API and what the computer's hardware
supports.
We will not go into detail on
exactly how everything works in this week's lab. Instead we will focus
on getting to the point where you can begin drawing.
B. Overview of an Interactive
Program.
Model-View-Controller Architecture
Interactive program design entails breaking your program into three
parts:
- The Model:
all the data that are unique to your program reside
there. This might be game state, the contents of a text file, or tables
in a database.
- The View:
it is a way of displaying
some or all of the model's data to user. Objects in the game state might be
drawn in 3D, text in the file might be drawn to the screen with
formatting, or queries on the database might be wrapped in HTML and
sent to a web browser.
- The Controller:
the methods for users to manipulate the model
or the view. Mouse actions and keystrokes might change the game state, select
text, or fill in and submit a form for a new query.
Object-oriented programming
was developed, in part, to aid with modularising the components of
Model-View-Architecture programs. Most user interface APIs are written
in an Object Oriented language.
You will be structuring your programs to handle these three aspects of
an interactive program. A 3D graphics:
- Model
consists of descriptions of the geometry, positioning, apperance and
lighting in
your scene. Ideally these could be saved to and loaded from a file.
- View
consists of the OpenGL rendering calls that set up your rendering area,
interpret the document, and send things to be drawn.
- Controller
is usually a set functions you write that respond to event
signals sent to your program via the Windowing API.
In your simplest programs your Document and View will be tightly
coupled. You might have one or two variables set up that control a few
things in your scene, and the scene itself may be described by hard
coded calls made directly to the OpenGL API with only a few dynamic
elements. Eventually you will learn to store the scene in separate data
structures.
The Main Events
All OpenGL programs should respond to at least three events:
- Setup
- There are at least two things to do here
- Aquire
Rendering Context: how you
perform this step is determined by your Windowing API.
- Set
initial OpenGL scene settings:
OpenGL's default settings are useless. An important part of the drawing pipeline, the shader program is missing. You will need to load, compile and activate one. It is also desirable to preload some objects you want to draw into data buffers and connect them to your shader programs. You may also need to enable, disable or configure various OpenGL settings to suit your rendering needs. There should be platform independent OpenGL code that sets the
scene for rendering.
- Rendering
- the function that does this is Windowing API specific, but the OpenGL
code will be completely generic and should be copy/paste portable.
- Shutdown - You should take care
of at least two things here
- Release Rendering Context: this
is determined by your Windowing API
- Clean
up after yourself.
There are other events you will frequently handle as well. If you
place your program in a resizeable window it is very important to
respond to size change events. If you don't, your rendered result will
be distorted when the window is resized. You may also wish to respond to mouse motions and clicks, and handle key strokes from the keyboard.
C. Building a Simple GLUT OpenGL
Program with XCode
GLUT is the cross platform programming library of choice for this course. Follow these instructions to learn how to set up a GLUT OpenGL program on the Macs in the lab. You will be given the opportunity to learn how to move the same code to Windows or Linux as part of your take home exercise.
Before starting these instructions make sure you have a Mac running Mountain Lion, and that you have installed XCode 4.6.2 and the 3rd party GLEW framework. You can find links to instructions for installing the GLEW framework on a Mac, or for configuring and using Windows and Linux PCs below the lab schedule. Windows and PC users should
1. Start Xcode
- Go to Spotlight, the
magnifying glass in the upper right corner of your screen.

- Type xcode.
- Click the match that appears
in the Applications section of the list. The same match may also be the
Top Hit.

You will see a Welcome screen
similar to this one:

This screen is useful, so please leave the "Show this window when
Xcode launches" box checked.
2. Create a New Project
The following will show you how to
create a framework for the
OpenGL viewing class. You will need to perform the following steps.
- Either click "Create New Project" on the welcome screen or:
- A
project manager window will appear and a dialogue box will slide out
- From the list of project
template categories on the left, select Mac OS X | Application.
- From the project
templates to the right, select Command Line Tool.
- Click Next
- Give your project a Product Name (I suggest OpenGL) and invent a Organization Identifier (yes it's required)
- Change the project Language to C++
- Click Next
- Choose a save location. The Desktop is the default and will do fine.
- Notice that you have the option of using the git project versioning system. This might be a good idea.
At this point, you have created a project to manage the
OpenGL program workspace window with multiple
areas, panes and bars. The following map is
from the Xcode User Guide, which is available through the help
menu.

Click here for a brief
tour of the workspace.
Click here for Apple's Xcode Basics Help to learn even more about Xcode
You should read through the tour and try the things suggested
there. At a minimum find main.cpp, read it and run it. The
program doesn't do much - it's just a Hello World app.
You may not see all the panes or areas yet. They
can be shown or hidden. Some may reveal themselves when you
perform certain actions.
3. Link to OpenGL and OpenGL Support Libraries
In MacOS X, libraries are stored as packages called Frameworks.
A framework encapsulates related headers, objects and documentation.
- Open the Link Binary With Libraries box to add a library
- Go to the Project Navigator
- Click on your project's top level group - there's a blue app icon beside it
- Click Build Phases
- Expand Link Binary With Libraries
- Click the + sign at the lower left of the Link Binary With Libraries box
- A popup window with a list of frameworks will appear.
- Find OpenGL.Framework in the list and click it to highlight it.
- Tip: type GL in the search box to filter the list
- Click Add
- Do the same for GLUT.Framework
- Next add GLEW.framework. It's a 3rd party framework, so you have to follow a different procedure to use it.
- Download GLEW.framework.zip, unzip it and place the file somewhere you will remember it.
- Go to add a framework as you did before. You won't find GLEW.Framework in the list
- Click the Add other... button
- In the Finder window that opens, navigate to the folder where you put GLEW.framework
- You should see GLEW.framework there.
- It is actually a folder. Make sure you get the whole folder, and not something inside.
- Click the Open button
And that's it. By adding frameworks you have added include and link
paths for the desired libraries with ease.
4. The Code
The XCode Project Wizard creates the minimum code necessary to run a
program. For our project we are creating a GLUT application and GLUT will
handle setting up windows. Later you will make a native Mac or Windows
application. The basic app will have enough code to show you an empty
window.
You have already set up your project with the necessary libraries, so
replace the code in your main.cpp with the following:
Basic Glut Program: main.cpp
// CS315 GLUT Template
#include "Angel.h"
//--- Headers, Prototypes and Global Variables -------------------------------
void init ( void );
void display ( void );
void reshape (int w, int h);
void keyboard ( unsigned char key, int x, int y );
//--- Main -------------------------------------------------------------------
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitWindowSize( 512, 512 );
#ifdef __APPLE__
glutInitDisplayMode( GLUT_3_2_CORE_PROFILE | GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
#else
// If you are using freeglut, the next two lines will check if
// the code is truly 3.2. Otherwise, comment them out
glutInitContextVersion( 3, 2 );
glutInitContextProfile( GLUT_CORE_PROFILE );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
#endif
glutCreateWindow( "OpenGL Template" );
glewExperimental = GL_TRUE;
glewInit();
init();
//Register callback functions
glutDisplayFunc( display );
glutKeyboardFunc( keyboard );
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
//--- Support functions ------------------------------------------------------
// OpenGL initialization
void init()
{
//Put Call to OpenGL Initializing function here
}
//----------------------------------------------------------------------------
void display( void )
{
// Put Call to main Drawing function here
glutSwapBuffers();
}
void reshape (int w, int h)
{
//Put Call to Size Changing function Here
}
//----------------------------------------------------------------------------
void keyboard( unsigned char key, int x, int y )
{
switch( key ) {
case 033: // Escape key
case 'q': case 'Q':
exit( EXIT_SUCCESS );
break;
}
}
Take a look at this code. There is a main function and four support
functions:
- main: Here we use
- GLUT functions to set up a request for an OpenGL Core Profile
OpenGL rendering context with certain other attributes, then
create a window with that rendering context. This must be done
before any other GLUT, GLEW or OpenGL commands, or they will have
no rendering context to act on and will crash.
- GLEW to access advanced OpenGL capabilities from that rendering context
- init to configure the context after it
is set up.
- glut*Func functions to register callbacks for important events - display requests, keyboard presses, and window reshaping
- glutMainLoop to hand control over to GLUT.
- init: This function is intended to perform first time
setup. It should be used to set an initial OpenGL state. It is a good
idea to know what the OpenGL defaults are, and to never assume that
they will be honoured by the driver provider. Always ask for what you
intend to use. In a GLUT program there is no init event, you simply do your init work once you have a valid rendering context. Other windowing APIs may have a specific init event. Both MFC and Cocoa do.
- display: this function will be called whenever the
window needs to be redrawn. It will be called at least once because a redraw
event is sent when the window is first displayed.
- reshape: this function will be called whenever the
window is resized. It will be called at least once because a resize
event is sent when the window is first displayed.
- keyboard: this function handles input from keys that
correspond to the ASCII table. If you want more advanced functions
This program is a GLUT + GLEW template. You will add code to the init,
display and reshape functions later.
Unfortunately, this program is not complete. You can compile and run the program, and you could even do some simple drawing, but most of the the OpenGL code we use in this class also depends on some external files.
Tip: add an existing file by saving it into your project folder next to main.cpp, then in XCode select the group with main.cpp in it, right click, and choose Add Files to "project name".... Files already in your project are greyed out for your convenience.
- Add all the files in Angel.zip. These
are OpenGL helpers provided by your textbook's author.
- Add all the files in uofrGraphics.zip. These are
OpenGL helpers provided by your lab instructors just for this
class.
- Add the shader files appropriate to your platform. Shaders help to shape and color the things you draw with OpenGL.
- The files: vshader.glsl and fshader.glsl
- The shaders need to be loaded by your program at runtime, so they need to be copied to your resources folder.
- Go back to build phases
- Click the upper left unlabeled + button
- Select New Copy Files Phase
- Expand the new copy file box by clicking the triangle in the upper left corner. You should see that the default destination is Resources - this is what you want
- Click +, select your shaders, then press Add.
You can now safely build and run your program. Note, however, that
there are no OpenGL calls in the template. You will get a raw OpenGL
window - it won't even be cleared for you so it may be filled with junk
from unitialized memory or from the Mac OS X compositor which also uses
OpenGL.
To run one of Dr. Angel's examples, go to his Windows code and replace your main.cpp with one of his .cpp files, then replace your shaders with the shaders that match it.
D. Setting Up the OpenGL Window and Drawing to It
OpenGL is set up and controlled by GLUT in this program. You will use
GLUT throughout the semester for your assignments. If you need to know
the details of a GLUT function mentioned in the notes, or if you want to
browse the available functions to learn new features, you can look at the
official online GLUT manual.
The GLUT functions in the template take care of the basics of setting
up an OpenGL drawing environment by:
- Creating and configuring a Device Context - this is mostly done
with glutInitDisplayMode(), but glutInit() may
also be involved.
- Establishing a Rendering Context - this is done by getting a
window and attaching the device context. In this program we set the
window's size and position with glutInitWindowSize,
glutInitWindowPosition, and
glutCreateWindow.
- Connecting callback functions that will handle events. Note that
there is little or no code in these functions.
- Starting the event loop.
In this section you will add code to appropriate callback functions to:
- Configure the Rendering Context for our window shape and
anticipated scene.
- Draw a scene.
- Reconfigure the Rendering Context when the window size
changes
- Clean up when the program is done. (This step is implementation
dependent and may not be possible on all platforms.)
You will learn more about how to do these things throughout the
semester. For now, copy the following code to appropriate places in
main.cpp and refer to the in-line comments for details.
New Code: Headers, Prototypes and Global Variables Section
// uofrGraphics library for CS315 Labs
// defines urgl object which can draw some simple shapes
/////////
#include "uofrGraphics.h"
// Window Event helpers
/////////
//Init
void InitializeOpenGL();
//Resize
bool ChangeSize(int w, int h);
//Draw
void Draw( void );
void PreRenderScene( void );
void RenderStockScene( void );
void RenderScene( void );
void drawSolidSphere(GLfloat radius, GLint slices, GLint stacks);
void drawSolidCube(GLfloat size);
//OpenGL State Management
////////
GLuint program1; //Shader
GLint uColor; //Shader color input
GLint uLightPosition;//Shader light position input
GLint mvIndex; //Shader positioning input
GLint projIndex; //Shader projection input
mat4 p, mv; //Local projection and positioning variables
// Scene Related Functions and Variables
////////
//Model Control Variables
GLfloat rotY = 0; //rotate model around y axis
GLfloat rotX = 0; //rotate model around x axis
New Code: Initialize Rendering Context
//Function: InitializeOpenGL
//Purpose:
// Put OpenGL into a useful state for the intended drawing.
// In this one we:
// - choose a background color
// - set up depth testing (Requires GL_DEPTH in Pixel Format)
// - turn on the lights
// - set up simplified material lighting properties
// - set an initial camera position
void InitializeOpenGL()
{
//Set up shader
program1 = InitShader( "vshader.glsl", "fshader.glsl" );
glUseProgram( program1 );
//Get locations of transformation matrices from shader
mvIndex = glGetUniformLocation(program1, "mv");
projIndex = glGetUniformLocation(program1, "p");
//Get locations of lighting uniforms from shader
uLightPosition = glGetUniformLocation(program1, "lightPosition");
uColor = glGetUniformLocation(program1, "uColor");
//Set default lighting and material properties in shader.
glUniform4f(uLightPosition, 0.0f, 0.0f, 10.0f, 0.0f);
glUniform3f(uColor, 1.0f, 1.0f, 1.0f);
//Configure urgl object in uofrGraphics library
urgl.connectShader(program1, "vPosition", "vNormal", NULL);
glEnable(GL_DEPTH_TEST);
}
New Code: Resize Window
// Function: ChangeSize
// Purpose:
// Tell OpenGL how to deal with a new window size.
// Arguments:
// int w, h: new width and height of the window, respectively.
bool ChangeSize(int w, int h)
{
GLfloat aspect_ratio; // width/height ratio
//Make sure the window size is valid
if ( 0 >= w || 0 >= h )
{
return false;
}
// tell OpenGL to render to whole window area
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
// compute the aspect ratio
// this is used to prevent the picture from distorting when
// the window is resized
aspect_ratio = (GLdouble)w/(GLdouble)h;
// calculate a new projection matrix
p = Perspective(50.0f, aspect_ratio, 0.5f, 20.0f);
// send the projection to the shader
glUniformMatrix4fv(projIndex, 1, GL_TRUE, p);
return true;
}
New Code: Draw Scene
// Function: Draw
// Purpose:
// Control drawing of the scene. To be called whenever the window
// needs redrawing.
void Draw()
{
// Clear the screen and the depth buffer
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
PreRenderScene();
RenderStockScene();
RenderScene();
}
// Use this to perform view transforms or other tasks
// that will affect both stock scene and detail scene
void PreRenderScene()
{
// select a default viewing transformation
// of a 20 degree rotation about the X axis
// then a -5 unit transformation along Z
mv = mat4();
mv *= Translate( 0.0f, 0.0f, -5.0f );
mv *= RotateX ( 20.0f );
//Allow variable controlled rotation around local x and y axes.
mv *= RotateX(rotX);
mv *= RotateY(rotY);
}
// Function: RenderStockScene
// Purpose:
// Draw a stock scene that looks like a
// black and white checkerboard
void RenderStockScene()
{
const GLfloat delta = 0.5f;
// define four vertices that make up a square.
vec4 v1( 0.0f, 0.0f, 0.0f, 1.0f);
vec4 v2( 0.0f, 0.0f, delta, 1.0f);
vec4 v3( delta, 0.0f, delta, 1.0f);
vec4 v4( delta, 0.0f, 0.0f, 1.0f);
int color = 0;
// define the two colors
vec3 color1( 0.9f, 0.9f, 0.9f );
vec3 color2( 0.05f, 0.05f, 0.05f );
mat4 placementX = mv;
mat4 placementZ;
placementX *= Translate( -10.0f * delta, 0.0f, -10.0f * delta );
for ( int x = -10 ; x <= 10 ; x++ )
{
placementZ = placementX;
for ( int z = -10 ; z <= 10 ; z++ )
{
glUniform3fv(uColor, 1, (color++)%2 ? color1 : color2 );
glUniformMatrix4fv( mvIndex, 1, GL_TRUE, placementZ );
urgl.drawQuad(v1, v2, v3, v4);
placementZ *= Translate( 0.0f, 0.0f, delta );
}
placementX *= Translate( delta, 0.0f, 0.0f );
}
}
// Function: RenderScene
// Purpose:
// Your playground. Code additional scene details here.
void RenderScene()
{
// draw a red sphere inside a light blue cube
// Set the drawing color to red
// Arguments are Red, Green, Blue
glUniform3f(uColor, 1.0f, 0.0f, 0.0f);
// Move the "drawing space" up by the sphere's radius
// so the sphere is on top of the checkerboard
// mv is a transformation matrix. It accumulates transformations through
// right side matrix multiplication.
mv *= Translate( 0.0f, 0.5f, 0.0f);
// Rotate drawing space by 90 degrees around X so the sphere's poles
// are vertical
mv *= RotateX( 90.0f );
//Send the transformation matrix to the shader
glUniformMatrix4fv(mvIndex, 1, GL_TRUE, mv);
// Draw a sphere.
// Arguments are Radius, Slices, Stacks
// Sphere is centered around current origin.
urgl.drawSolidSphere(0.5f, 20, 20);
// when we rotated the sphere earlier, we rotated drawing space
// and created a new "frame"
// to move the cube up or down we now have to refer to the z-axis
mv *= Translate(0.0f, 0.0f, 0.5f);
//Send the transformation matrix to the shader
glUniformMatrix4fv(mvIndex, 1, GL_TRUE, mv);
// set the drawing color to light blue
glUniform3f(uColor, 0.5f, 0.5f, 1.0f);
// Draw the cube.
// Argument refers to length of side of cube.
// Cube is centered around current origin.
urgl.drawSolidCube(1.0f);
}
Once you have added all this, go back to main.cpp, add code to the
GLUT event callback functions to call the appropriate helpers, then run
your program to see what is displayed.
It should look like this:
Good luck!
If your program runs correctly, you might be wondering how exactly the
picture is drawn. That is, you might want to understand how each function
works. Well, you do not have to worry too much about this in the first
lab. Over the rest of the semester we will go through these subjects
one-by-one in detail. Of course, we will learn a lot of more advanced
functions and features.
Tip: Only after you have called the initialization function will you know whether your shaders are being copied. If you get complaints about vshader.glsl or fshader.glsl in the output pane, go back and fix it.
E. Backup Your Project before You Leave
It's a good idea to backup your project before you leave. You can
copy your work to a USB drive or your Hercules account. If you use an IDE
such as XCode or Visual Studio you must exit it before doing a backup or
you will be unable to copy some of your files. Remember to save all the
documents before quitting.
Copy the entire project folder. This folder can be a bit large. To
save space you might want to delete build products and support files as
they can be rather large. Don't worry - you will get it back the next
time you compile your program. All the source files should be small
enough to put on a floppy disk.
Note: Cleaning project folders:
- XCode:
- Visual Studio:
- All Debug and Release folders
- All .sdf files (They are rather large... usually > 16MB)
Note: Opening Existing projects
To open an existing project:
- go to the project's directory
- XCode: double click on the file with the .xcodeproj extension
- Visual Studio: double click on the file with the .sln extension
The project should be up and ready to go, just as you left it.
EXERCISE
To be completed and submitted to URCourses by the first midnight one
week after the lab.
Play with OpenGL
Try to make some simple changes to the scene. For help with the
function calls refer to the Official Online OpenGL
Manual.
- Move the red sphere (urgl.drawSolidSphere) up so
that its bottom is exactly 0.5 units above the checkerboard.
- Make the blue cube (urgl.drawSolidCube) half as
big and position it directly below the sphere. It should be a perfect
fit.
Your result should look like this:

You may add other objects to the scene if you wish, but please keep the blue cube and red sphere visible.
Think About Event Programming
- What is an event?
- Can you trigger an event with code?
- Find glutPostRedisplay() in the
online GLUT documentation. What is its purpose?
- Find the entry on the glutReshapeFunc() event callback registration function.
- You have an example
of its use in your code.
- Notice how its argument is satisfied by the void reshape(int, int) function.
- You can grab the edges of your window to resize it.
- Try commenting out the callback registration line. What happens when you resize the window?
- Add interactivity to your program by registering your own event callback function:
- See if you can find a callback that triggers when you move the mouse while one or more mouse buttons are pressed (this is a drag event)
- Use the incoming x and y position of the mouse to rotate the scene.
- You can modify the global rotX and rotY variables to do this.
- Extra: What would you have to do so the scene only moves when the left mouse button is held? Implement it if you can.
Learn About the CS315 Libraries
You probably have a clear idea of what OpenGL is, but you may be confused about all the libraries used in this lab's program. Take a moment to learn about them.
- What is freeglut and why would you want to use it?
- What is the URL for the freeglut project?
- What is GLEW and why would you want to use it?
- What is the URL for GLEW's homepage?
- What is in Angel.zip and where did it come from?
- What is in uofrGraphics.zip and who wrote it?
Cross Platform OpenGL
Now that you have created a GLUT based OpenGL program on a Mac,
demonstrate that OpenGL+GLUT+GLEW is a cross platform API by compiling
and running your program with a different operating system. You will find
Visual Studio specific set up information the CS 315 lab schedule
page. The brave may attempt to do this on Linux or other UNIX-like OS.
Deliverables
Package your project folders and written answers into
one zip file. Submit the .zip file to URCourses.
Include the following:
- Working OpenGL XCode project with modified box/sphere position. It
should spin when you press any mouse button move the mouse. It should not spin
continuously.
- Working GLUT based project on a second platform.
- Windows: a cleaned up Visual Studio project
- Linux or other UNIX-like: a screen shot showing your build and run commands and the resulting OpenGL window.
- Document with written answers to the "Think About Event
Programming" questions.
- Document with written answers to: "Learn About the CS315 Libraries"
References
- The Cocoa code is based on a sample from Cocoa Programming for Mac
OS X, 3rd. Ed.
- The GLUT code is based on the RedBook's
examples and Dr. Angel's textbook samples.