Introduction

Author: Movania Muhammad Mobeen

Hello readers,

In this article, we will learn how to port all the tutorials source code to the new OpenGL 3.3. specs. We will use the glut framework using the freeglut library. OpenGL 3.0 and above introduced two profiles for rendering. The core profile (which basically strips all legacy stuff from OpenGL) and the compatability profile (which keeps old deprecated functionality). To gain access to the new functions, we would need glew or any other extension helper library. Ok let's have a look at tutorial 2. In this tutorial, we see a rotating cube with per vertex colors assigned. There are two main things it. The rendering and handling of geometry (using the OpenGL immediate mode) and rotations (using the OpenGL matrix stack using glRotate function). This instruction is deprecated in the core profile, it will be removed in the future OpenGL versions while it exists in the compatability profile for legacy code. In the modern OpenGL 3.0, all geometry is handled using the buffer objects specifically vertex buffer objects (VBO) and their states are contained in the vertex array objects (VAOs). For transformations, we have two options, either we code the matrices and vector math utils ourselves or we use open source alternates like glm. We opt for the second option of using the existing libraries like glm since they are rigorously tested and well maintained. The glm library can be downloaded from here: http://glm.g-truc.net/download.html

Compilation instructions on windows with visualstudio 2008

For all of the tutorials ported to OpenGL3.3, you would need three external libraries glew, freeglut and glm.

