Tips and Tricks for Getting Started with OpenGL and GLSL 4.0

0
177
14 min read

 

OpenGL 4.0 Shading Language Cookbook

OpenGL 4.0 Shading Language Cookbook

Over 60 highly focused, practical recipes to maximize your OpenGL Shading language use

        Read more about this book      

(For more resources on this subject, see here.)

Introduction

The first step towards using the OpenGL Shading Language version 4.0 is to create a program that utilizes the latest version of the OpenGL API. GLSL programs don’t stand on their own, they must be a part of a larger OpenGL program. In this article, we will see some tips on getting a basic OpenGL/GLSL program up and running and some techniques for communication between the OpenGL application and the shader (GLSL) program. There isn’t any GLSL programming in this article, but don’t worry, we’ll jump into GLSL with both feet in the next article. First, let’s start with some background.

The OpenGL Shading Language

The OpenGL Shading Language (GLSL) is now a fundamental and integral part of the OpenGL API. Going forward, every program written using OpenGL will internally utilize one or several GLSL programs. These “mini-programs” written in GLSL are often referred to as shader programs, or simply shaders. A shader program is one that runs on the GPU, and as the name implies, it (typically) implements the algorithms related to the lighting and shading effects of a 3-dimensional image. However, shader programs are capable of doing much more than just implementing a shading algorithm. They are also capable of performing animation, tessellation, and even generalized computation.

The field of study dubbed GPGPU (General Purpose Computing on Graphics Processing Units) is concerned with utilization of GPUs (often using specialized APIs such as CUDA or OpenCL) to perform general purpose computations such as fluid dynamics, molecular dynamics, cryptography, and so on.

Shader programs are designed to be executed directly on the GPU and often in parallel. For example, a fragment shader might be executed once for every pixel, with each execution running simultaneously on a separate GPU thread. The number of processors on the graphics card determines how many can be executed at one time. This makes shader programs incredibly efficient, and provides the programmer with a simple API for implementing highly parallel computation.

