Tips and Tricks for Getting Started with OpenGL and GLSL 4.0

Exclusive offer: get 50% off this eBook here
OpenGL 4.0 Shading Language Cookbook

OpenGL 4.0 Shading Language Cookbook — Save 50%

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

$29.99    $15.00
by David Wolff | August 2011 | Cookbooks Open Source Web Graphics & Video

The OpenGL Shading Language (GLSL) Version 4.0 brings unprecedented power and flexibility to programmers interested in creating modern, interactive, graphical programs. It allows us to harness the power of modern Graphics Processing Units (GPUs) in a straightforward way by providing a simple yet powerful language and API.

This article by David Wolff, author of OpenGL 4.0 Shading Language Cookbook, provides tips and tricks for setting up your OpenGL development environment to take advantage of the latest OpenGL and GLSL language features. It also teaches the basic techniques for communicating with shader programs. In this article, we will cover:

  • Using the GLEW library to access the latest OpenGL functionality
  • Using the GLM library for mathematics
  • Determining the GLSL and OpenGL version
  • Compiling a shader
  • Linking a shader program
  • Sending data to a shader using per-vertex attributes and vertex buffer objects
  • Getting a list of active vertex input attributes and indices
  • Sending data to a shader using uniform variables
  • Getting a list of active uniform variables

 

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: %s\n",
    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 4x4 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 : %s\n", vendor);