Downloading libraries: 1) glew (http://glew.sourceforge.net/) Get the latest binaries no need for source. Set the visualstudio paths so that the headers can be found and then copy the glew32.dll from the bin folder to your ur source directory (where your .vcproj file is placed).

2) freeglut (http://freeglut.sourceforge.net/) Get the latest stable release as of today the current version (2.6.0) can be downloaded from http://prdownloads.sourceforge.net/free ... z?download Go in the visualstudio2008 folder and open the freeglut.sln project and build all. Then copy the freeglut.dll from the Debug folder to ur source directory (where your .vcproj file is placed).

3) glm (http://glm.g-truc.net/download.html) this is just a header. Download the zip file and extract it.

Setting up visual studio 2008: Once extracted, you may then add the path to the visual studio project directories. Go to Tools -> Options -> Projects & Solutions -> VC++ Directories. On the right hand pane you would see a Platform drop down list and Show directories for drop down list. Press the show directories for drop down list and select include files. Assuming that your have stored the libraries in c:\Libraries folder then add the following paths to the top of the list.

C:\Libraries\glm-0.9.0.6
C:\Libraries\glew-1.5.7\include
C:\Libraries\freeglut-2.6.0\include

You then click the show directories for drop down list again and select the library files. Then add the following lines on top.

C:\Libraries\glew-1.5.7\lib
C:\Libraries\freeglut-2.6.0\VisualStudio2008\Debug

Press ok and you are done. Note: If you add the freeglut headers, they automatically add the linker setting to import the freeglut library. For glew however, you need to set it your self. GO to Project menu -> Poperties -> Configuration properties -> Linker -> Input -> Additional Dependencies and add glew32.lib to the text field. Alternatively, you can also use a pragma like this at the top of your code after the include files to add the lib programmatically in visual studio.

#pragma comment(lib,"glew32.lib")

For visual studio 2010, the project settings are no more stored in the tools menu. They are now setup per project in the project property pages. Go to project menu -> project properties -> configuration properties. This contains VC++ directories menu item which contains the include and lib file directories.

The X64 version of glew might give you linker errors in this case, we recommend you download the glew src and build it on your system. This should solve the linker issues.

Converting immediate mode geometry handling to OpenGL 3.3

The tutorial 2 uses a custom structure to hold vertex and index list of the cube as shown below:

// And, finally our first object!
obj_type cube =
{
    {
        -10, -10, 10,   // vertex v0
        10,  -10, 10,   // vertex v1
        10,  -10, -10,  // vertex v2
        -10, -10, -10,  // vertex v3
        -10, 10,  10,   // vertex v4
        10,  10,  10,   // vertex v5
        10,  10,  -10,  // vertex v6
        -10, 10,  -10   // vertex v7
    },
    {
        0, 1, 4,  // polygon v0,v1,v4
        1, 5, 4,  // polygon v1,v5,v4
        1, 2, 5,  // polygon v1,v2,v5
        2, 6, 5,  // polygon v2,v6,v5
        2, 3, 6,  // polygon v2,v3,v6
        3, 7, 6,  // polygon v3,v7,v6
        3, 0, 7,  // polygon v3,v0,v7
        0, 4, 7,  // polygon v0,v4,v7
        4, 5, 7,  // polygon v4,v5,v7
        5, 6, 7,  // polygon v5,v6,v7
        3, 2, 0,  // polygon v3,v2,v0
        2, 1, 0,  // polygon v2,v1,v0
    }
};

Then it issues the following immediate mode calls to render the geometry:

 glBegin(GL_TRIANGLES); // GlBegin and glEnd delimit the vertices that define a primitive (in our case triangles)
    for (l_index=0;l_index<12;l_index++)
    {
        glColor3f(1.0,0.0,0.0); // Color for the vertex
        glVertex3f( cube.vertex[ cube.polygon[l_index].a ].x,    
                    cube.vertex[ cube.polygon[l_index].a ].y,    
                    cube.vertex[ cube.polygon[l_index].a ].z);//Vertex definition
        glColor3f(0.0,1.0,0.0);
        glVertex3f( cube.vertex[ cube.polygon[l_index].b ].x,    
                    cube.vertex[ cube.polygon[l_index].b ].y,    
                    cube.vertex[ cube.polygon[l_index].b ].z);
        glColor3f(0.0,0.0,1.0);
        glVertex3f( cube.vertex[ cube.polygon[l_index].c ].x,    
                    cube.vertex[ cube.polygon[l_index].c ].y,    
                    cube.vertex[ cube.polygon[l_index].c ].z);
    }
glEnd();

In OpenGL3.3, we have to use buffer objects. Like typical OpenGL object, there is a glGenBuffers function that allows us to create a new buffer object. The buffer object's state is stored in a vertex array object (VAO). So we must also create a VAO object using glGenVertexArrays as follows:

glGenVertexArrays(1, &vaoID);
glGenBuffers (1, &vboVerticesID);
glGenBuffers (1, &vboColorsID);
glGenBuffers (1, &vboIndicesID);

These calls create 1 vertex array object and three buffer objects for storing the cube geometry, colors and indices. Once, the VAO is generated we must bind it using:

glBindVertexArray(vaoID);

Now any buffer object we bind, its state is governed by the bound VAO. Next, we bind our vertices VBO like this:

glBindBuffer (GL_ARRAY_BUFFER, vboVerticesID);

The first parameter is the binding point which for our case is the array buffer binding point and the second parameter is the vbo to bind to this binding point. Next we put data into the array buffer using:

glBufferData (GL_ARRAY_BUFFER, sizeof(cube.vertex), &cube.vertex[0], GL_STATIC_DRAW);

The first parameter is the same as the previous call. The second parameter is the size (in bytes) of the data. The third parameter is the pointer to the data and the fourth parameter is the usage flag which tells the OpenGL driver how you intend to use the buffer object. For our case, we would not modify the buffer since we will just read positions from it so we give it GL_STATIC_DRAW. Next, we first enable the vertex attribute and set the vertex attribute pointer. We first give it the location of our attribute in the shader. Next, we give it the total number of items, their type, their stride and the offset pointer in the array. This allows us to link our shader attribute variable to the client side array (in our case the cube.vertex array). Note that the handling of attribute location is through the GLSLShader class's [] operator. It takes the name of the attribute and in return gives us its location. We explain the GLSLShader class in a later tutorial in detail.

shader.EnableVertexAttribute("vVertex");
glVertexAttribPointer (shader["vVertex"], 3, GL_FLOAT, GL_FALSE,stride,0);

Similarly, we bind our color vertex buffer to the array buffer target however now the location is set to a different attribute thus the VAO knows that this attribute is for the second buffer binding:

glBindBuffer (GL_ARRAY_BUFFER, vboColorsID);
glBufferData (GL_ARRAY_BUFFER, sizeof(colors), &colors[0], GL_STATIC_DRAW);
shader.EnableVertexAttribute("vColor");
glVertexAttribPointer (shader["vColor"], 3, GL_FLOAT, GL_FALSE,stride,0);

Finally, we attach our index buffer to the element array buffer binding. The parameters are same as the previous function as described earlier:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndicesID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube.polygon), &cube.polygon[0], GL_STATIC_DRAW);

