Getting Started with GLSL

Exclusive offer: get 50% off this eBook here
OpenGL 4 Shading Language Cookbook, Second Edition

OpenGL 4 Shading Language Cookbook, Second Edition — Save 50%

Over 70 recipes demonstrating simple and advanced techniques for producing high-quality, real-time 3D graphics using OpenGL and GLSL 4.x with this book and ebook

$32.99    $16.50
by David Wolff | December 2013 | Cookbooks Games Open Source

In this article, by David Wolff, author of OpenGL 4 Shading Language Cookbook Second Edition, we will cover the steps needed to compile, link, and use GLSL shaders within an OpenGL program. It also covers how to send data to shaders using attributes and uniform variables, and the use of the GLM library for mathematics support. Every modern OpenGL program today requires a function loader. We will also cover the use of GLLoadGen, a relatively new and easy-to-use OpenGL loader generator.

(For more resources related to this topic, see here.)

In this article, we will cover the following recipes:

  • Using a function loader to access the latest OpenGL functionality
  • Using GLM for mathematics
  • Determining the GLSL and OpenGL version
  • Compiling a shader
  • Linking a shader program

The OpenGL Shading Language(GLSL) Version 4 brings unprecedented power and flexibility to programmers interested in creating modern, interactive, and 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. Of course, the first step towards using GLSL 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 provide some tips and techniques for getting a basic program up and running. First, let's start with some background.

The OpenGL Shading Language

The GLSL is now a fundamental and integral part of the OpenGL API. Going forward, every program written using the OpenGL API will internally utilize one or several GLSL programs. These "mini-programs" are often referred to as shader programs. A shader program usually consists of several components called shaders. Each shader executes within a different section of the OpenGL pipeline. Each shader runs on the GPU, and as the name implies, (typically) implement the algorithms related to the lighting and shading effects of an image. However, shaders are capable of doing much more than just implementing a shading algorithm. They are also capable of performing animation, tessellation, or 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. With compute shaders, introduced in OpenGL 4.3, we can now do GPGPU within OpenGL.

Shader programs are designed for direct execution on the GPU and are executed 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 series cards (source: http://en.wikipedia.org/wiki/Comparison_of_Nvidia_graphics_processing_units).

Model

Unified Shader Processors

GeForce GTS 450

192

GeForce GTX 480

480

GeForce GTX 780

2304

Shader programs are intended to replace parts of the OpenGL architecture referred to as the fixed-function pipeline. Prior to OpenGL Version 2.0, the shading algorithm was "hard-coded" into the pipeline and had only limited configurability. This default lighting/shading algorithm was a core part of the fixed-function 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 will help 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 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/glEndwas 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 that is writing code intended to be used with 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 forward compatible context, which is distinguished slightly from the concept of a core/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. Some window APIs provide the ability to select forward compatible status along with the profile.

The steps for selecting a core or compatibility profile are window system API dependent. For example, in GLFW, one can select a forward compatible, 4.3 core profile using the following code:

glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 ); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow *window = glfwCreateWindow(640, 480, "Title", NULL, NULL);

All programs in this article are designed to be compatible with a forward compatible OpenGL 4.3 core profile.

Using a function loader 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 is not difficult, but requires somewhat tedious work, and has a tendency to clutter your code. Additionally, Windows typically comes with a standard OpenGL gl.h file that also conforms to OpenGL 1.1. The OpenGL wiki states that Microsoft has no plans to ever update the gl.hand opengl32.lib that come with their compilers. Thankfully, others have provided libraries that manage all of this for us by transparently providing the needed function pointers, while also exposing the needed functionality in header files. There are several libraries available that provide this kind of support. One of the oldest and most common is GLEW (OpenGL Extension Wrangler). However, there are a few serious issues with GLEW that might make it less desirable, and insufficient for my purposes when writing this article. First, at time of writing, it doesn't yet support core profiles properly, and for this article, I want to focus only on the latest non-deprecated functionality. Second, it provides one large header file that includes everything from all versions of OpenGL. It might be preferable to have a more streamlined header file that only includes functions that we might use. Finally, GLEW is distributed as a library that needs to be compiled separately and linked into our project. It is often preferable to have a loader that can be included into a project simply by adding the source files and compiling them directly into our executable, avoiding the need to support another link-time dependency.

In this recipe, we'll use the OpenGL Loader Generator (GLLoadGen), available from https://bitbucket.org/alfonse/glloadgen/wiki/Home. This very flexible and efficient library solves all three of the issues described in the previous paragraph. It supports core profiles and it can generate a header that includes only the needed functionality, and also generates just a couple of files (a source file and a header) that we can add directly into our project.

Getting ready

To use GLLoadGen, you'll need Lua. Lua is a lightweight embeddable scripting language that is available for nearly all platforms. Binaries are available at http://luabinaries.sourceforge.net, and a fully packaged install for Windows (LuaForWindows) is available at:

https://code.google.com/p/luaforwindows