printf("GL Renderer : %s\n", renderer);
printf("GL Version (string) : %s\n", version);
printf("GL Version (integer) : %d.%d\n", major, minor);
printf("GLSL Version : %s\n", 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("%s\n", glGetStringi( GL_EXTENSIONS, i ) );

OpenGL 4.0 Shading Language Cookbook Over 60 highly focused, practical recipes to maximize your OpenGL Shading language use
Published: July 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

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

Compiling a shader

The GLSL compiler is built into the OpenGL library, and shaders can only be compiled within the context of a running OpenGL program. There is currently no external tool for pre-compiling GLSL shaders and/or shader programs.

Recently, OpenGL 4.1 added the ability to save compiled shader programs to a file, enabling OpenGL programs to avoid the overhead of shader compilation by loading pre-compiled shader programs.

Compiling a shader involves creating a shader object, providing the source code (as a string or set of strings) to the shader object, and asking the shader object to compile the code. The process is represented by the following diagram.

OpenGL GLSL 4.0 Shading Language tutorial

Getting ready

To compile a shader, we'll need a basic example to work with. Let's start with the following simple vertex shader. Save it in a file named basic.vert.

#version 400

in vec3 VertexPosition;
in vec3 VertexColor;

out vec3 Color;

void main()
{
Color = VertexColor;
gl_Position = vec4( VertexPosition, 1.0 );
}

In case you're curious about what this code does, it works as a "pass-through" shader. It takes the input attributes VertexPosition and VertexColor and passes them along to the fragment shader via the output variables gl_Position and Color.

Next, we'll need to build a basic shell for an OpenGL program using any standard windowing toolkit. Examples of cross-platform toolkits include GLUT, FLTK, Qt, or wxWidgets. Throughout this text, I'll make the assumption that you can create a basic OpenGL program with your favorite toolkit. Virtually all toolkits have a hook for an initialization function, a resize callback (called upon resizing of the window), and a drawing callback (called for each window refresh). For the purposes of this recipe, we need a program that creates and initializes an OpenGL context; it need not do anything other than display an empty OpenGL window.

Finally, we need to load the shader source code into a character array named shaderCode. Don't forget to add the null character at the end! This example assumes that the variable shaderCode points to an array of GLchar that is properly terminated by a null character.

How to do it...

To compile a shader, use the following steps:

  1. Create the shader object as follows.

    GLuint vertShader = glCreateShader( GL_VERTEX_SHADER );
    if( 0 == vertShader )
    {
    fprintf(stderr, "Error creating vertex shader.\n");
    exit(1);
    }

  2. Copy the source code (perhaps from multiple locations) into the shader object.

    const GLchar * shaderCode = loadShaderAsString("basic.vert");
    const GLchar* codeArray[] = {shaderCode};
    glShaderSource( vertShader, 1, codeArray, NULL );

  3. Compile the shader.

    glCompileShader( vertShader );

  4. Verify the compilation status.

    GLint result;
    glGetShaderiv( vertShader, GL_COMPILE_STATUS, &result );
    if( GL_FALSE == result )
    {
    fprintf( stderr, "Vertex shader compilation failed!\n" );
    GLint logLen;
    glGetShaderiv( vertShader, GL_INFO_LOG_LENGTH, &logLen );
    if( logLen > 0 )
    {
    char * log = (char *)malloc(logLen);
    GLsizei written;
    glGetShaderInfoLog(vertShader, logLen, &written, log);

    fprintf(stderr, "Shader log:\n%s", log);
    free(log);
    }
    }

How it works...

The first step is to create the shader object using the function glCreateShader. The argument is the type of shader, and can be one of the following: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_EVALUATION_SHADER, or GL_TESS_CONTROL_SHADER. In this case, since we are compiling a vertex shader, we use GL_VERTEX_SHADER. This function returns the value used for referencing the vertex shader object, sometimes called the object "handle". We store that value in the variable vertShader. If an error occurs while creating the shader object, this function will return 0, so we check for that and if it occurs, we print an appropriate message and terminate.

Following the creation of the shader object, we load the source code into the shader object using the function glShaderSource. This function is designed to accept an array of strings in order to support the option of compiling multiple files at once. So before we call glShaderSource, we place a pointer to our source code into an array named sourceArray. The first argument to glShaderSource is the handle to the shader object. The second is the number of source code strings that are contained in the array. The third argument is a pointer to an array of source code strings. The final argument is an array of GLint values that contains the length of each source code string in the previous argument. In this case, we pass a value of NULL, which indicates that each source code string is terminated by a null character. If our source code strings were not null terminated then this argument must be a valid array. Note that once this function returns, the source code has been copied into OpenGL internal memory, so the memory used to store the source code can be freed.

The next step is to compile the source code for the shader. We do this by simply calling glCompileShader, and passing the handle to the shader that is to be compiled. Of course, depending on the correctness of the source code, the compilation may fail, so the next step is to check whether or not the compilation was successful.

We can query for the compilation status by calling glGetShaderiv, which is a function for querying the attributes of a shader object. In this case we are interested in the compilation status, so we use GL_COMPILE_STATUS as the second argument. The first argument is of course the handle to the shader object, and the third argument is a pointer to an integer where the status will be stored. The function provides a value of either GL_TRUE or GL_FALSE in the third argument, indicating whether or not the compilation was successful.

If the compile status is GL_FALSE, then we can query for the shader log, which will provide additional details about the failure. We do so by first querying for the length of the log by calling glGetShaderiv again with a value of GL_INFO_LOG_LENGTH. This provides the length of the log in the variable logLen, including the null termination character. We then allocate space for the log, and retrieve the log by calling glGetShaderInfoLog. The first parameter is the handle to the shader object, the second is the size of the character buffer for storing the log, the third argument is a pointer to an integer where the number of characters actually written (excluding the null terminator character) will be stored, and the fourth argument is a pointer to the character buffer for storing the log itself. Once the log is retrieved, we print it to stderr and free its memory space.

There's more...

The technique for compiling a shader is nearly identical for each shader type. The only significant difference is the argument to glCreateShader.

Of course, shader compilation is only the first step. To create a working shader program, we often have at least two shaders to compile, and then the shaders must be linked together into a shader program object. We'll see the steps involved in linking in the next recipe.

Deleting a shader object

Shader objects can be deleted when no longer needed by calling glDeleteShader. This frees the memory used by the shader and invalidates its handle. Note that if a shader object is already attached to a program object, it will not be immediately deleted, but flagged for deletion when it is detached from the program object.

Linking a shader program

Once we have compiled our shaders and before we can actually install them into the OpenGL pipeline, we need to link them together into a shader program. Among other things, the linking step involves making the connections between the input variables from one shader to the output variables of another, and making the connections between the other input/output variables of a shader to appropriate locations in the OpenGL environment.

Linking involves steps that are similar to those involved in compiling a shader. We attach each shader object to a new shader program object and then tell the shader program object to link (making sure that the shader objects are compiled before linking).

OpenGL GLSL 4.0 Shading Language tutorial

Getting ready

For this recipe we'll assume that you've already compiled two shader objects whose handles are stored in the variables vertShader and fragShader.

For this and a few other recipes in this article, we'll use the following source code for the fragment shader:

#version 400

in vec3 Color;

out vec4 FragColor;

void main() {
FragColor = vec4(Color, 1.0);
}

For the vertex shader, we'll use the source code from the previous recipe.

How to do it...

In our OpenGL initialization function, and after the compilation of the shader objects referred to by vertShader and fragShader, use the following steps.

  1. Create the program object.

    GLuint programHandle = glCreateProgram();
    if( 0 == programHandle )
    {
    fprintf(stderr, "Error creating program object.\n");
    exit(1);
    }

  2. Attach the shaders to the program object.

    glAttachShader( programHandle, vertShader );
    glAttachShader( programHandle, fragShader );

  3. Link the program.

    glLinkProgram( programHandle );

  4. Verify the link status.

    GLint status;
    glGetProgramiv( programHandle, GL_LINK_STATUS, &status );
    if( GL_FALSE == status ) {

    fprintf( stderr, "Failed to link shader program!\n" );

    GLint logLen;
    glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH,
    &logLen);
    if( logLen > 0 )
    {
    char * log = (char *)malloc(logLen);
    GLsizei written;
    glGetProgramInfoLog(programHandle, logLen,
    &written, log);
    fprintf(stderr, "Program log: \n%s", log);
    free(log);
    }
    }

  5. If linking is successful, install the program into the OpenGL pipeline.

    else
    {
    glUseProgram( programHandle );
    }

