Introduction
Some question posted by users on Code Project forums made me realize that most of the tutorials on OpenGL on the internet are sadly out of date. Many reference the old NeHe tutorials which have not been updated since Jeff Molofee turned over maintenance of his site to GameDev.
Visual Studio 2013 and 2015 have been released since then as has Windows 7, 8 and 10. So it seemed like it was time to at least try and create some basic tutorials in line with the changes.
Background
Our goal will be simple - produce the standard rotating bitmap cube in an OpenGL window.
All Windows versions since Windows 98 carry OpenGL natively there is nothing you need to add or do. Windows 7, 8 and 10 all run OpenGL version 3.3.0 out of the box but some video cards manufacturers as part of their driver installs may boost that to as high as version 4.5. You can however assume that you have OpenGL version 3.3 at the very minimum on any current Windows machine.
The first problem encountered by people trying to use the NeHe tutorials is that the GLAUX library has been deprecated and no longer exists either in Visual Studio or as a DLL for Windows. The reasoning for that is the functions are easily replaced by using standard Win32 API calls and in this article series, we will use that process. I see comments all the time about going and downloading the header file and compiled DLL for GLAUX but will we avoid that as there is no future in it.
I am going to try to use the Keep It Simple Stupid (KISS) approach and part of that approach is the programming code has no Objects or Frameworks. It is not because I don't believe such things have merits but it is to try to keep the code as simple as possible and to the widest audience.
The code uses the standard <TChar.h>
interface so it compiles in Ansi, Unicode and Wide character modes. I debated about not doing it in keeping with the KISS principle but felt to not do so would restrict the compilation modes and options particularly for non English speaking countries. As all that is involved is the exchange of the "char
" type for "TCHAR
" and a few _T
statements around text strings I felt it detracts little from understanding.
Using the Code
First let's look at the pseudo code for our OpenGL window system we will use that like this:
1.) Initialize OpenGL (Called Once)
2.) Scale the OpenGL viewPort (Initial Call)
repeat
3.) Draw the scene
4.) Transfer the scene to screen
until window closes
** Note Item 2) the scale process also gets called if the window changes size
The reason for this approach is because it will make the OpenGL sections flexible including migration into frameworks like MFC, WPF, etc.
The other concept I have used is putting all the OpenGL data into a simple structure which is attached as a property to the Window itself. In this first tutorial, people may wonder why bother to do that as it would be easy to just use normal global variables. The reasons will become obvious in Lesson 2 when we have multiple OpenGL windows and then on following lessons with more advanced multiple viewports.
This is our OpenGLData for the first lesson. The Render Context will always be there in future lessons but all the other data will vary depending on what is needed. In our first lesson, we will load a bitmap as a texture (gltexture
in the struct
), we will put that texture on the sides of a cube and rotate it (xrot
, yrot
being used for the rotation).
typedef struct OpenGLData {
HGLRC Rc; GLuint glTexture; GLfloat xrot; GLfloat yrot; } GLDATABASE;code blocks look like this
That basic data structure is attached to a window handle via a static
defined label string
using the SetProp
API call and retrieved whenever needed by using the GetProp
call.
The label
property we use in this tutorial is:
static const TCHAR* DATABASE_PROPERTY = _T("OurDataStructure");
This is the code for creating and attaching the data structure to a window handle we use:
GLDATABASE* db = (GLDATABASE*) malloc(sizeof(GLDATABASE)); db->Rc = InitGL(Wnd); db->glTexture = 0; db->xrot = 0.0f; db->yrot = 0.0f; SetProp(Wnd, DATABASE_PROPERTY, (HANDLE) db);
Anytime, we want access to the data we make a simple retrieval call like this:
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);
That all said, what we have in this lesson is then a standard window application skeleton. As the application Window is created, the WM_CREATE
will be used to initialize the OpenGL system. The returned Render
context will be held in our data structure. The ReSizeGLScene
is called directly after it to set the initial size of the OpenGL system to the window size created.
With the normal window then running, that just leaves us to deal with the WM_PAINT
message for the window and the code for that looks like this:
case WM_PAINT: { PAINTSTRUCT Ps;
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); BeginPaint(Wnd, &Ps); DrawGLScene(db, Ps.hdc); SwapBuffers(Ps.hdc); EndPaint(Wnd, &Ps); return 0;
}
It starts with the normal BeginPaint
call but then calls out to OpenGL to draw its scene onto its render context. On the return back, we swap the render context onto the PaintStructure
device context which draws the OpenGL scene onto our window. We then cleanup and exit.
The trick to this is the render process is not continually running it only occurs when a WM_PAINT
message is provided so if you wish to animate things, you need to provide WM_PAINT
messages after changes. When we activate the timer, it changes the rotation and then Invalidates the window which does exactly that creating a WM_PAINT
message to make the window redraw.
This process is great for things like constructing an 3D Object editor and the like but not very good in fast moving games where we need fast continual render processing of the scene. In later lessons, we will deal with that process and use threads.
So now, run the program and using the file menu, select to load a bitmap and on doing so, you will see something like this:
Now start a timer and your cube should start to rotate and you that is it for this lesson. So there we have it, our first OpenGL tutorial done with OpenGL running in a standard application window.
In our next lesson, we are going to push things a bit further with an MDI application.
History
- Version 1.0: First release of code