The computing power available in modern graphics cards is impressive. The following table shows the number of shader processors available for several models in the NVIDIA GeForce 400 series cards (source: http://en.wikipedia.org/wiki/Comparison_of_Nvidia_graphics_processing_units).

OpenGL GLSL 4.0 Shading Language tutorial

Shader programs are intended to replace parts of the OpenGL architecture referred to as the fixed-function pipeline. The default lighting/shading algorithm was a core part of this fixedfunction pipeline. When we, as programmers, wanted to implement more advanced or realistic effects, we used various tricks to force the fixed-function pipeline into being more flexible than it really was. The advent of GLSL helped by providing us with the ability to replace this “hard-coded” functionality with our own programs written in GLSL, thus giving us a great deal of additional flexibility and power.

In fact, recent (core) versions of OpenGL not only provide this capability, but they require shader programs as part of every OpenGL program. The old fixed-function pipeline has been deprecated in favor of a new programmable pipeline, a key part of which is the shader program written in GLSL.

Profiles: Core vs. Compatibility

OpenGL version 3.0 introduced a deprecation model, which allowed for the gradual removal of functions from the OpenGL specification. Functions or features can now be marked as deprecated, meaning that they are expected to be removed from a future version of OpenGL. For example, immediate mode rendering using glBegin/glEnd was marked deprecated in version 3.0 and removed in version 3.1.

In order to maintain backwards compatibility, the concept of compatibility profiles was introduced with OpenGL 3.2. A programmer who is writing code intended for a particular version of OpenGL (with older features removed) would use the so-called core profile. Someone who also wanted to maintain compatibility with older functionality could use the compatibility profile.

It may be somewhat confusing that there is also the concept of a full vs. forward compatible context, which is distinguished slightly from the concept of a core vs. compatibility profile. A context that is considered forward compatible basically indicates that all deprecated functionality has been removed. In other words, if a context is forward compatible, it only includes functions that are in the core, but not those that were marked as deprecated. A full context supports all features of the selected version. Some window APIs provide the ability to select full or forward compatible status along with the profile.

The steps for selecting a core or compatibility profile are window system API dependent. For example, in recent versions of Qt (at least version 4.7), one can select a 4.0 core profile using the following code:

QGLFormat format;
format.setVersion(4,0);
format.setProfile(QGLFormat::CoreProfile);
QGLWidget *myWidget = new QGLWidget(format);

(you can download the example code here)

All programs in this article are designed to be compatible with an OpenGL 4.0 core profile.

Using the GLEW Library to access the latest OpenGL functionality

The OpenGL ABI (application binary interface) is frozen to OpenGL version 1.1 on Windows. Unfortunately for Windows developers, that means that it is not possible to link directly to functions that are provided in newer versions of OpenGL. Instead, one must get access to these functions by acquiring a function pointer at runtime. Getting access to the function pointers requires somewhat tedious work, and has a tendency to clutter your code. Additionally, Windows typically comes with a standard OpenGL header file that conforms to OpenGL 1.1. The OpenGL wiki states that Microsoft has no plans to update the gl.h and opengl32.lib that comes with their compilers. Thankfully, others have provided libraries that manage all of this for us by probing your OpenGL libraries and transparently providing the necessary function pointers, while also exposing the necessary functionality in its header files. One such library is called GLEW (OpenGL Extension Wrangler).

Getting ready

Download the GLEW distribution from http://glew.sourceforge.net. There are binaries available for Windows, but it is also a relatively simple matter to compile GLEW from source (see the instructions on the website: http://glew.sourceforge.net).

Place the header files glew.h and wglew.h from the GLEW distribution into a proper location for your compiler. If you are using Windows, copy the glew32.lib to the appropriate library directory for your compiler, and place the glew32.dll into a system-wide location, or the same directory as your program’s executable. Full installation instructions for all operating systems and common compilers are available on the GLEW website.

How to do it…

To start using GLEW in your project, use the following steps:

  1. Make sure that, at the top of your code, you include the glew.h header before you include the OpenGL header files:

    #include <GL/glew.h>
    #include <GL/gl.h>
    #include <GL/glu.h>

  2. In your program code, somewhere just after the GL context is created (typically in an initialization function), and before any OpenGL functions are called, include the following code:

    GLenum err = glewInit();
    if( GLEW_OK != err )
    {
    fprintf(stderr, "Error initializing GLEW: %sn",
    glewGetErrorString(err) );
    }

That’s all there is to it!

How it works…

Including the glew.h header file provides declarations for the OpenGL functions as function pointers, so all function entry points are available at compile time. At run time, the glewInit() function will scan the OpenGL library, and initialize all available function pointers. If a function is not available, the code will compile, but the function pointer will not be initialized.

There’s more…

GLEW includes a few additional features and utilities that are quite useful.

GLEW visualinfo

The command line utility visualinfo can be used to get a list of all available extensions and “visuals” (pixel formats, pbuffer availability, and so on). When executed, it creates a file called visualinfo.txt, which contains a list of all the available OpenGL, WGL, and GLU extensions, including a table of available visuals (pixel formats, pbuffer availability, and the like).

GLEW glewinfo

The command line utility glewinfo lists all available functions supported by your driver. When executed, the results are printed to stdout.

Checking for extension availability at runtime

You can also check for the availability of extensions by checking the status of some GLEW global variables that use a particular naming convention. For example, to check for the availability of ARB_vertex_program, use something like the following:

if ( ! GLEW_ARB_vertex_program )
{
fprintf(stderr, "ARB_vertex_program is missing!n");
...
}

See also

Another option for managing OpenGL extensions is the GLee library (GL Easy Extension). It is available from http://www.elf-stone.com/glee.php and is open source under the modified BSD license. It works in a similar manner to GLEW, but does not require runtime initialization.

Using the GLM library for mathematics

Mathematics is core to all of computer graphics. In earlier versions, OpenGL provided support for managing coordinate transformations and projections using the standard matrix stacks (GL_MODELVIEW and GL_PROJECTION). In core OpenGL 4.0, however, all of the functionality supporting the matrix stacks has been removed. Therefore, it is up to us to provide our own support for the usual transformation and projection matrices, and then to pass them into our shaders. Of course, we could write our own matrix and vector classes to manage this, but if you’re like me, you prefer to use a ready-made, robust library.

One such library is GLM (OpenGL Mathematics) written by Christophe Riccio. Its design is based on the GLSL specification, so the syntax is very similar to the mathematical support in GLSL. For experienced GLSL programmers, this makes it very easy to use. Additionally, it provides extensions that include functionality similar to some of the much-missed OpenGL functions such as glOrtho, glRotate, or gluLookAt.

Getting ready

Download the latest GLM distribution from http://glm.g-truc.net. Unzip the archive file, and copy the glm directory contained inside to anywhere in your compiler’s include path.

How to do it…

Using the GLM libraries is simply a matter of including the core header file (highlighted in the following code snippet) and headers for any extensions. We’ll include the matrix transform extension, and the transform2 extension.

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform2.hpp>

The GLM classes are then available in the glm namespace. The following is an example of how you might go about making use of some of them.

glm::vec4 position = glm::vec4( 1.0f, 0.0f, 0.0f, 1.0f );

glm::mat4 view = glm::lookAt( glm::vec3(0.0,0.0,5.0),
glm::vec3(0.0,0.0,0.0),
glm::vec3(0.0,1.0,0.0) );

glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate( model, 90.0f, glm::vec3(0.0f,1.0f,0.0) );

glm::mat4 mv = view * model;

glm::vec4 transformed = mv * position;

How it works…

The GLM library is a header-only library. All of the implementation is included within the header files. It doesn’t require separate compilation and you don’t need to link your program to it. Just placing the header files in your include path is all that’s required!

The preceding example first creates a vec4 (four coordinate vector) representing a position. Then it creates a 4×4 view matrix by using the glm::lookAt function from the transform2 extension. This works in a similar fashion to the old gluLookAt function. In this example, we set the camera’s location at (0,0,5), looking towards the origin, with the “up” direction in the direction of the Y-axis. We then go on to create the modeling matrix by first storing the identity matrix in the variable model (via the constructor: glm::mat4(1.0f)), and multiplying by a rotation matrix using the glm::rotate function. The multiplication here is implicitly done by the glm::rotate function. It multiplies its first parameter by the rotation matrix that is generated by the function. The second parameter is the angle of rotation (in degrees), and the third parameter is the axis of rotation. The net result is a rotation matrix of 90 degrees around the Y-axis.

Finally, we create our model view matrix (mv) by multiplying the view and model variables, and then using the combined matrix to transform the position. Note that the multiplication operator has been overloaded to behave in the expected way.

As stated above, the GLM library conforms as closely as possible to the GLSL specification, with additional features that go beyond what you can do in GLSL. If you are familiar with GLSL, GLM should be easy and natural to use.

Swizzle operators (selecting components using commands like: foo.x, foo.xxy, and so on) are disabled by default in GLM. You can selectively enable them by defining GLM_SWIZZLE before including the main GLM header. The GLM manual has more detail. For example, to enable all swizzle operators you would do the following:

#define GLM_SWIZZLE

#include <glm/glm.hpp>

There’s more…

It is not recommended to import all of the GLM namespace using a command like:

using namespace glm;

This will most likely cause a number of namespace clashes. Instead, it is preferable to import symbols one at a time, as needed. For example:

#include <glm/glm.hpp>
using glm::vec3;
using glm::mat4;

Using the GLM types as input to OpenGL

GLM supports directly passing a GLM type to OpenGL using one of the OpenGL vector functions (with the suffix “v”). For example, to pass a mat4 named proj to OpenGL we can use the following code:

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

...

glm::mat4 proj = glm::perspective( viewAngle, aspect,
nearDist, farDist );
glUniformMatrix4fv(location, 1, GL_FALSE, &proj[0][0]);

See also

The GLM website http://glm.g-truc.net has additional documentation and examples.

Determining the GLSL and OpenGL version

In order to support a wide range of systems, it is essential to be able to query for the supported OpenGL and GLSL version of the current driver. It is quite simple to do so, and there are two main functions involved: glGetString and glGetIntegerv.

How to do it…

The code shown below will print the version information to stdout:

const GLubyte *renderer = glGetString( GL_RENDERER );
const GLubyte *vendor = glGetString( GL_VENDOR );
const GLubyte *version = glGetString( GL_VERSION );
const GLubyte *glslVersion =
glGetString( GL_SHADING_LANGUAGE_VERSION );

GLint major, minor;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);