How it works...

We start by calling glCreateProgram to create an empty program object. This function returns a handle to the program object, which we store in a variable named programHandle. If an error occurs with program creation, the function will return 0. We check for that, and if it occurs, we print an error message and exit.

Next, we attach each shader to the program object using glAttachShader. The first argument is the handle to the program object, and the second is the handle to the shader object to be attached.

Then, we link the program by calling glLinkProgram, providing the handle to the program object as the only argument. As with compilation, we check for the success or failure of the link with the subsequent query.

We check the status of the link by calling glGetProgramiv. Similar to glGetShaderiv, glGetProgramiv allows us to query various attributes of the shader program. In this case, we ask for the status of the link by providing GL_LINK_STATUS as the second argument. The status is returned in the location pointed to by the third argument, in this case named status.

The link status is either GL_TRUE or GL_FALSE indicating the success or failure of the link. If the value of the status is GL_FALSE, we retrieve and display the program information log, which should contain additional information and error messages. The program log is retrieved by the call to glGetProgramInfoLog. The first argument is the handle to the program object, the second is the size of the buffer to contain the log, the third is a pointer to a GLsizei variable where the number of bytes written to the buffer will be stored (excluding the null terminator), and the fourth is a pointer to the buffer that will store the log. The buffer can be allocated based on the size returned by the call to glGetProgramiv with the parameter GL_INFO_LOG_LENGTH. The string that is provided in log will be properly null terminated.

Finally, if the link is successful, we install the program into the OpenGL pipeline by calling glUseProgram, providing the handle to the program as the argument.

With the simple fragment shader from this recipe and the vertex shader from the preceding recipe compiled, linked, and installed into the OpenGL pipeline, we have a complete OpenGL pipeline and are ready to begin rendering. Drawing a triangle and supplying different values (red, green, and blue) for the Color attribute yields an image of a multi-colored triangle where the vertices are red, green, and blue, and inside the triangle, the three colors are interpolated, causing a blending of colors throughout.

OpenGL GLSL 4.0 Shading Language tutorial

There's more...

You can compile and link multiple shader programs within a single OpenGL program. They can be swapped in and out of the OpenGL pipeline by calling glUseProgram to select the desired program.

Deleting a shader program

If a program is no longer needed, it can be deleted from the OpenGL memory by calling glDeleteProgram, providing the program handle as the only argument. This invalidates the handle and frees the memory used by the program. Note that if the program object is currently in use, it will not be immediately deleted, but will be flagged for deletion when it is no longer in use.

The deletion of a shader program detaches the shader objects that were attached to the program but does not delete them unless those shader objects have already been flagged for deletion by a previous call to glDeleteShader.

Sending data to a shader using per-vertex attributes and vertex buffer objects

The vertex shader is invoked once per vertex. Its main job is to process the data associated with the vertex, and pass it (and possibly other information) along to the next stage of the pipeline. In order to give our vertex shader something to work with, we must have some way of providing (per-vertex) input to the shader. Typically, this includes the vertex position, normal vector, and texture coordinate (among other things). In earlier versions of OpenGL (prior to 3.0), each piece of vertex information had a specific "channel" in the pipeline. It was provided to the shaders using functions such as glVertex, glTexCoord, and glNormal (or within vertex arrays using glVertexPointer, glTexCoordPointer, or glNormalPointer).The shader would then access these values via built-in variables such as gl_Vertex and gl_Normal. This functionality was deprecated in OpenGL 3.0 and later removed. Instead, now vertex information must be provided using generic vertex attributes, usually in conjunction with (vertex) buffer objects. The programmer is now free to define an arbitrary set of per-vertex attributes to provide as input to the vertex shader. For example, in order to implement normal mapping, we might decide that position, normal vector, and tangent vector should be provided along with each vertex. With OpenGL 4.0, it's easy to define this as the set of input attributes. This gives us a great deal of flexibility to define our vertex information in any way that is appropriate for our application, but may require a bit of getting used to for those of us who are used to the old way of doing things.

