In this article I will demonstrate a basic introduction in OpenGL. It will be in tutorial format that the reader can follow along on their own. The final result should be a working template that can be used to create your own projects using OpenGL.
Contents
- 1 History
- 2 Prerequisite
- 3 OpenGL Project
- 4 Creating the Source
- 4.1 Headers
- 4.2 Globals and Forward-Declarations
- 4.3 The Main Method
- 4.4 The Cleanup Method
- 4.5 The InitGL Function
- 4.6 The DisplayGL Method
- 4.7 The IdleGL Method
- 4.8 The KeyboardGL Method
- 4.9 The MouseGL and MotionGL Methods
- 4.10 The ReshapeGL Method
- 4.11 Rendering 2D Primitives
- 4.12 Drawing 2D Polygons
- 4.13 Using Colors
- 4.14 Adding some Spin
- 4.15 Enter the 3rd Dimension
- 5 Video Lecture
- 6 References
- 7 Download the Source
History
OpenGL was introduced by Silicon Graphics Inc in 1992 and was used in various fields that required complex visualization of 2D and 3D images such as CAD, virtual reality, flight simulators, and scientific visualization. It was also very obvious to enthusiasts that OpenGL could be used to render graphics in video games as well.
In 2006 SGI transferred control of the OpenGL standard from the ARB to a new working group within The Khronos Group.
OpenGL is both free and platform-independent making it the most widely used, supported, and best documented 2D and 3D graphics API.
So enough boring history, lets make something!
Prerequisite
The only prerequisite for developing OpenGL applications using the core OpenGL API is that you have a supported development environment such as Microsoft’s Visual Studio 2008 and that you have supported drivers from your graphics card manufacturer. Installing Visual Studio 2008 will provide you with the headers and libs that are required to build your OpenGL applications, and your graphics vendor will provide the OpenGL DLL that is used at runtime and implements the OpenGL functionality.
GLUT
Optionally, you can use the OpenGL Utility Toolkit (GLUT) if you want to be able to create code that is portable to other platforms with the least amount of changes to your source. You can obtain the source code for GLUT from the OpenGL website, or you can download the pre-built windows binaries from Nate Robin’s GLUT for Win32 website.
- GLUT Source: http://www.opengl.org/resources/libraries/glut/
- GLUT pre-built binaries for Win32: http://www.xmission.com/~nate/glut.html
GLEW or GLEE
If you plan on using extensions in your program, you can use either the OpenGL Extension Wrangler Library (GLEW), or The OpenGL Easy Extension Library (GLEE).
OpenGL Project
In this tutorial, I will show how to create a project in Visual Studio 2008 that can be used as a starting point for future OpenGL projects. I choose to use VS2008 because it is still a very widely used development environment and many samples and tutorials will still use VS2008 for their C++ projects.
We will start by creating a new project in Visual Studio 2008.
In Visual Studio 2008, create a new project by either going to the File menu and selecting New > Project… or use the short-cut Ctrl+Shift+N:
From the New Project dialog box that appears, select Visual C++ > General from the Project types: list and select the Empty Project template in the Templates: frame on the right. Choose any name you want for your project (I will use OpenGL Template) and choose a location to store your new project.
Once you click OK on the New Project dialog, you will be presented with an empty solution which has only one project.
Creating the Source
Create a new source file called main.cpp
in the OpenGL Template project by right-clicking the project node in the Solution Explorer and select Add > New Item…
You will be presented with the Add New Item dialog box. Select Visual C++ > Code from the Categories: list and select the C++ File (.cpp) from the Templates: frame. Choose main for the name of the file to create. You can choose to have the new file created in the default location which is the same location as that of the project file, but I always like to keep my compiled source files in a folder called src
.
After you click the Add button, the main.cpp
source file should be added to your project’s Source Files folder and opened in the editor.
Headers
We will first include a few headers into our main source code that we will be using in this example:
1 2 3 4 |
#define _USE_MATH_DEFINES #include <math.h> #include <iostream> #include <ctime> |
We will also use GLUT in this project, so we will add the GLUT header as well:
6 |
#include <GL/glut.h> |
If we decided to use GLUT, or any other 3rd party library that isn’t magically part of Visual Studios search paths for headers and libraries, then we must tell Visual Studio explicitly where it can find the headers and libraries that are needed to compile and link our program. If you would like your project to be portable, this usually means that the 3rd party headers and libraries need to be included together with the source for your project.
In the case of GLUT, we will copy the header to a directory relative to our project folder called include\GL and the lib file to a directory in our project called lib. Then we need to tell Visual Studio where to find these headers and libs.
Right-click on the project node in the solution explorer and select Properties from the pop-up menu that appears.
In the Property Pages template that appears, select Configuration Properties > C/C++ > General from the list.
Project properties are always described relative to the location of the project file itself. So directories to our additional include folder and additional library directories should also be described relative to our include folder.
In the Additional Include Directories text field, add include (or whatever name you used for the directory that contains your header files).
Next, we need to specify the folder where our compiled libraries are located.
Select Linker > General from the list view and in the Additional Library Directories text field, add lib (or whatever name you used for the directory that contains your compiled library files).
And finally, we need to specify exactly what libs we want to link in our program.
Select the Linker > Input option in the list and in the Additional Dependencies text field add the name of the 3rd party libs we want to link in our program. In the case of GLUT, we would specify glut32.lib
.
We could actually argue that the inclusion of this lib as an additional dependency is superfluous because the library will be included automatically by the following code in the glut.h
header file:
66 67 68 |
# pragma comment (lib, "opengl32.lib") /* link with Microsoft OpenGL lib */ # pragma comment (lib, "glu32.lib") /* link with Microsoft OpenGL Utility lib */ # pragma comment (lib, "glut32.lib") /* link with Win32 GLUT lib */ |
So as long as your linker can find these libraries in the Additional Include Directories, these libs will be automatically linked into your final project without needing to specify them in the Additional Dependencies property.
And the final step is to copy the pre-compiled DLL file (glut32.dll
) into the same folder where our binary will be generated for our program. We usually specify a folder relative to our project file called bin that will be used to store the compiled result of our program as well as 3rd party compiled DLL’s. In order for our program to find and load these DLL’s, we must be sure the DLL’s are in the default search paths for executable files. When we run our program in Visual Studio 2008, the default working folder is the location of the project file. This is usually undesirable because the runtime will not find the DLL’s in our bin folder because the bin folder is not in the default search paths. To resolve this, we will modify the default working folder that is used when we run our program.
In the configuration properties page, select the Configuration Properties > Debugging. In the Working Directory text field, we will specify the special macro $(OutDir)
as the working directory when we debug our program using the Visual Studio debugger. This will also guarantee that the location of our executable and the 3rd party DLLs will be found in the search paths.
This user file is not something you generally package with your project files and source files because it is specific to this user on this computer. What you can do, is make a copy of this file and remove the COMPUTERNAME.username part from the filename which would result in a file with the name ProjectName.vcproj.user and this file will be used as the default user file for all new users who will work on your project (and for you the next time you want to build and run your program after a fresh install of your operating system for example). Visual Studio will automatically generate a new file for the user that is specific to that user on that computer based on the file that is called ProjectName.vcproj.user. Anytime you make changes to the default settings in the debugging options, you will need to copy and rename the file again. If you want other users to get the changes you specified in the .user file, they need to delete their existing .COMPUTERNAME.username.user file and Visual Studio will regenerate their user settings file based on the default .user file the next time they open the project.
Now that we have our project setup with all our headers, libs and DLL’s we can continue programming!
Globals and Forward-Declarations
The first thing we will do is define a few structs that will be used as vector type objects for 2D and 3D space.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
struct float2 { float2( float _x = 0.0f, float _y = 0.0f ) : x(_x), y(_y) {} float x; float y; }; struct float3 { float3( float _x = 0.0f, float _y = 0.0f, float _z = 0.0f ) : x(_x), y(_y), z(_z) {} float x; float y; float z; }; |
And a few global variables that will be used by GLUT to initialize the render window:
25 26 27 |
int g_iWindowWidth = 512; int g_iWindowHeight = 512; int g_iGLUTWindowHandle = 0; |
If an error occurs, we will use a global error code to report the error when the application exists.
28 |
int g_iErrorCode = 0; |
And we will also forward-declare the OpenGL callback functions.
30 31 32 33 34 35 36 |
void InitGL( int argc, char* argv[] ); void DisplayGL(); void IdleGL(); void KeyboardGL( unsigned char c, int x, int y ); void MouseGL( int button, int state, int x, int y ); void MotionGL( int x, int y ); void ReshapeGL( int w, int h ); |
Since these are pretty important functions, I will take some time to explain their use.
DisplayGL()
: This function will be registered as the render function that is invoked by GLUT when the current window needs to be redisplayed.
IdleGL()
: This function is registered with GLUT and it is invoked when windows system events are not being received. This function is ideal for background processing tasks such as the game’s Update methods.
KeyboardGL( unsigned char c, int x, int y )
: Whenever the user presses a key on the keyboard, this method will be used as the callback method.
unsigned char c
contains the ASCII keyboard key code that was pressed.int x, int y
are the mouse’s location in window-relative coordinates at the moment the key was pressed.
MouseGL( int button, int state, int x, int y )
: This method is registered with GLUT and it will be invoked when the user presses a mouse button on the current window. Each press and release action will generate an event.
int button
will be one ofGLUT_LEFT_BUTTON
,GLUT_MIDDLE_BUTTON
, orGLUT_RIGHT_BUTTON
.int state
will be eitherGLUT_DOWN
, orGLUT_UP
indicating whether the event was a pressed, or released event.int x, int y
store the position of the mouse relative to the current window.
MotionGL( int x, int y )
: This method is invoked when the mouse moves within the window while one or more mouse buttons are pressed.
int x, int y
store the position of the mouse relative to the current window.
ReshapGL( int width, int height )
: The reshape callback function is invoked when the render window is resized. This method will also be called the first time the window is displayed so it can be used as the only method that is used to setup the projection matrix.
int width, int height
parameters specify the new window size in pixels.
We will also declare a few functions that will be used to draw some primitives.
39 40 41 |
void DrawRectangle( float width, float height ); void DrawCircle( float radius, int numSides = 8 ); void DrawTriangle( float2 p1, float2 p2, float2 p3 ); |
44 45 46 |
void DrawCube( float width, float height, float depth ); void DrawSphere( float radius ); void DrawPyramid( float scale = 1.0f ); |
These functions are defined later.
There will be several scenes in this demo, so I will also declare an enumeration to store the different scene types and a variable to store the current value.
48 49 50 51 52 53 54 55 56 |
enum ESceneType { ST_Scene1 = 0, ST_Scene2, ST_Scene3, ST_Scene4, ST_NumScenes }; ESceneType g_eCurrentScene = ST_Scene1; |
Since we also want to rotate some of our primitives, we will store some global parameters that will keep track of the current rotation.
59 60 61 |
float g_fRotate1 = 0.0f; float g_fRotate2 = 0.0f; float g_fRotate3 = 0.0f; |
And we will use the clock_t
type to store the number of ticks between frames.
63 64 |
std::clock_t g_PreviousTicks; std::clock_t g_CurrentTicks; |
Since our demo will render several different scenes, we will also declare some functions to render those scenes.
66 67 68 69 70 71 72 73 |
// Render a simple scene with 2D primitives void RenderScene1(); // Render a slightly more complex scene using different colors void RenderScene2(); // Render a scene with animated transformations void RenderScene3(); // Render a scene with 3D objects that perform rotations on all 3 axis. void RenderScene4(); |
And the last function that will be forward-declared is the function that is responsible for cleaning up our resources.
76 |
void Cleanup( int exitCode, bool bExit = true ); |
The Main Method
The first function we will define is the “main” method. Here is where it all begins.
78 79 80 81 82 83 84 85 |
int main( int argc, char* argv[] ) { // Capture the previous time to calculate the delta time on the next frame g_PreviousTicks = std::clock(); InitGL( argc, argv ); glutMainLoop(); } |
The first thing we do in this function is to capture the current ticks from the system clock. Later, we will query the clock again to find out how much time has passed since the last time we updated our game state.
The next method InitGL( int argc, char* argv[] )
will initialize the OpenGL context and the render window. This function is described in more detail later.
Then we invoke the glutMainLoop()
method. Invoking this function will start the GLUT event processing loop. Once it is called, it will never end. Everything your program does will be done using the function callbacks which will be registered in the InitGL()
method.
The Cleanup Method
The Cleanup( int errorCode, bool bExit )
method is used to cleanup resources used by your program. In this case, we only have to destroy the render window we have created.
88 89 90 91 92 93 94 95 96 97 98 99 100 |
void Cleanup( int errorCode, bool bExit ) { if ( g_iGLUTWindowHandle != 0 ) { glutDestroyWindow( g_iGLUTWindowHandle ); g_iGLUTWindowHandle = 0; } if ( bExit ) { exit( errorCode ); } } |
The InitGL Function
Setting up an OpenGL context and render window is very easy using GLUT as we will see in the next function.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
void InitGL( int argc, char* argv[] ) { std::cout << "Initialise OpenGL..." << std::endl; glutInit(&argc, argv); int iScreenWidth = glutGet(GLUT_SCREEN_WIDTH); int iScreenHeight = glutGet(GLUT_SCREEN_HEIGHT); glutInitDisplayMode( GLUT_RGBA | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH ); glutInitWindowPosition( ( iScreenWidth - g_iWindowWidth ) / 2, ( iScreenHeight - g_iWindowHeight ) / 2 ); glutInitWindowSize( g_iWindowWidth, g_iWindowHeight ); g_iGLUTWindowHandle = glutCreateWindow( "OpenGL" ); // Register GLUT callbacks glutDisplayFunc(DisplayGL); glutIdleFunc(IdleGL); glutMouseFunc(MouseGL); glutMotionFunc(MotionGL); glutKeyboardFunc(KeyboardGL); glutReshapeFunc(ReshapeGL); // Setup initial GL State glClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); glClearDepth( 1.0f ); glShadeModel( GL_SMOOTH ); std::cout << "Initialise OpenGL: Success!" << std::endl; } |
The glutInit()
method is used to initialize the GLUT library and initiate communication with the windowing system. In this demo, we simply pass along the program’s command-line arguments without change.
The glutGet()
method can query the current GLUT state variables. In this case, we will simply query the current screen width and height using the GLUT_SCREEN_WIDTH
, and GLUT_SCREEN_HEIGHT
. This is the resolution of the current screen that is associated with the current windowing system. We will use this value to position our window in the middle of the screen.
The glutInitDisplayMode()
method is used to initialize the display mode that will be used to create new top-level windows. The bitmask mode parameter specifies the display mode.
GLUT_RGBA
: Set’s the color mode to RGBA. The render window will be created with a color buffer that stores components for the color channels (as apposed toGLUT_INDEX
which stores the color buffer in index color mode).GLUT_ALPHA
: This will ensure that the color buffer has a channel to store the alpha of the pixel.GLUT_DOUBLE
: The window will be double buffered. Two buffers will be used to present the final image, one is used to render to, while the other is the one currently being presented on the screen. At the moment the application is finished rendering, the buffers are swapped. This prevents an effect called screen tearing.GLUT_DEPTH
: This will ensure a depth buffer is created for the render window. The depth buffer is used to make sure that pixels that appear closer to the viewer are not drawn behind pixels that appear further away from the viewer, regardless of the order in which the pixels are rasterized to the color buffer.
The glutInitWindowPosition()
and glutInitWindowSize()
methods will initialize the position and size of the newly created window. The x
, and y
parameters are the number of pixels relative to screen space position where the top-left is (0,0)
. The width
and height
parameters are specified in screen pixels.
The glutCreateWindow
will actually create the render window using the parameters we have specified before we call this function. The single parameter to this function specifies the name that will be used to identify the window. This method returns a single int that is used to refer to this window. This will also implicitly set the current window state for GLUT to the newly created window.
The next set of functions glutDiplayFunc()
to glutReshapeFunc()
will register the callbacks for the GULT event system. These callbacks will be associated with the current window.
The glClearColor()
method specifies the color that will be used to clear the color buffer when the glClear()
method is invoked with the GLUT_COLOR_BUFFER_BIT
bitfield parameter.
The glClearDepth()
method is used to specify the value to clear the depth buffer to when the glClear()
method is invoked with the GLUT_DEPTH_BUFFER_BIT
bitfield parameter. In this case, we specify 1.0
as the only parameter to this method. A valid depth range is from 0.0
(absolutely close to the viewer), to 1.0
(absolutely far from the viewer). Setting the depth buffer to 1.0
will ensure that all new pixels (within the view frustum) will be drawn.
The glShadeModel()
method specifies the shading model to use for rendering. The GL_SMOOTH
parameter will ensure that the color of pixels are interpolated between vertices for a smoother looking surfaces. The other option GL_FLAT
will shade the face of a triangle with a flat color. The actual color used is dependent on the type of primitive being drawn.
The DisplayGL Method
The display method is the callback function that was registered with the GLUT event processing loop and is invoked whenever the current window contents need to be redrawn.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
void DisplayGL() { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); switch ( g_eCurrentScene ) { case ST_Scene1: { RenderScene1(); } break; case ST_Scene2: { RenderScene2(); } break; case ST_Scene3: { RenderScene3(); } break; case ST_Scene4: { RenderScene4(); } break; } glutSwapBuffers(); glutPostRedisplay(); } |
The first thing we will do at the beginning of almost every render call (except in the case where you might want to perform some special effect that might require the color buffer not to be cleared) is to clear our buffers. This is done with the glClear()
method. This method will reset the contents of the current render target (this is usually the frame buffer for the current window). The parameters that are passed to this method will determine which buffers are cleared. In our case, we want to clear the color buffer and the depth buffer using the parameters GL_COLOR_BUFFER_BIT
and GL_DEPTH_BUFFER_BIT
. We don’t need to specify the GL_STENCIL_BUFFER_BIT
since we didn’t specify a stencil buffer in our glutInitDisplayMode()
method.
The switch statement will be used to determine the scene that will be rendered. Each scene is described in more detail later.
The method glutSwapBuffers()
will flip the back buffer (the off-screen buffer that is currently being rendered to) with the front buffer (the frame buffer that is currently being displayed). If we didn’t specify GLUT_DOUBLE
in the glutInitDisplayMode()
, then this method would have no effect because we would only have one buffer (the front buffer that is always displayed).
And the final function call glutPostRedisplay()
is used to tell GLUT that we are ready to render another frame. Invoking this method will not cause another frame to be immediately rendered with the DisplayGL()
method (otherwise calling this method here would cause an infinite loop), but instead it simply marks, or flags the current window to be redisplayed.
The IdleGL Method
The IdleGL()
method will be invoked whenever there are no events to be processed in the GLUT even loop. We will use this method to update the logic of our demo.
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
void IdleGL() { // Update our simulation g_CurrentTicks = std::clock(); float deltaTicks = ( g_CurrentTicks - g_PreviousTicks ); g_PreviousTicks = g_CurrentTicks; float fDeltaTime = deltaTicks / (float)CLOCKS_PER_SEC; // Rate of rotation in (degrees) per second const float fRotationRate = 50.0f; // Update our rotation parameters g_fRotate1 += ( fRotationRate * fDeltaTime ); g_fRotate1 = fmodf( g_fRotate1, 360.0f ); g_fRotate2 += ( fRotationRate * fDeltaTime ); g_fRotate2 = fmodf( g_fRotate2, 360.0f ); g_fRotate3 += ( fRotationRate * fDeltaTime ); g_fRotate3 = fmodf( g_fRotate3, 360.0f ); glutPostRedisplay(); } |
The first thing we do is calculate the amount of time that has passed since the last time this method has been called. The std::clock()
function returns the number of clock ticks that have elapsed since the program has started execution. We will use the calculated deltaTime
value to ensure that our program runs smoothly independently of the frame rate and we can express our program’s values in terms of elapsed time in seconds.
The next few lines (181-188) will update our rotation parameters that will be used later to rotate our primitives. You may have noticed that all three parameters are updated at the same rate and I probably could have just used a single rotate variable, but if I wanted to, I could rotate each axis of the primitive with at a different rate.
The final line has the same effect here as it did in the DisplayGL()
method. It marks the current window to be redisplayed. This makes sense since we have updated some of the state of our demo, we want to see the results but to do that, we need to redisplay the scene by telling GLUT that we want to invoke the DisplayGL()
method again.
The KeyboardGL Method
This method is invoked by the GLUT even loop whenever the user presses a key on the keyboard. The method will be passed an ASCII character code that represents the keyboard key that was pressed, as well as the x
and y
window relative position of the mouse in pixels.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
void KeyboardGL( unsigned char c, int x, int y ) { // Store the current scene so we can test if it has changed later. ESceneType currentScene = g_eCurrentScene; switch ( c ) { case '1': { glClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); // White background g_eCurrentScene = ST_Scene1; } break; case '2': { glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // Black background g_eCurrentScene = ST_Scene2; } break; case '3': { glClearColor( 0.27f, 0.27f, 0.27f, 1.0f ); // Dark-Gray background g_eCurrentScene = ST_Scene3; } break; case '4': { glClearColor( 0.73f, 0.73f, 0.73f, 1.0f ); // Light-Gray background g_eCurrentScene = ST_Scene4; } break; case 's': case 'S': { std::cout << "Shade Model: GL_SMOOTH" << std::endl; // Switch to smooth shading model glShadeModel( GL_SMOOTH ); } break; case 'f': case 'F': { std::cout << "Shade Model: GL_FLAT" << std::endl; // Switch to flat shading model glShadeModel( GL_FLAT ); } break; case 'r': case 'R': { std::cout << "Reset Parameters..." << std::endl; g_fRotate1 = g_fRotate2 = g_fRotate3 = 0.0f; } break; case '\033': // escape quits case '\015': // Enter quits case 'Q': // Q quits case 'q': // q (or escape) quits { // Cleanup up and quit Cleanup(0); } break; } if ( currentScene != g_eCurrentScene ) { std::cout << "Changed Render Scene: " << ( g_eCurrentScene + 1 ) << std::endl; } glutPostRedisplay(); } |
In this method, I arbitrarily choose the keys that I wanted to be handled for this demo.
Keyboard keys 1–4 will be used to change the current scene that will be rendered when DisplayGL()
is invoked. We also change the clear color of the color buffer when we change the scene. This will make it more obvious when we change between the different scenes.
The s key is used to enable smooth shading model and the f key is used to enable flat shading model.
Pressing the r key will reset our rotation parameters that are used to rotate our primitives.
Pressing the Esc, Enter, or q key will quite the demo and close the render window by calling the Cleanup()
method. In general, I like to use the Esc key to close demo programs.
And finally we call the glutPostRedisplay()
method to ensure our window gets marked for redisplay.
The MouseGL and MotionGL Methods
The MouseGL()
and MotionGL()
methods are used to handle mouse button clicks and mouse movement events that are sent from the GLUT event loop.
266 267 268 269 270 271 272 273 274 |
void MouseGL( int button, int state, int x, int y ) { } void MotionGL( int x, int y ) { } |
In this particular demo, we don’t do anything with the mouse, so these methods are left blank. I included them here because the idea is we can use this source to create additional demos in OpenGL in which case we might actually want to do something with the mouse. I should also not that there is additional mouse motion function that will receive events when the mouse is moved over the render window when no buttons are pressed. This method is called PassiveMotionGL()
and is registered with the glutPassiveMotionFunc()
method.
The ReshapeGL Method
The ReshapeGL method is invoked by the GLUT event loop whenever the render window is resized. This method will also be called whenever the render window is shown for the first time so we can use it to setup our viewport and projection parameters.
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
void ReshapeGL( int w, int h ) { std::cout << "ReshapGL( " << w << ", " << h << " );" << std::endl; if ( h == 0) // Prevent a divide-by-zero error { h = 1; // Making Height Equal One } g_iWindowWidth = w; g_iWindowHeight = h; glViewport( 0, 0, g_iWindowWidth, g_iWindowHeight ); // Setup the projection matrix glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective( 60.0, (GLdouble)g_iWindowWidth/(GLdouble)g_iWindowHeight, 0.1, 100.0 ); glutPostRedisplay(); } |
To prevent a divide-by-zero error, we first check to make sure the height is not zero.
The glViewport()
method is used to setup the viewport rectangle for the current OpenGL context. The first two parameters specify the bottom-left corner of the viewport rectangle in pixels. The second two parameters specify the width and height of the viewport in pixels. For this demo, we will only specify one view that fills the entire screen.
We will also setup the projection matrix in this method. To do that, we first set our matrix mode to GL_PROJECTION
and use the gluPerspective()
method to initialize the projection matrix to a perspective projection matrix. The parameters to the gluPerspective()
proejction are:
fovy
: The field of view angle measured in degrees, in the y-direction.aspect
: The aspect ratio of the viewport. This is almost always going to be set to (viewportWidth/viewportHeight
) which in this case is the same as our window width and window height respectively.zNear
: This is the distance from the viewer to the near clipping plane. Objects that are in front of this plane will be clipped by the rasterizer.zFar
: This is the distance from the viewer to the far clipping plane. Object that are farther away than this will be clipped from view by the rasterizer.
And the final thing we will do is notify GLUT that the current window can be redisplayed.
Rendering 2D Primitives
The next methods that we will define are helper functions that will draw some basic primitives using OpenGL immediate mode. We will combine calls to these methods in our scene rendering functions to create a complex scenes.
When we use immediate mode to draw primitives, we always begin our primitive list with a call to glBegin()
and our primitive list is always terminated with a call to glEnd()
. The parameter that is passed to glBegin()
determines the type of primitive we want to draw and it can be one of the following values:
GL_POINTS
treats each vertex as a point that will be rendered to the screen. Points will always be drawn the same size irrelevant of their distance to the camera determined by the value specified by theglPointSize(GLfloat size)
method. The standard size for points is1
.GL_LINES
treats each pair of vertices as separate line segments. You must specify at least 2 vertices for a line to be drawn.GL_LINE_STRIP
will draw an open connected series of lines where each vertex is connected by a line segment. The last vertex specified will not be automatically connected to the first vertex.GL_LINE_LOOP
will draw a closed connected series of lines where each vertex is connected by a line segment. The last vertex will be automatically connected to the first vertex in the list.GL_TRIANGLES
treats each triple set of vertices as a single triangle. Individual triangle primitives will not be connected.GL_TRIANGLE_STRIP
will draw a connected group of triangles. For each additional vertex after the 3rd, the triangle is closed by adding an edge from the \(n^{th}\) to the \((n-2)^{th}\) vertex.GL_TRIANGLE_FAN
is useful for drawing circles with any number of vertices where the first vertex is the central vertex that is used to connect all additional vertices in the list.GL_QUADS
is used to draw a set of separated quadrilaterals (4-vertex polygons).GL_QUAD_STRIP
is used to draw a set of connected quadrilaterals.GL_POLYGON
is used to draw a closed (convex) polygon from any number of vertices.
An example of using each primitive type is shown in the image below.
In our methods, we will only use a few of the primitive types to draw our shapes.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
void DrawTriangle( float2 p1, float2 p2, float2 p3 ) { glBegin( GL_TRIANGLES ); glVertex2f( p1.x, p1.y ); glVertex2f( p2.x, p2.y ); glVertex2f( p3.x, p3.y ); glEnd(); } void DrawRectangle( float width, float height ) { const float w = width / 2.0f; const float h = height / 2.0f; glBegin( GL_QUADS ); glVertex2f( -w, h ); // Top-Left glVertex2f( w, h ); // Top-Right glVertex2f( w, -h ); // Bottom-Right glVertex2f( -w, -h ); // Bottom-Left glEnd(); } void DrawCircle( float radius, int numSides /* = 8 */ ) { const float step = M_PI / numSides; glBegin( GL_TRIANGLE_FAN ); glVertex2f(0.0f, 0.0f); for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step ) { glVertex2f( radius * sinf(angle), radius * cosf(angle) ); } glVertex2f( 0.0f, radius ); // One more vertex to close the circle glEnd(); } |
Starting with the first method DrawTriangle()
, we use a the GL_TRIANGLES
primitive type to draw a single triangle from three vertices.
The next method show is DrawRectangle()
in which case the GL_QUADS
primitive type is best suited for this purpose.
The best primitive to draw a circle is GL_TRIANGLE_FAN
so we’ll use that in the DrawCircle()
method. Notice the first vertex at position (0.0, 0.0)
is the common vertex that will join all the other vertices. We also add another vertex on line 331 that has the same position as the second vertex. This extra vertex is needed to close the circle, otherwise our circle would have a half-eaten pizza shape!
Drawing 2D Polygons
The first render method I will show you is a simple method that simply draws three 2D shapes, a red triangle, a blue square (quad), and a yellow circle.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
void RenderScene1() { glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode glLoadIdentity(); // Load the identity matrix glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate our view matrix back and a bit to the left. glColor3f( 1.0f, 0.0f, 0.0f ); // Set Color to red DrawTriangle( float2(0.0f, 1.0f), float2(-1.0f, -1.0f), float2(1.0f, -1.0f ) ); glTranslatef( 3.0f, 0.0f, 0.0f ); // Shift view 3 units to the right glColor3f( 0.0f, 0.0f, 1.0f ); // Set Color to blue DrawRectangle( 2.0f, 2.0f ); glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen glColor3f( 1.0f, 1.0f, 0.0f ); // Set color to yellow DrawCircle( 1.0f, 16 ); } |
The first thing we do in this method is to switch to modelview matrix mode. OpenGL supports three matrix stacks.
GL_MODELVIEW
matrix stack is a combination of view and model (or world) matrix transformation. Manipulating the modelview matrix will determine how objects are placed in the world relative to the viewer. We can imagine that the viewer is always at the origin of the global coordinate system and objects are positioned in the world by manipulating this matrix relative to that origin.GL_PROJECTION
matrix stack is used to manipulate the lens of our camera. Changes made to the projection matrix will determine how the objects will appear on the screen. There are generally two types of projections used in games:- Perspective Projection: Using a perspective projection, objects that are farther away from the viewer will appear smaller while objects closer to the viewer will appear larger. This is called perspective.
- Orthographic Projection: Using an orthographic projection (also known as orthogonal projection or parallel projection) results in all objects, regardless of their distance from the viewer, appear to be the same size. This is useful to screen elements like a HUD or for displaying text. This type of projection is also useful for rendering schematic views such as a maps or building schematics.
GL_TEXTURE
matrix stack is applied to the texture coordinates of a model before any texture mapping is applied. Using the texture matrix stack, you can scale, rotate, or translate a texture on a model to achieve interesting effects.
Then we reset the modelview matrix to the identity matrix using the glLoadIdentity()
method. This method has the effect of resetting any translations, scales, and rotations back to the default state thus setting the world back the origin of the global coordinate space.
The next line causes the modelview matrix to be translated 6 units away from the viewer (along the z-axis), one unit up (along the y-axis), and 1.5 units to the left (along the x-axis) of the current viewer. All primitives rendered will now be rendered relative to this position.
ReshapeGL()
method, we setup our projection matrix to be a perspective matrix using the gluPerspective()
method. This means that the units don’t necessarily refer to screen pixels, but instead to some arbitrary world units. The scale of the world is completely meaningless and we could choose to treat each unit as a meter, a foot, a yard, a centimeter, or a millimeter or any valid measurement of distance. Whatever unit you choose to use, you should at least be consistent in your application with your choice of units and the artists should definitely be aware of this choice before they start modeling. Personally, I would recommend you treat one unit as a meter because this makes the most sense when you start working with physics packages that use meters and seconds and the basic units for distance and time.On line 341 we specify a color that is to be used for all additional vertices that we send to the OpenGL display list. In this case the color we want to use is red. OpenGL can be thought of as a state-management system. OpenGL stores many state variables that remain constant until you explicitly change the value of that state. Using the glColor3f
we can manipulate the value of the color state value. Every vertex we render after this call will get this color when rendered to the screen until we explicitly change the color state.
Then we draw the triangle using the DrawTriangle()
method described above.
Since we don’t want the next primitive to overlap the triangle we just drew, we will move the relative center three units to the right using the method glTranslatef()
. You should be aware that this translation occurs relative to the previous translation we performed, so this means that the next primitive is not drawn at (3.0, 0.0, 0.0)
but it is actually drawn at (1.5, 1.0, -6.0)
because that is the combination of our previous translation and this one.
Then we set the color to blue and draw a rectangle using the method described above.
Then we do another translation, and draw another primitive, this time a yellow circle.
The result of rendering this scene is shown below.
Not very exciting is it… Let’s see if we can do something about that.
Using Colors
The next type of scene we will render will be similar to the first, but this time we will add some different colors to each vertex.
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
void RenderScene2() { glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode glLoadIdentity(); // Load the identity matrix glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left // Draw a triangle with different colors on each vertex glBegin( GL_TRIANGLES ); glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex2f( 0.0f, 1.0f ); // Top-Center glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex2f( -1.0f, -1.0f ); // Bottom-Left glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex2f( 1.0f, -1.0f ); // Bottom-Right glEnd(); glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right // Draw a rectangle with different colors on each vertex glBegin( GL_QUADS ); glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex2f( -1.0f, 1.0f ); // Top-Left glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex2f( 1.0f, 1.0f ); // Top-Right glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex2f( 1.0f, -1.0f ); // Bottom-Right glColor3f( 1.0f, 1.0f, 1.0f ); // White glVertex2f( -1.0f, -1.0f ); // Bottom-Left glEnd(); glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen // Draw a circle with blended red/blue vertices. const float step = M_PI / 16; const float radius = 1.0f; glBegin( GL_TRIANGLE_FAN ); glColor3f( 1.0f, 1.0f, 1.0f ); glVertex2f(0.0f, 0.0f); for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step ) { float fSin = sinf(angle); float fCos = cosf(angle); glColor3f( ( fCos + 1.0f ) * 0.5f, ( fSin + 1.0f ) * 0.5f , 0.0f); glVertex2f( radius * fSin, radius * fCos ); } glColor3f( 1.0f, 0.5f, 0.0f ); glVertex2f( 0.0f, radius ); // One more vertex to close the circle glEnd(); } |
This function is quite a bit longer than the previous, that’s because we are unrolling the work we did in the helper functions. I had to do this because the helper functions can only draw primitives with a single color, but I wanted to set the color of each vertex for this scene so I had to write-out all of the operations by hand.
The only difference between the two scenes is in this scene I change the color state before I send a vertex to the display list. If GL_SMOOTH
is the current shader model, then the colors will be interpolated between the different vertices. If GL_FLAT
is the current shader model, then each primitive (triangle or quad in this case) will be draw in a single color. The color of the last vertex in the primitive is used to fill the entire primitive. You can see this for yourself by pressing the f key to switch to flat shading model and the s key will switch to smooth shading model.
The following image shows how this scene should look.
Adding some Spin
Well, even looking at colored primitives is not that exciting. How about adding some animation? In the next scene we will draw the same three primitives, but this time we will add a bit of spin.
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
void RenderScene3() { glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode glLoadIdentity(); // Load the identity matrix glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left glPushMatrix(); // Push the current transformation onto the matrix stack glRotatef( g_fRotate1, 0.0f, 0.0f, 1.0f ); // Draw a triangle with different colors on each vertex glBegin( GL_TRIANGLES ); glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex2f( 0.0f, 1.0f ); // Top-Center glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex2f( -1.0f, -1.0f ); // Bottom-Left glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex2f( 1.0f, -1.0f ); // Bottom-Right glEnd(); glPopMatrix(); glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right glPushMatrix(); glRotatef( g_fRotate2, 0.0f, 0.0f, 1.0f ); // Draw a rectangle with different colors on each vertex glBegin( GL_QUADS ); glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex2f( -1.0f, 1.0f ); // Top-Left glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex2f( 1.0f, 1.0f ); // Top-Right glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex2f( 1.0f, -1.0f ); // Bottom-Right glColor3f( 1.0f, 1.0f, 1.0f ); // White glVertex2f( -1.0f, -1.0f ); // Bottom-Left glEnd(); glPopMatrix(); glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen glPushMatrix(); glRotatef( g_fRotate3, 0.0f, 0.0f, 1.0f ); // Draw a circle with blended red/blue vertecies const float step = M_PI / 16; const float radius = 1.0f; glBegin( GL_TRIANGLE_FAN ); glColor3f( 1.0f, 1.0f, 1.0f ); glVertex2f(0.0f, 0.0f); for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step ) { float fSin = sinf(angle); float fCos = cosf(angle); glColor3f( ( fCos + 1.0f ) * 0.5f, ( fSin + 1.0f ) * 0.5f , 0.0f); glVertex2f( radius * fSin, radius * fCos ); } glColor3f( 1.0f, 0.5f, 0.0f ); glVertex2f( 0.0f, radius ); // One more vertex to close the circle glEnd(); glPopMatrix(); } |
The only difference between this scene and the 2nd is the addition of the glPushMatrix()
followed by the glRotatef(angle, x, y, z)
shown in the highlighted lines above.
The glPushMatrix()
function pushes the state of the current matrix onto the matrix stack. Since we don’t want to bother calculated the inverse for the rotation matrix (to undo the rotation), we simply “push-on” the current matrix state and after we finished rendering our primitive, we simply “pop-off” the rotated matrix resulting in the unrotated matrix. Be careful to pair each occurrence of a glPushMatrix()
with a matching glPopMatrix()
.
The glRotatef()
will add a rotation to the current matrix. This method takes three parameters:
angle
: the angle of rotation in degrees.x, y, z
: thex
,y
, andz
coordinates of a vector about which the rotation will occur.
In this example, we simply rotate around the z-axis (0.0, 0.0, 1.0)
since we are looking directly down the z-axis, this will be the most obvious rotation to apply.
The image below shows the result of rotating the primitives.
Enter the 3rd Dimension
Up until this point we have only been using 2D vertices but this isn’t really taking advantage of the amazing power of the rendering hardware. In the next scene we will add 3D primitives. Instead of a triangle, we’ll render a pyramid. Instead of a a rectangle, we’ll render a cube. And instead of a circle, we’ll render a sphere.
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
void RenderScene4() { glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glEnable( GL_DEPTH_TEST ); glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left glPushMatrix(); // Push the current modelview matrix on the matrix stack glRotatef(g_fRotate1, 1.0f, 1.0f, 1.0f ); // Rotate on all 3 axis glBegin( GL_TRIANGLES ); // Draw a pyramid glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of front face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( -1.0f, -1.0f, 1.0f ); // Left of front face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( 1.0f, -1.0f, 1.0f ); // Right of front face glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of right face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( 1.0f, -1.0f, 1.0f ); // Left of right face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( 1.0f, -1.0f, -1.0f ); // Right of right face glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of back face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( 1.0f, -1.0f, -1.0f ); // Left of back face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( -1.0f, -1.0f, -1.0f ); // Right of back face glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of left face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( -1.0f, -1.0f, -1.0f ); // Left of left face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( -1.0f, -1.0f, 1.0f ); // Right of left face glEnd(); // Render a quad for the bottom of our pyramid glBegin( GL_QUADS ); glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( -1.0f, -1.0f, 1.0f ); // Left/right of front/left face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( 1.0f, -1.0f, 1.0f ); // Right/left of front/right face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( 1.0f, -1.0f, -1.0f ); // Right/left of right/back face glColor3f( 0.0f, 0.0f, 1.0f ); // Blue glVertex3f( -1.0f, -1.0f, -1.0f ); // Left/right of right/back face glEnd(); glPopMatrix(); glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right glPushMatrix(); // Push the current modelview matrix on the matrix stack glRotatef( g_fRotate2, 1.0f, 1.0f, 1.0f ); // Rotate the primitive on all 3 axis glBegin( GL_QUADS ); // Top face glColor3f( 0.0f, 1.0f, 0.0f ); // Green glVertex3f( 1.0f, 1.0f, -1.0f ); // Top-right of top face glVertex3f( -1.0f, 1.0f, -1.0f ); // Top-left of top face glVertex3f( -1.0f, 1.0f, 1.0f ); // Bottom-left of top face glVertex3f( 1.0f, 1.0f, 1.0f ); // Bottom-right of top face // Bottom face glColor3f( 1.0f, 0.5f, 0.0f ); // Orange glVertex3f( 1.0f, -1.0f, -1.0f ); // Top-right of bottom face glVertex3f( -1.0f, -1.0f, -1.0f ); // Top-left of bottom face glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom-left of bottom face glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom-right of bottom face // Front face glColor3f( 1.0f, 0.0f, 0.0f ); // Red glVertex3f( 1.0f, 1.0f, 1.0f ); // Top-Right of front face glVertex3f( -1.0f, 1.0f, 1.0f ); // Top-left of front face glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom-left of front face glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom-right of front face // Back face glColor3f( 1.0f, 1.0f, 0.0f ); // Yellow glVertex3f( 1.0f, -1.0f, -1.0f ); // Bottom-Left of back face glVertex3f( -1.0f, -1.0f, -1.0f ); // Bottom-Right of back face glVertex3f( -1.0f, 1.0f, -1.0f ); // Top-Right of back face glVertex3f( 1.0f, 1.0f, -1.0f ); // Top-Left of back face // Left face glColor3f( 0.0f, 0.0f, 1.0f); // Blue glVertex3f( -1.0f, 1.0f, 1.0f); // Top-Right of left face glVertex3f( -1.0f, 1.0f, -1.0f); // Top-Left of left face glVertex3f( -1.0f, -1.0f, -1.0f); // Bottom-Left of left face glVertex3f( -1.0f, -1.0f, 1.0f); // Bottom-Right of left face // Right face glColor3f( 1.0f, 0.0f, 1.0f); // Violet glVertex3f( 1.0f, 1.0f, 1.0f); // Top-Right of left face glVertex3f( 1.0f, 1.0f, -1.0f); // Top-Left of left face glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom-Left of left face glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom-Right of left face glEnd(); glPopMatrix(); glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen glPushMatrix(); glRotatef( g_fRotate3, 1.0f, 1.0f, 1.0f ); glColor3f( 1.0f, 1.0f, 0.0f ); // Yellow glutSolidSphere( 1.0f, 16, 16 ); // Use GLUT to draw a solid sphere glScalef( 1.01f, 1.01f, 1.01f ); glColor3f( 1.0f, 0.0f, 0.0f ); // Red glutWireSphere( 1.0f, 16, 16 ); // Use GLUT to draw a wireframe sphere glPopMatrix(); } |
The first thing you’ll probably notice is this is the longest render method thus far. Drawing 3D primitives requires plenty of vertices (less if we use vertex buffers because then we can reuse vertices that appear at the same location).
There really isn’t anything new being show here except for the fact that we are plotting vertices with glVertex3f()
instead of glVertex2f()
. You may also notice that I’m using a built-in method provided by GLUT to draw the sphere (on line 579). I’m just too lazy to plot all the points for the sphere. However using this function means I can only render the sphere with a single solid color. Because it is difficult to see the rotation of a solid sphere, I added the wireframe sphere that is drawn just above the solid sphere using the glutWireSphere()
method on line 581.
If everything is correct, you should see something similar to what is shown below.
Keep in mind this is only a very brief introduction to OpenGL. There are still many more things to learn such as, lighting, textures, meshes, animation, and the programmable pipeline.
Video Lecture
I’ve created a video lecture for this article. This video is best viewed at 1080p but it should also be perfectly viewable at 720p. It may be difficult to read the text at lower resolutions.
In this video I am using Visual Studio 2012 to setup an OpenGL template that can be used to create other OpenGL demos later.
There is a known issue with Camtasia Studio that prevents pop-up windows from being captured. I didn’t realize this was happening until after I recorded the entire tutorial. There are a few times when I right-click on the project in Visual Studio to bring up the pop-up menu and select “Properties” to display the project’s properties. This however wasn’t captured by Camtasia Studio!
References
OpenGL Architecture Review Board, Dave Shreiner. OpenGL(R) Reference Manual: The Official Reference Document to OpenGL, Version 1.4 (4th Edition)
|
OpenGL Architecture Review Board, Dave Shreiner, Mason Woo, Jackie Neider, Tom Davis. OpenGL(R) Programming Guide: The Official Guide to Learning OpenGL(R), Version 2 (5th Edition)
|
Luke Benstead, Dave Astle, Kevin Hawkins (2009). Beginning OpenGL: Game Programming Second Edition. Course Technology.
|
Download the Source
You can download the source code (including the Visual Studio 2008 project files):
[OpenGL Template.zip]
Alright, so that thing with the matrices I talked about in class. The thing is, the glPushMatrix, glPopMatrix, glTranslate and glRotate functions are considered deprecated in OpenGL 3.0 and above because they were never hardware accelerated to begin with: http://www.opengl.org/wiki/FAQ#glTranslate.2C_glRotate.2C_glScale
They have always been really slow. If you want speed (and 3.0 compliance) you will have to make your own matrix stack and upload these matrices to your shaders.
Fortunately, the GLM library is here to save the day (and your ass): http://glm.g-truc.net/
Thanks Quinten for the information. I havn’t used GLM before but it looks like a pretty good math library if you are using the OpenGL API. I think I will incorporate it into future lectures.
The link appears to be broken, google docs says the document can’t be found.
I double-checked the link.. It should be working. Anyone else having trouble with the google doc link?
I tried to send an e-mail to your account but apparently I can’t send e-mail to you. Perhaps you have to verify your gmail account before you can use it?
The links should be fixed now.
You are great people !!!
Thank you for your explanation from ground up not like OpenGL Super Bible
where is included they own framework and in order to draw simple triangles,
beginner have to understand complicated framework first.
In another tutorials claimed for beginners to draw triangles you have to include
100 classes(because one class call another and so on).
Great job thank you.
Have nice time.