printf("GL Vendor : %sn", vendor);
printf("GL Renderer : %sn", renderer);
printf("GL Version (string) : %sn", version);
printf("GL Version (integer) : %d.%dn", major, minor);
printf("GLSL Version : %sn", glslVersion);

How it works…

Note that there are two different ways to retrieve the OpenGL version: using glGetString and glGetIntegerv. The former can be useful for providing readable output, but may not be as convenient for programmatically checking the version because of the need to parse the string. The string provided by glGetString(GL_VERSION) should always begin with the major and minor versions separated by a dot; however, the minor version could be followed with a vendor-specific build number. Additionally, the rest of the string can contain additional vendor-specific information and may also include information about the selected profile.

glGetInteger is available in OpenGL 3.0 or greater.

The queries for GL_VENDOR and GL_RENDERER provide additional information about the OpenGL driver. The call glGetString(GL_VENDOR) returns the company responsible for the OpenGL implementation. The call to glGetString(GL_RENDERER) provides the name of the renderer which is specific to a particular hardware platform (such as “ATI Radeon HD 5600 Series”). Note that both of these do not vary from release to release, so can be used to determine the current platform.

Of more importance to us is the call to glGetString(GL_SHADING_LANGUAGE_VERSION) which provides the supported GLSL version number. This string should begin with the major and minor version numbers separated by a period, but similar to the GL_VERSION query, may include other vendor-specific information.

There’s more…

It is often useful to query for the supported extensions of the current OpenGL implementation. In versions prior to OpenGL 3.0, one could retrieve a full, space separated list of extension names with the following code:

GLubyte *extensions = glGetString(GL_EXTENSIONS);

The string that is returned can be extremely long and parsing it can be susceptible to error if not done carefully.

In OpenGL 3.0, a new technique was introduced, and the above functionality was deprecated (and finally removed in 3.1). Extension names are now indexed and can be individually queried by index. We use the glGetStringi variant for this. For example, to get the name of the extension stored at index i, we use: glGetString(GL_EXTENSIONS, i). To print a list of all extensions, we could use the following code:

GLint nExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &nExtensions);

for( int i = 0; i < nExtensions; i++ )
printf("%sn", glGetStringi( GL_EXTENSIONS, i ) );

LEAVE A REPLY

Please enter your comment!
Please enter your name here