In the vertex shader, per-vertex input attributes are declared by using the GLSL qualifier in. For example, to define a 3-component vector input attribute named VertexColor, we use the following code:

in vec3 VertexColor;

Of course, the data for this attribute must be supplied by the OpenGL program. To do so, we make use of vertex buffer objects. The buffer object contains the values for the input attribute and in the main OpenGL program we make the connection between the buffer and the input attribute, and define how to "step through" the data. Then, when rendering, OpenGL pulls data for the input attribute from the buffer for each invocation of the vertex shader.

For this recipe, we'll draw the simplest OpenGL shape, a triangle. Our vertex attributes will include the position and color. We'll use a fragment shader to blend the colors of each vertex across the triangle to produce an image similar to the one shown in the following screenshot. The vertices of the triangle are red, green, and blue, and the interior of the triangle has those three colors blended together.

OpenGL GLSL 4.0 Shading Language tutorial

Getting ready

We'll start with a simple, empty OpenGL program, and the following shaders.

The vertex shader (basic.vert):

#version 400

in vec3 VertexPosition;
in vec3 VertexColor;

out vec3 Color;

void main()
{
Color = VertexColor;
gl_Position = vec4(VertexPosition,1.0);
}

Note that there are two input attributes in the vertex shader: VertexPosition and VertexColor. Our program needs to provide the data for these two attributes for each vertex. We will do so by mapping our polygon data to these variables.

It also has one output variable named Color, which is sent to the fragment shader. In this case, Color is just an unchanged copy of VertexColor. Also, note that the attribute VertexPosition is simply expanded and passed along to the built-in output variable gl_Position for further processing.

The fragment shader (basic.frag):

#version 400

in vec3 Color;

out vec4 FragColor;

void main() {
FragColor = vec4(Color, 1.0);
}

There is just one input variable for this shader, Color. This links to the corresponding output variable in the vertex shader, and will contain a value that has been interpolated across the triangle based on the values at the vertices. We simply expand and copy this color to the output variable FragColor.

Write code to compile and link these shaders into a shader program. In the following code, I'll assume that the handle to the shader program is programHandle.

How to do it...

Use the following steps to set up your buffer objects and render the triangle.

  1. Just prior to linking the shader program, define the mapping between vertex attributes and shader input variables using glBindAttribLocation.

    // Bind index 0 to the shader input variable "VertexPosition"
    glBindAttribLocation(programHandle, 0, "VertexPosition");

    // Bind index 1 to the shader input variable "VertexColor"
    glBindAttribLocation(programHandle, 1, "VertexColor");

  2. Create a global (or private instance) variable to hold our handle to the vertex array object:

    GLuint vaoHandle;

  3. Within the initialization function, create and populate the vertex buffer objects for each attribute.

    float positionData[] = {
    -0.8f, -0.8f, 0.0f,
    0.8f, -0.8f, 0.0f,
    0.0f, 0.8f, 0.0f };

    float colorData[] = {
    1.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 1.0f };

    // Create the buffer objects
    GLuint vboHandles[2];
    glGenBuffers(2, vboHandles);
    GLuint positionBufferHandle = vboHandles[0];
    GLuint colorBufferHandle = vboHandles[1];

    // Populate the position buffer
    glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionData,
    GL_STATIC_DRAW);

    // Populate the color buffer
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorData,
    GL_STATIC_DRAW);

  4. Create and bind to a vertex array object, which stores the relationship between the buffers and the input attributes.

    // Create and set-up the vertex array object
    glGenVertexArrays( 1, &vaoHandle );
    glBindVertexArray(vaoHandle);

    // Enable the vertex attribute arrays
    glEnableVertexAttribArray(0); // Vertex position
    glEnableVertexAttribArray(1); // Vertex color

    // Map index 0 to the position buffer
    glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
    glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0,
    (GLubyte *)NULL );

    // Map index 1 to the color buffer
    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
    glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 0,
    (GLubyte *)NULL );

  5. In the render function, bind to the vertex array object and call glDrawArrays to initiate rendering.

    glBindVertexArray(vaoHandle);
    glDrawArrays(GL_TRIANGLES, 0, 3 );

How it works...

Vertex attributes are the input variables to our vertex shader. In the vertex shader above, our two attributes are VertexPosition and VertexColor. Since we can give these variables any name we like, OpenGL provides a way to refer to vertex attributes in the OpenGL program by associating each (active) input variable with a generic attribute index. These generic indices are simply integers between 0 and GL_MAX_VERTEX_ATTRIBS – 1. We refer to the vertex attributes in our OpenGL code by referring to the corresponding generic vertex attribute index.

The first step above involves making connections between the shader input variables VertexPosition and VertexColor and the generic vertex attribute indexes 0 and 1 respectively, using the function glBindAttribLocation. If this is done within the OpenGL application, we have to do this before the program is linked.