Download the GLLoadGen distribution from: https://bitbucket.org/alfonse/glloadgen/downloads. The distribution is compressed using 7zip, which is not widely installed, so you may need to install a 7zip utility, available at http://7-zip.org/. Extract the distribution to a convenient location on your hard drive. Since GLLoadGen is written in Lua, there's nothing to compile, once the distribution is uncompressed, you're ready to go.

How to do it...

The first step is to generate the header and source files for the OpenGL version and profile of choice. For this example, we'll generate files for an OpenGL 4.3 core profile. We can then copy the files into our project and compile them directly alongside our code:

  1. To generate the header and source files, navigate to the GLLoadGen distribution directory, and run GLLoadGen with the following arguments:

    lua LoadGen.lua -style=pointer_c -spec=gl -version=4.3 \-profile=core core_4_3

  2. The previous step should generate two files: gl_core_4_3.c and gl_core_4_3.h. Move these files into your project and include gl_core_4_3.c in your build. Within your program code, you can include the gl_core_4_3.h file whenever you need access to the OpenGL functions. However, in order to initialize the function pointers, you need to make sure to call a function to do so. The needed function is called ogl_LoadFunctions. Somewhere just after the GL context is created (typically in an initialization function), and before any OpenGL functions are called, use the following code:

    int loaded = ogl_LoadFunctions(); if(loaded == ogl_LOAD_FAILED) { //Destroy the context and abort return; } int num_failed = loaded - ogl_LOAD_SUCCEEDED; printf("Number of functions that failed to load: %i.\n", num_failed);

That's all there is to it!

How it works...

The lua command in step 1 generates a pair of files, that is; a header and a source file. The header provides prototypes for all of the selected OpenGL functions and redefines them as function pointers, and defines all of the OpenGL constants as well. The source file provides initialization code for the function pointers as well as some other utility functions. We can include the gl_core_4_3.h header file wherever we need prototypes for OpenGL functions, so all function entry points are available at compile time. At run time, the ogl_LoadFunctions()function will initialize all available function pointers. If some functions fail to load, the number of failures can be determined by the subtraction operation shown in step 2. If a function is not available in the selected OpenGL version, the code may not compile, because only function prototypes for the selected OpenGL version and profile are available in the header (depending on how it was generated).

The command line arguments available to GLLoadGen are fully documented here: https://bitbucket.org/alfonse/glloadgen/wiki/Command_Line_Options. The previous example shows the most commonly used setup, but there's a good amount of flexibility built into this tool.

Now that we have generated this source/header pair, we no longer have any dependency on GLLoadGen and our program can be compiled without it. This is a significant advantage over tools such as GLEW.

There's more...

GLLoadGen includes a few additional features that are quite useful. We can generate more C++ friendly code, manage extensions, and generate files that work without the need to call an initialization function.

Generating a C++ loader

GLLoadGen supports generation of C++ header/source files as well. This can be selected via the -style parameter. For example, to generate C++ files, use -style=pointer_cpp as in the following example:

lua LoadGen.lua -style=pointer_cpp -spec=gl -version=4.3
\-profile=core core_4_3

This will generate gl_core_4_3.cpp and gl_core_4_3.hpp. This places all OpenGL functions and constants within the gl::namespace, and removes their gl(or GL) prefix. For example, to call the function glBufferData, you might use the following syntax.

gl::BufferData(gl::ARRAY_BUFFER, size, data, gl::STATIC_DRAW);

Loading the function pointers is also slightly different. The return value is an object rather than just a simple integer and LoadFunctions is in the gl::sys namespace.

gl::exts::LoadTest didLoad = gl::sys::LoadFunctions(); if(!didLoad) { // Clean up (destroy the context) and abort. return; } printf("Number of functions that failed to load: %i.\n", didLoad.GetNumMissing());

-load styles

GLLoadGen supports the automatic initialization of function pointers. This can be selected using the noload_c or noload_cpp options for the style parameter. With these styles, there is no need to call the initialization function ogl_LoadFunctions. The pointers are loaded automatically, the first time a function is called. This can be convenient, but there's very little overhead to loading them all at initialization.

Using Extensions

GLLoadGen does not automatically support extensions. Instead, you need to ask for them with command line parameters. For example, to request ARB_texture_view and ARB_vertex_attrib_binding extensions, you might use the following command.

lua LoadGen.lua -style=pointer_c -spec=gl -version=3.3 \-profile
=core core_3_3 \-exts ARB_texture_view ARB_vertex_attrib_binding

The -exts parameter is a space-separated list of extensions. GLLoadGen also provides the ability to load a list of extensions from a file (via the -extfile parameter) and provides some common extension files on the website.

You can also use GLLoadGen to check for the existence of an extension at run-time. For details, see the GLLoadGen wiki.

See also

  • GLEW, an older, and more common loader and extension manager, available from glew.sourceforge.net.

Using GLM 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 recent versions of core OpenGL 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 some might 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 GLM very easy to use and familiar. Additionally, it provides extensions that include functionality similar to some of the much missed OpenGL functions such as glOrtho, glRotate, or gluLookAt.

Getting ready

Since GLM is a header-only library, installation is simple. Download the latest GLM distribution from http://glm.g-truc.net. Then, unzip the archive file, and copy the glm directory contained inside to anywhere in your compiler's include path.

How to do it...

To use the GLM libraries, it is simply a matter of including the core header file, and headers for any extensions. For this example, we'll include the matrix transform extension as follows:

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

Then the GLM classes are 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(1.0f); // The identity matrix 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 previous example first creates a vec4(four coordinate vector) representing a position. Then it creates a 4 x 4 view matrix by using the glm::lookAt function. This works in a similar fashion to the old gluLookAt function. Here, 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 model matrix by first storing the identity matrix in the variable model(via the single argument constructor), 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 (on the right) 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. Since before this statement, model is the identity matrix, the net result is that model becomes a rotation matrix of 90 degrees around the y-axis.

Finally, we create our modelview 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.

There's more...

It is not recommended to import all of the GLM namespace by using the following command:

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:

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

See also

  • The Qt SDK includes many classes for vector/matrix mathematics, and is another good option if you're already using Qt
  • The GLM website http://glm.g-truc.net has additional documentation and examples
OpenGL 4 Shading Language Cookbook, Second Edition Over 70 recipes demonstrating simple and advanced techniques for producing high-quality, real-time 3D graphics using OpenGL and GLSL 4.x with this book and ebook
Published: December 2013
eBook Price: $32.99
Book Price: $54.99
See more
Select your format and quantity:

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 as follows 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. It is important to note that the use of glGetIntegerv to query for version information requires 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 the 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 in the context of this article 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 previous 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: glGetStringi(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 ) );

See also

  • The GLLoadGen tool has additional support for querying version and extension information. Refer to the Using a function loader to access the latest OpenGL functionality recipe and the GLLoadGen website.

Compiling a shader

To get started, we need to know how to compile our GLSL shaders. The GLSL compiler is built right 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 precompiling 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 roughly represented by the following diagram:

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 430 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 a window toolkit that supports OpenGL. Examples of cross-platform toolkits include GLFW, 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. Note that you'll also need to load the OpenGL function pointers (refer to the Using a function loader to access the latest OpenGL functionality recipe).

Finally, 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(EXIT_FAILURE); }

  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 = new char[logLen]; GLsizei written; glGetShaderInfoLog(vertShader, logLen, &written, log); fprintf(stderr, "Shader log:\n%s", log); delete [] log; } }