In the end, we unbind our vertex array:

glBindVertexArray(0);

At this point, we have successfully converted our geometry handling at the client side. Now we look at the shaders used in our case.

Shader handling of per vertex and per fragment attributes

Our vertex shader is fairly straight forward. It multiplies the current vertex position (vVertex) with the given modelview projection matrix (MVP) to get the clip space position of the vertex. The current vertex's color (vColor) is output to an attribute (vSmoothColor) so that it is smoothly interpolated (smooth qualifier) by the rasterizer and finally given to the fragment shader.

#version 330

in vec3 vColor;
in vec4 vVertex;
smooth out vec4 vSmoothColor;

uniform mat4 MVP;
void main()
{
   vSmoothColor = vec4(vColor,1);
   gl_Position = MVP*vVertex;
}

The fragment shader simply assigns the interpolated attribute (vSmoothColor) as the current fragment's color (vFragColor):

#version 330
smooth in vec4 vSmoothColor;

out vec4 vFragColor;
void main(void)
{
   vFragColor = vSmoothColor;
}

Handling of transformations and matrices

The final bit left to port our code to OpenGL 3.3 is to handle the matrices. We use the glm math library. The library is compliant to the GLSL specification and contains all of its functionality plus a lot of additional stuff added via extensions. In order to use glm in our project's, we need to include the following header:

#include < glm/glm.hpp >

In addition, we also need the following headers for handling of matrices:

#include < glm/gtc/matrix_projection.hpp >
#include < glm/gtc/matrix_transform.hpp >
#include < glm/gtc/type_ptr.hpp >

We are now ready to roll. We first handle the projection matrix. In the pre OpenGL 3.0 version of the code, the projection matrix is first loaded and then the gluPerspective func. is called as follows:

glMatrixMode(GL_PROJECTION); // Projection transformation
glLoadIdentity(); // We initialize the projection matrix as identity
gluPerspective(45.0f,(GLfloat)w/(GLfloat)h,1.0f,1000.0f);

This creates a new projection matrix and multiplies it with the top matrix in the projection matrix stack (which at the beginning is the identity matrix). In glm, we can achieve this by using:

P = glm::perspective(45.0f, (GLfloat)w/h, 1.f, 1000.f);

This generates the perspective projection matix and stores it in a variable for later use. For compliance, we keep all the parameters exactly the same. The second matrix handling is carried out in the display function where the modelview matrix is setup using a sequence of glTranslatef and glRotatef calls as follows:

glTranslatef(0.0,0.0,-50);
glRotatef(rotation_x,1.0,0.0,0.0);
glRotatef(rotation_y,0.0,1.0,0.0);
glRotatef(rotation_z,0.0,0.0,1.0);

We achieve the same thing by setting up the following matrices in glm:

glm::mat4 T = glm::translate(glm::mat4(1.0f),glm::vec3(0.0f, 0.0f, -50));
glm::mat4 Rx = glm::rotate(T,  rotation_x, glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 Ry = glm::rotate(Rx, rotation_y, glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 MV = glm::rotate(Ry, rotation_z, glm::vec3(0.0f, 0.0f, 1.0f));
glm::mat4 MVP = P*MV;

Once again for compliance, we keep the same parameters. We concatenate the modelview and projection matrices on the client side and pass it to the shader using:

glUniformMatrix4fv(shader("MVP"), 1, GL_FALSE, glm::value_ptr(MVP));

In the above lines, we use yet another operator this time the parenthesis () operator defined in the GLSLShader class. This operator returns the location of a uniform variable in the shader. For this to work, we must make sure that the shader is currently attached which we ensure by calling shader.Use() function.

Rendering with shader

Once all of the above things are set, we can start with our rendering. We first attach our VAO by calling:

glBindVertexArray(vaoID);

Next we attach our shader and pass the MVP matrix to the shader:

shader.Use();
glUniformMatrix4fv(shader("MVP"), 1, GL_FALSE, glm::value_ptr(MVP));

Next, we issue the glDrawElements call like this:

glDrawElements(GL_TRIANGLES, sizeof(cube.polygon)/sizeof(cube.polygon[0]), GL_UNSIGNED_INT, 0);

The first paramter is the type of primitive to render. The second parameter is the total number of indices, the third parameter is the type of the index and the fourth parameter is the pointer. If we have already attached our VAO, this pointer is set as null since the element array buffer binding is used from the vao to render the geometry. After the draw call, we detach our shader and the VAO:

shader.UnUse();
glBindVertexArray(0);

Finally, we swap our back buffer to show the current rendering on screen:

glutSwapBuffers();

New glut functions in freeglut

In order to support OpenGL 3.0 and above, we must use freeglut. This library handles the lower level details of the context creation for us. Freeglut adds two new functions to glut namely:

glutInitContextVersion (3, 3);

These allow us to request a specific context with the version specified. The first integer is the major version and the second is the minor version. So if we want an OpenGL version 4.1 compatible context, we would issue the following call:

glutInitContextVersion (4, 1);

Next, the context flags are specified to make sure that the core profile is selected. We may also choose GLUT_COMPATIBILITY_PROFILE if we want to have support for legacy OpenGL functions:

glutInitContextFlags (GLUT_CORE_PROFILE | GLUT_DEBUG);	
glutInitWindowSize(800,600);
glutCreateWindow("First step-OpenGL 3.3");

These functions create the window with the specified dimensions and title. Next we need to initialize glew in order to expose the funciton pointers to our client application. GLEW obtains information on the supported extensions from the graphics driver. Experimental or pre-release drivers, however, might not report every available extension through the standard mechanism, in which case GLEW will report it unsupported. To circumvent this situation, the glewExperimental global switch is turned on by setting it to GL_TRUE before calling glewInit(), which ensures that all extensions with valid entry points will be exposed:

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;
    }
}

These lines initialize glew library to enable us to call functions exposed through OpenGL extensions:

cout << "Using GLEW " << glewGetString(GLEW_VERSION)<< endl;
cout << "Vendor: " << glGetString (GL_VENDOR)<< endl;
cout << "Renderer: " << glGetString (GL_RENDERER)<< endl;
cout << "Version: " << glGetString (GL_VERSION)<< endl;
cout << "GLSL: " << glGetString (GL_SHADING_LANGUAGE_VERSION)<< endl;

These functions output relevant information on the console window to show us the current verison of glew, the current vendor and other useful information. Running the code gives us the following output:

Tut openglandglut.png

Thats it we have our code ported to OpenGL3.3. One thing to note is that the output of this tutorial is not exactly like the output from the earlier version. This is because the earlier version used distinct unshared vertices using the immediate mode. In our case, we are using shared vertices hence the colors are shared.

SOURCE CODE

The Source Code of this lesson can be downloaded from the Tutorials Main Page