It is not strictly necessary to explicitly specify the mappings between attribute variables and generic attribute indexes, because OpenGL will automatically map active vertex attributes to generic indexes when the program is linked. We could then query for the mappings and determine the indexes that correspond to the shader's input variables. It may be somewhat clearer, however, to explicitly specify the mapping as we do in this example.

The next step involves setting up a pair of buffer objects to store our position and color data. As with most OpenGL objects, we start by acquiring handles to two buffers by calling glGenBuffers. We then assign each handle to a separate descriptive variable to make the following code clearer.

For each buffer object, we first bind the buffer to the GL_ARRAY_BUFFER binding point by calling glBindBuffer. The first argument to glBindBuffer is the target binding point. For vertex attribute data, we use GL_ARRAY_BUFFER. Examples of other kinds of targets (such as GL_UNIFORM_BUFFER, or GL_ELEMENT_ARRAY_BUFFER) will be seen in later examples. Once our buffer object is bound, we can populate the buffer with vertex/color data by calling glBufferData. The second and third arguments to this function are the size of the array and a pointer to the array containing the data. Let's focus on the first and last argument. The first argument indicates the target buffer object. The data provided in the third argument is copied into the buffer that is bound to this binding point. The last argument is one that gives OpenGL a hint about how the data will be used so that it can determine how best to manage the buffer internally. For full details about this argument, take a look at the OpenGL documentation (http://www.opengl.org/sdk/docs/man4/). In our case, the data specified once will not be modified, and will be used many times for drawing operations, so this usage pattern best corresponds to the value GL_STATIC_DRAW.

Now that we have set up our buffer objects, we tie them together into a vertex array object (VAO). The VAO contains information about the connections between the data in our buffers and the input vertex attributes. We create a VAO using the function glGenVertexArrays. This gives us a handle to our new object, which we store in the (global) variable vaoHandle. Then we enable the generic vertex attribute indexes 0 and 1 by calling glEnableVertexAttribArray. Doing so indicates that the values for the attributes will be accessed and used for rendering.

The next step makes the connection between the buffer objects and the generic vertex attribute indexes.

// Map index 0 to the position buffer
glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0,
(GLubyte *)NULL );

First we bind the buffer object to the GL_ARRAY_BUFFER binding point, and then we call glVertexAttribPointer, which tells OpenGL which generic index the data should be used with, the format of the data stored in the buffer object, and where it is located within the buffer object that is bound to the GL_ARRAY_BUFFER binding point. The first argument is the generic attribute index. The second is the number of components per vertex attribute (1, 2, 3, or 4). In this case, we are providing 3-dimensional data, so we want 3 components per vertex. The third argument is the data type of each component in the buffer. The fourth is a Boolean which specifies whether or not the data should be automatically normalized (mapped to a range of [-1,1] for signed integral values or [0,1] for unsigned integral values). The fifth argument is the stride, which indicates the byte offset between consecutive attributes. Since our data is tightly packed, we use a value of zero. The last argument is a pointer, which is not treated as a pointer! Instead, its value is interpreted as a byte offset from the beginning of the buffer to the first attribute in the buffer. In this case, there is no additional data in either buffer prior to the first element, so we use a value of zero (NULL).

The vertex array object stores all of the OpenGL state related to the relationship between buffer objects and the generic vertex attributes, as well as the information about the format of the data in the buffer objects. This allows us to quickly return all of this state when rendering.

In the render function, it is simply a matter of clearing the color buffer using glClear, binding to the vertex array object, and calling glDrawArrays to draw our triangle. The function glDrawArrays initiates rendering of primitives by stepping through the buffers for each enabled attribute array, and passing the data down the pipeline to the vertex shader. The first argument is the render mode (in this case we are drawing triangles), the second is the starting index in the enabled arrays, and the third argument is the number of indices to be rendered (3 vertexes for a single triangle).

To summarize, rendering with vertex buffer objects (VBOs) involves the following steps:

  1. Before linking the shader program, define the mappings between generic vertex attribute indexes and shader input variables by calling glBindAttribLocation.
  2. Create and populate the buffer objects for each attribute.
  3. Create and define the vertex array object by calling glVertexAttribPointer while the appropriate buffer is bound.
  4. When rendering, bind to the vertex array object and call glDrawArrays, or other appropriate rendering function (for example, glDrawElements).

There's more...