How it works...

The first step is to create the shader object using the glCreateShader function. 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, GL_TESS_CONTROL_SHADER, or (as of version 4.3) GL_COMPUTE_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 (as opposed to just a single one) in order to support the option of compiling multiple sources (files, strings) 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 the previous code, 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. Note that this includes 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 previous example only demonstrated compiling a vertex shader. There are several other types of shaders including fragment, geometry, and tessellation shaders. The technique for compiling is nearly identical for each shader type. The only significant difference is the argument to glCreateShader.

It is also important to note that 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 (refer to the Linking a shader program recipe), it will not be immediately deleted, but flagged for deletion when it is detached from the program object.

See also

  • The Linking a shader program recipe

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 input variables from one shader to output variables of another, and making the connections between the 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).

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 430 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, Compiling a shader.

How to do it...

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

  1. Create the program object using the following code:

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

  2. Attach the shaders to the program object as follows:

    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 = new char[logLen]; GLsizei written; glGetProgramInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log: \n%s", log); delete [] 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 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 previous 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 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.

There's more...

You can use 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 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.

Also, 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.

Summary

In this article, we learned about the steps needed to compile, link, and use GLSL shaders within an OpenGL program. We also learned how to send data to shaders using attributes and uniform variables, and the use of the GLM library for mathematics support.

Resources for Article:


Further resources on this subject:


OpenGL 4 Shading Language Cookbook, Second Edition Over 70 recipes demonstrating simple and advanced techniques for producing high-quality, real-time 3D graphics using OpenGL and GLSL 4.x with this book and ebook
Published: December 2013
eBook Price: $32.99
Book Price: $54.99
See more
Select your format and quantity:

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


 Android NDK Beginner’s Guide
Android NDK Beginner’s Guide

OpenGL Development Cookbook
OpenGL Development Cookbook

  OpenGL 4.0 Shading Language Cookbook
OpenGL 4.0 Shading Language Cookbook

Learning Game Physics with Bullet Physics and OpenGL
Learning Game Physics with Bullet Physics and OpenGL

 Android Native Development Kit Cookbook
Android Native Development Kit Cookbook

 jMonkeyEngine 3.0 Beginner's Guide
jMonkeyEngine 3.0 Beginner's Guide

 Learning Raphaël JS Vector Graphics
Learning Raphaël JS Vector Graphics

 Augmented Reality with Kinect
Augmented Reality with Kinect


Your rating: None Average: 1.3 (3 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
B
A
e
s
H
B
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