Setting up the OpenGL v3.3 core profile on Visual Studio 2010 using the GLEW and freeglut libraries
We will start with a very basic example in which we will set up the modern OpenGL v3.3 core profile. This example will simply create a blank window and clear the window with red color.
OpenGL or any other graphics API for that matter requires a window to display graphics in. This is carried out through platform specific codes. Previously, the GLUT library was invented to provide windowing functionality in a platform independent manner. However, this library was not maintained with each new OpenGL release. Fortunately, another independent project, freeglut, followed in the GLUT footsteps by providing similar (and in some cases better) windowing support in a platform independent way. In addition, it also helps with the creation of the OpenGL core/compatibility profile contexts. The latest version of freeglut may be downloaded from http://freeglut.sourceforge.net. The version used in the source code accompanying this book is v2.8.0. After downloading the freeglut library, you will have to compile it to generate the libs/dlls.
The extension mechanism provided by OpenGL still exists. To aid with getting the appropriate function pointers, the GLEW library is used. The latest version can be downloaded from http://glew.sourceforge.net. The version of GLEW used in the source code accompanying this book is v1.9.0. If the source release is downloaded, you will have to build GLEW first to generate the libs and dlls on your platform. You may also download the pre-built binaries.
Prior to OpenGL v3.0, the OpenGL API provided support for matrices by providing specific matrix stacks such as the modelview, projection, and texture matrix stacks. In addition, transformation functions such as translate, rotate, and scale, as well as projection functions were also provided. Moreover, immediate mode rendering was supported, allowing application programmers to directly push the vertex information to the hardware.
In OpenGL v3.0 and above, all of these functionalities are removed from the core profile, whereas for backward compatibility they are retained in the compatibility profile. If we use the core profile (which is the recommended approach), it is our responsibility to implement all of these functionalities including all matrix handling and transformations. Fortunately, a library called glm
exists that provides math related classes such as vectors and matrices. It also provides additional convenience functions and classes. For all of the demos in this book, we will use the glm
library. Since this is a headers only library, there are no linker libraries for glm
. The latest version of glm
can be downloaded from http://glm.g-truc.net. The version used for the source code in this book is v0.9.4.0.
There are several image formats available. It is not a trivial task to write an image loader for such a large number of image formats. Fortunately, there are several image loading libraries that make image loading a trivial task. In addition, they provide support for both loading as well as saving of images into various formats. One such library is the
SOIL
image loading library. The latest version of SOIL
can be downloaded from http://www.lonesock.net/soil.html.
Once we have downloaded the SOIL
library, we extract the file to a location on the hard disk. Next, we set up the include and library paths in the Visual Studio environment. The include path on my development machine is D:\Libraries\soil\Simple OpenGL Image Library\src
whereas, the library path is set to D:\Libraries\soil\Simple OpenGL Image Library\lib\VC10_Debug
. Of course, the path for your system will be different than mine but these are the folders that the directories should point to.
These steps will help us to set up our development environment. For all of the recipes in this book, Visual Studio 2010 Professional version is used. Readers may also use the free express edition or any other version of Visual Studio (for example, Ultimate/Enterprise). Since there are a myriad of development environments, to make it easier for users on other platforms, we have provided premake script files as well.
The code for this recipe is in the Chapter1/GettingStarted
directory.
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
How to do it...
Let us setup the development environment using the following steps:
- After downloading the required libraries, we set up the Visual Studio 2010 environment settings.
- We first create a new Win32 Console Application project as shown in the preceding screenshot. We set up an empty Win32 project as shown in the following screenshot:
- Next, we set up the include and library paths for the project by going into the Project menu and selecting project Properties. This opens a new dialog box. In the left pane, click on the Configuration Properties option and then on VC++ Directories.
- In the right pane, in the Include Directories field, add the GLEW and freeglut subfolder paths.
- Similarly, in the Library Directories, add the path to the lib subfolder of GLEW and freeglut libraries as shown in the following screenshot:
- Next, we add a new
.cpp
file to the project and name itmain.cpp
. This is the main source file of our project. You may also browse throughChapter1/ GettingStarted/GettingStarted/main.cpp
which does all this setup already. - Let us skim through the
Chapter1/ GettingStarted/GettingStarted/main.cpp
file piece by piece.#include <GL/glew.h> #include <GL/freeglut.h> #include <iostream>
These lines are the include files that we will add to all of our projects. The first is the GLEW header, the second is the freeglut header, and the final include is the standard input/output header.
- In Visual Studio, we can add the required linker libraries in two ways. The first way is through the Visual Studio environment (by going to the Properties menu item in the Project menu). This opens the project's property pages. In the configuration properties tree, we collapse the Linker subtree and click on the Input item. The first field in the right pane is
Additional Dependencies
. We can add the linker library in this field as shown in the following screenshot: - The second way is to add the
glew32.lib
file to the linker settings programmatically. This can be achieved by adding the followingpragma
:#pragma comment(lib, "glew32.lib")
- The next line is the using directive to enable access to the functions in the std namespace. This is not mandatory but we include this here so that we do not have to prefix
std::
to any standard library function from the iostream header file.using namespace std;
- The next lines define the width and height constants which will be the screen resolution for the window. After these declarations, there are five function definitions . The
OnInit()
function is used for initializing any OpenGL state or object,OnShutdown()
is used to delete an OpenGL object,OnResize()
is used to handle the resize event,OnRender()
helps to handle the paint event, andmain()
is the entry point of the application. We start with the definition of themain()
function.const int WIDTH = 1280; const int HEIGHT = 960; int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitContextVersion (3, 3); glutInitContextFlags (GLUT_CORE_PROFILE | GLUT_DEBUG); glutInitContextProfile(GLUT_FORWARD_COMPATIBLE); glutInitWindowSize(WIDTH, HEIGHT);
- The first line
glutInit
initializes the GLUT environment. We pass the command line arguments to this function from our entry point. Next, we set up the display mode for our application. In this case, we request the GLUT framework to provide support for a depth buffer, double buffering (that is a front and a back buffer for smooth, flicker-free rendering), and the format of the frame buffer to be RGBA (that is with red, green, blue, and alpha channels). Next, we set the required OpenGL context version we desire by using theglutInitContextVersion
. The first parameter is the major version of OpenGL and the second parameter is the minor version of OpenGL. For example, if we want to create an OpenGL v4.3 context, we will callglutInitContextVersion (4, 3)
. Next, the context flags are specified:glutInitContextFlags (GLUT_CORE_PROFILE | GLUT_DEBUG); glutInitContextProfile(GLUT_FORWARD_COMPATIBLE);
Tip
In OpenGL v4.3, we can register a callback when any OpenGL related error occurs. Passing
GLUT_DEBUG
to theglutInitContextFlags
functions creates the OpenGL context in debug mode which is needed for the debug message callback. - For any version of OpenGL including OpenGL v3.3 and above, there are two profiles available: the core profile (which is a pure shader based profile without support for OpenGL fixed functionality) and the compatibility profile (which supports the OpenGL fixed functionality). All of the matrix stack functionality
glMatrixMode(*)
,glTranslate*
,glRotate*
,glScale*
, and so on, and immediate mode calls such asglVertex*
,glTexCoord*
, andglNormal*
of legacy OpenGL, are retained in the compatibility profile. However, they are removed from the core profile. In our case, we will request a forward compatible core profile which means that we will not have any fixed function OpenGL functionality available. - Next, we set the screen size and create the window:
glutInitWindowSize(WIDTH, HEIGHT); glutCreateWindow("Getting started with OpenGL 3.3");
- Next, we initialize the GLEW library. It is important to initialize the GLEW library after the OpenGL context has been created. If the function returns
GLEW_OK
the function succeeds, otherwise the GLEW initialization fails.glewExperimental = GL_TRUE; GLenum err = glewInit(); if (GLEW_OK != err){ cerr<<"Error: "<<glewGetErrorString(err)<<endl; } else { if (GLEW_VERSION_3_3) { cout<<"Driver supports OpenGL 3.3\nDetails:"<<endl; } } cout<<"\tUsing glew "<<glewGetString(GLEW_VERSION)<<endl; cout<<"\tVendor: "<<glGetString (GL_VENDOR)<<endl; cout<<"\tRenderer: "<<glGetString (GL_RENDERER)<<endl; cout<<"\tVersion: "<<glGetString (GL_VERSION)<<endl; cout<<"\tGLSL: "<<glGetString(GL_SHADING_LANGUAGE_VERSION)<<endl;
The
glewExperimental
global switch allows the GLEW library to report an extension if it is supported by the hardware but is unsupported by the experimental or pre-release drivers. After the function is initialized, the GLEW diagnostic information such as the GLEW version, the graphics vendor, the OpenGL renderer, and the shader language version are printed to the standard output. - Finally, we call our initialization function
OnInit()
and then attach our uninitialization functionOnShutdown()
as theglutCloseFunc
method—the close callback function which will be called when the window is about to close. Next, we attach our display and reshape function to their corresponding callbacks. The main function is terminated with a call to theglutMainLoop()
function which starts the application's main loop.OnInit(); glutCloseFunc(OnShutdown); glutDisplayFunc(OnRender); glutReshapeFunc(OnResize); glutMainLoop(); return 0; }
There's more…
The remaining functions are defined as follows:
void OnInit() { glClearColor(1,0,0,0); cout<<"Initialization successfull"<<endl; } void OnShutdown() { cout<<"Shutdown successfull"<<endl; } void OnResize(int nw, int nh) { } void OnRender() { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glutSwapBuffers(); }
For this simple example, we set the clear color to red (R:1, G:0, B:0, A:0). The first three are the red, green, and blue channels and the last is the alpha channel which is used in alpha blending. The only other function defined in this simple example is the OnRender()
function, which is our display callback function that is called on the paint event. This function first clears the color and depth buffers to the clear color and clear depth values respectively.
Tip
Similar to the color buffer, there is another buffer called the depth buffer. Its clear value can be set using the glClearDepth
function. It is used for hardware based hidden surface removal. It simply stores the depth of the nearest fragment encountered so far. The incoming fragment's depth value overwrites the depth buffer value based on the depth clear function specified for the depth test using the glDepthFunc
function. By default the depth value gets overwritten if the current fragment's depth is lower than the existing depth in the depth buffer.
The glutSwapBuffers
function is then called to set the current back buffer as the current front buffer that is shown on screen. This call is required in a double buffered OpenGL application. Running the code gives us the output shown in the following screenshot.