You may have noticed that I've neglected saying anything about the output variable FragColor in the fragment shader. This variable receives the final output color for each fragment (pixel). Like vertex input variables, this variable also needs to be associated with a location. Of course, we typically would like this to be linked to the back color buffer, which by default (in double buffered systems) is "color number" zero. (The relationship of the color numbers to render buffers can be changed by using glDrawBuffers.) In this program we are relying on the fact that the linker will automatically link our only fragment output variable to color number zero. To explicitly do so, we could (and probably should) have used the following command prior to program linking:

glBindFragDataLocation(programHandle, 0, "FragColor");

We are free to define multiple output variables for a fragment shader, thereby enabling us to render to multiple output buffers. This can be quite useful for specialized algorithms such as deferred shading.

Using layout qualifiers

We can avoid the need to call glBindAttribLocation within the OpenGL program by using layout qualifiers to define the attribute index within the shader itself. For example, we could remove the two calls to glBindAttribLocation, and change the input variable declarations in our vertex shader to:

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexColor;

This would indicate to the linker that VertexPosition should correspond to generic attribute index 0 and VertexColor to index 1.

We can use a layout qualifier to define the color number for our fragment output variables as well:

layout (location = 0) out vec4 FragColor;

This would tell the linker to bind the output variable FragColor to color number 0, avoiding the need to call glBindFragDataLocation within our OpenGL program.

Using element arrays

It is often the case that we need to step through our vertex arrays in a non-linear fashion. In other words we may want to "jump around" the data rather than just moving through it from beginning to end, as we did in this example. For example, we might want to draw a cube where the vertex data consists of only eight positions (the corners of the cube). In order to draw the cube, we would need to draw 12 triangles (2 for each face), each of which consists of 3 vertices. All of the required position data is in the original 8 positions, but to draw all the triangles, we'll need to jump around and use each position for at least three different triangles.

To jump around in our vertex arrays, we can make use of element arrays. The element array is another buffer that defines the indices used when stepping through the vertex arrays. For details on using element arrays, take a look at the function glDrawElements in the OpenGL documentation (http://www.opengl.org/sdk/docs/man4/).

Interleaved arrays

In this example, we used two buffers (one for color and one for position). Instead, we could have used just a single buffer and combined all of the data. The data for multiple attributes can be interleaved within an array, such that all of the data for a given vertex is grouped together within the buffer. Doing so just requires careful use of the arguments to glVertexAttribPointer (particularly the fifth argument: the stride). Take a look at the OpenGL documentation for full details (http://www.opengl.org/sdk/docs/man4/).

The decision about when to use interleaved arrays, and when to use separate arrays, is highly dependent on the situation. Interleaved arrays may bring better results due to the fact that data is accessed together and resides closer in memory (so-called locality of reference), resulting in better caching performance.

OpenGL 4.0 Shading Language Cookbook Over 60 highly focused, practical recipes to maximize your OpenGL Shading language use
Published: July 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

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

Getting a list of active vertex input attributes and indices

As covered in the previous recipe, the input variables within a vertex shader are linked to generic vertex attribute indices at the time the program is linked. If we need to specify the relationship, we can either call glBindAttribLocation before linking, or we can use layout qualifiers within the shader itself.

However, it may be preferable to let the linker create the mappings automatically and query for them after program linking is complete. In this recipe, we'll see a simple example that prints all the active attributes and their indices.

Getting ready

Start with an OpenGL program that compiles and links a shader pair. You could use the shaders from the previous recipe.

As in previous recipes, we'll assume that the handle to the shader program is stored in a variable named programHandle.

How to do it...

After linking the shader program, use the following steps to print information about the active attributes in your shader program:

  1. Retrieve the number of active attributes and the maximum length of their names using glGetProgramiv.

    GLint maxLength, nAttribs;
    glGetProgramiv(programHandle, GL_ACTIVE_ATTRIBUTES,
    &nAttribs);
    glGetProgramiv(programHandle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
    &maxLength);

  2. Allocate a buffer to hold each attribute name.

    GLchar * name = (GLchar *) malloc( maxLength );

  3. Get and print information about each active attribute using glGetActiveAttrib and glGetAttribLocation.

    GLint written, size, location;
    GLenum type;
    printf(" Index | Name\n");
    printf("------------------------------------------------\n");
    for( int i = 0; i < nAttribs; i++ ) {
    glGetActiveAttrib( programHandle, i, maxLength, &written,
    &size, &type, name );
    location = glGetAttribLocation(programHandle, name);
    printf(" %-5d | %s\n",location, name);
    }

    free(name);

How it works...

We start by querying for the number of active attributes by calling glGetProgramiv with the argument GL_ACTIVE_ATTRIBUTES. The result is stored in nAttribs. Next, we query for the length of the longest attribute name (GL_ACTIVE_ATTRIBUTE_MAX_LENGTH) and store the result in maxLength. This includes the null terminating character, so we use that value to allocate space to store each variable name.

Next, we loop over each index (0 to nAttrib - 1), and retrieve information about each attribute by calling glGetActiveAttrib and glGetAttribLocation. The function glGetActiveAttrib returns a bunch of information about the attribute at the index provided as the second argument. Note that this index is not necessarily the same as the generic vertex attribute index (location) for the variable. The function provides the attribute name, size, and type, which are stored in the variables name, size, and type. Once we have the variable name, we can query for its location (the generic attribute index), by calling glGetAttribLocation, and passing in the program handle and the variable name. We then print the variable's location and name to standard out.

There's more...

It should be noted that, in order for a vertex shader input variable to be considered active, it must be used within the vertex shader. In other words, a variable is considered active if it is determined by the GLSL linker that it may be accessed during program execution. If a variable is declared within a shader, but not used, the above code will not display the variable because it is not considered active and will be effectively ignored by OpenGL.

Sending data to a shader using uniform variables

Vertex attributes provide one avenue for providing input to shaders, a second technique is uniform variables. Uniform variables are intended to be used for data that may change relatively infrequently compared to per-vertex attributes. In fact, it is simply not possible to set per-vertex attributes with uniform variables. For example, uniform variables are well suited for the matrices used for modeling, viewing, and projective transformations.

Within a shader, uniform variables are read-only. Their values can only be changed from outside the shader, via the OpenGL API. However, they can be initialized within the shader by assigning to a constant value along with the declaration.

Uniform variables can appear in any shader within a shader program, and are always used as input variables. They can be declared in one or more shaders within a program, but if a variable with a given name is declared in more than one shader, its type must be the same in all shaders. In other words, the uniform variables are held in a shared uniform namespace for the entire shader program.

In this recipe, we'll draw the same triangle as in previous recipes in this article, however, this time we'll rotate the triangle using a uniform matrix variable.

OpenGL GLSL 4.0 Shading Language tutorial

Getting ready

We'll use the following vertex shader.

#version 400

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexColor;

out vec3 Color;

uniform mat4 RotationMatrix;

void main()
{
Color = VertexColor;
gl_Position = RotationMatrix * vec4(VertexPosition,1.0);
}

Note the variable RotationMatrix is declared using the uniform qualifier. We'll provide the data for this variable from the OpenGL program. The RotationMatrix is used to transform VertexPosition before assigning it to the default output position variable gl_Position.

We'll use the same fragment shader as in previous recipes.

#version 400

in vec3 Color;

layout (location = 0) out vec4 FragColor;

void main() {
FragColor = vec4(Color, 1.0);
}

Within the main OpenGL code, we determine the rotation matrix and send it to the shader's uniform variable. To create our rotation matrix, we'll use the GLM library (see: Using the GLM library for mathematics in this article). Within the main OpenGL code, add the following include statements:

#include <glm/glm.hpp>

using glm::mat4;
using glm::vec3;

#include <glm/gtc/matrix_transform.hpp>

We'll also assume that code has been written to compile and link the shaders, and to create the vertex array object for the color triangle. We'll assume that the handle to the vertex array object is vaoHandle, and the handle to the program object is programHandle.

How to do it...

Within the render method, use the following code.

glClear(GL_COLOR_BUFFER_BIT);

mat4 rotationMatrix = glm::rotate(mat4(1.0f), angle,
vec3(0.0f,0.0f,1.0f));

GLuint location =glGetUniformLocation(programHandle,
"RotationMatrix");

if( location >= 0 )
{
glUniformMatrix4fv(location, 1, GL_FALSE,
&rotationMatrix[0][0]);
}

glBindVertexArray(vaoHandle);
glDrawArrays(GL_TRIANGLES, 0, 3 );

How it works...

The steps involved with setting the value of a uniform variable include finding the location of the variable, and then assigning a value to that location using one of the glUniform functions.

In this example, we start by clearing the color buffer, then creating a rotation matrix using GLM. Next, we query for the location of the uniform variable by calling glGetUniformLocation. This function takes the handle to the shader program object and the name of the uniform variable, and returns its location. If the uniform variable is not an active uniform variable, the function returns -1.

We then assign a value to the uniform variable using glUniformMatrix4fv. The first argument is the uniform variable's location. The second is the number of matrices that are being assigned (the uniform variable could be an array). The third is a Boolean value indicating whether or not the matrix should be transposed when loaded into the uniform variable. With GLM matrices, a transpose is not required, so we use GL_FALSE here. If you were implementing the matrix using an array, and the data was in row-major order, you might need to use GL_TRUE for this argument. The last argument is a pointer to the data for the uniform variable.

There's more...

Of course uniform variables can be any valid GLSL type including complex types such as arrays or structures. OpenGL provides a glUniform function with the usual suffixes, appropriate for each type. For example, to assign to a variable of type vec3, one would use glUniform3f or glUniform3fv.

For arrays, we can use the functions ending in "v" to initialize multiple values within the array. Note that if it is desired, we can query for the location of a particular element of the uniform array using the [] operator. For example, to query for the location of the second element of MyArray we will query in the following way:

GLuint location =
glGetUniformLocation( programHandle, "MyArray[1]" );

For structures, the members of the structure must be initialized individually. As with arrays, one can query for the location of a member of a structure using something like the following:

GLuint location =
glGetUniformLocation( programHandle,
"MyMatrices.Rotation" );

Where the structure variable is MyMatrices and the member of the structure is Rotation.

Getting a list of active uniform variables

While it is a simple process to query for the location of an individual uniform variable, there may be instances where it can be useful to generate a list of all active uniform variables. For example, one might choose to create a set of variables to store the location of each uniform and assign their values after the program is linked. This would avoid the need to query for uniform locations when setting the value of the uniform variables, creating slightly more efficient code.

Getting ready

We'll start with a basic OpenGL program that compiles and links a shader program. You could use the shaders from the recipe Sending data to a shader using per-vertex attributes and vertex buffer objects. In the following example, we'll assume that the handle to the program is in a variable named programHandle.

How to do it...

After linking the shader program, use the following steps to print information about the active uniform variables:

  1. Retrieve the maximum length of the names of all of the active uniforms and the number of active uniforms using glGetProgramiv.

    GLint nUniforms, maxLen;

    glGetProgramiv( programHandle, GL_ACTIVE_UNIFORM_MAX_LENGTH,
    &maxLen);
    glGetProgramiv( programHandle, GL_ACTIVE_UNIFORMS,
    &nUniforms);

  2. Allocate space to store each uniform variable's name.

    GLchar * name = (GLchar *) malloc( maxLen );

  3. Retrieve and print information about each active uniform using glGetActiveUniform and glGetUniformLocation.

    GLint size, location;
    GLsizei written;
    GLenum type;
    printf(" Location | Name\n");
    printf("------------------------------------------------\n");
    for( int i = 0; i < nUniforms; ++i ) {
    glGetActiveUniform( programHandle, i, maxLen, &written,
    &size, &type, name );
    location = glGetUniformLocation(programHandle, name);
    printf(" %-8d | %s\n", location, name);
    }

    free(name);

How it works...

In step one above, we call the function glGetProgramiv to query for the maximum length of the uniform variable names (GL_ACTIVE_UNIFORM_MAX_LENGTH), and the number of active uniforms (GL_ACTIVE_UNIFORMS). The maximum length value includes the null terminating character, so in step 2 we allocate enough space to store a name of that length.

Next, we loop from zero to the number of uniforms minus one, and call glGetActiveUniform to retrieve information about each variable. Similar to glGetActiveAttrib, this function provides several pieces of information about the variable including its size, type, and name. We then query for the location of that uniform variable by calling glGetUniformLocation. It is quite often the case that the index used in the call to glGetActiveUniform is the same as the uniform's location, but we make the call just to be sure.

Finally, we print the name and location of the variable to standard out.

There's more...

As with vertex attributes, a uniform variable is not considered active unless it is determined by the GLSL linker that it will be used within the shader.

Note that one could also use the function glGetActiveUniformName instead of glGetActiveUniform. The former only provides the name, while the latter also provides the size and type.

Summary

In this article we covered setting up your OpenGL development environment to take advantage of the latest OpenGL and GLSL language features. Also we saw the basic techniques for communicating with shader programs.


Further resources on this subject:


About the Author :


David Wolff

David Wolff is an associate professor in the Computer Science and Computer Engineering Department at Pacific Lutheran University (PLU). He received a PhD in Physics and an MS in Computer Science from Oregon State University. He has been teaching computer graphics to undergraduates at PLU for over 10 years, using OpenGL.

Books From Packt


Blender 2.5 HOTSHOT
Blender 2.5 HOTSHOT

Blender 2.49 Scripting
Blender 2.49 Scripting

Away3D 3.6 Cookbook
Away3D 3.6 Cookbook

OpenSceneGraph 3.0: Beginner's Guide
OpenSceneGraph 3.0: Beginner's Guide

3D Graphics with XNA Game Studio 4.0
3D Graphics with XNA Game Studio 4.0

Panda3D 1.7 Game Developer's Cookbook
Panda3D 1.7 Game Developer's Cookbook

Papervision3D Essentials
Papervision3D Essentials

OGRE 3D 1.7 Beginner's Guide
OGRE 3D 1.7 Beginner's Guide


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
L
7
H
M
m
r
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software