Book Image

OpenGL Development Cookbook

By : Muhammad Mobeen Movania
Book Image

OpenGL Development Cookbook

By: Muhammad Mobeen Movania

Overview of this book

OpenGL is the leading cross-language, multi-platform API used by masses of modern games and applications in a vast array of different sectors. Developing graphics with OpenGL lets you harness the increasing power of GPUs and really take your visuals to the next level. OpenGL Development Cookbook is your guide to graphical programming techniques to implement 3D mesh formats and skeletal animation to learn and understand OpenGL. OpenGL Development Cookbook introduces you to the modern OpenGL. Beginning with vertex-based deformations, common mesh formats, and skeletal animation with GPU skinning, and going on to demonstrate different shader stages in the graphics pipeline. OpenGL Development Cookbook focuses on providing you with practical examples on complex topics, such as variance shadow mapping, GPU-based paths, and ray tracing. By the end you will be familiar with the latest advanced GPU-based volume rendering techniques.
Table of Contents (15 chapters)
OpenGL Development Cookbook
About the Author
About the Reviewers

Designing a GLSL shader class

We will now have a look at how to set up shaders. Shaders are special programs that are run on the GPU. There are different shaders for controlling different stages of the programmable graphics pipeline. In the modern GPU, these include the vertex shader (which is responsible for calculating the clip-space position of a vertex), the tessellation control shader (which is responsible for determining the amount of tessellation of a given patch), the tessellation evaluation shader (which computes the interpolated positions and other attributes on the tessellation result), the geometry shader (which processes primitives and can add additional primitives and vertices if needed), and the fragment shader (which converts a rasterized fragment into a colored pixel and a depth). The modern GPU pipeline highlighting the different shader stages is shown in the following figure.

Note that the tessellation control/evaluation shaders are only available in the hardware supporting OpenGL v4.0 and above. Since the steps involved in shader handling as well as compiling and attaching shaders for use in OpenGL applications are similar, we wrap these steps in a simple class we call GLSLShader.

Getting ready

The GLSLShader class is defined in the GLSLShader.[h/cpp] files. We first declare the constructor and destructor which initialize the member variables. The next three functions, LoadFromString, LoadFromFile, and CreateAndLinkProgram handle the shader compilation, linking, and program creation. The next two functions, Use and UnUse functions bind and unbind the program. Two std::map datastructures are used. They store the attribute's/uniform's name as the key and its location as the value. This is done to remove the redundant call to get the attribute's/uniform's location each frame or when the location is required to access the attribute/uniform. The next two functions, AddAttribute and AddUniform add the locations of the attribute and uniforms into their respective std::map (_attributeList and _uniformLocationList).

class GLSLShader
  void LoadFromString(GLenum whichShader, const string& source);
  void LoadFromFile(GLenum whichShader, const string& filename);
  void CreateAndLinkProgram();
  void Use();
  void UnUse();
  void AddAttribute(const string& attribute);
  void AddUniform(const string& uniform);
  GLuint operator[](const string& attribute);
  GLuint operator()(const string& uniform);
  void DeleteShaderProgram();

  GLuint  _program;
  int _totalShaders;
  GLuint _shaders[3];
  map<string,GLuint> _attributeList;
  map<string,GLuint> _uniformLocationList;

To make it convenient to access the attribute and uniform locations from their maps , we declare the two indexers. For attributes, we overload the square brackets ([]) whereas for uniforms, we overload the parenthesis operation (). Finally, we define a function DeleteShaderProgram for deletion of the shader program object. Following the function declarations are the member fields.

How to do it…

In a typical shader application, the usage of the GLSLShader object is as follows:

  1. Create the GLSLShader object either on stack (for example, GLSLShader shader;) or on the heap (for example, GLSLShader* shader=new GLSLShader();)

  2. Call LoadFromFile on the GLSLShader object reference

  3. Call CreateAndLinkProgram on the GLSLShader object reference

  4. Call Use on the GLSLShader object reference to bind the shader object

  5. Call AddAttribute/AddUniform to store locations of all of the shader's attributes and uniforms respectively

  6. Call UnUse on the GLSLShader object reference to unbind the shader object

Note that the above steps are required at initialization only. We can set the values of the uniforms that remain constant throughout the execution of the application in the Use/UnUse block given above.

At the rendering step, we access uniform(s), if we have uniforms that change each frame (for example, the modelview matrices). We first bind the shader by calling the GLSLShader::Use function. We then set the uniform by calling the glUniform{*} function, invoke the rendering by calling the glDraw{*} function, and then unbind the shader (GLSLShader::UnUse). Note that the glDraw{*} call passes the attributes to the GPU.

How it works…

In a typical OpenGL shader application, the shader specific functions and their sequence of execution are as follows:


Execution of the above four functions creates a shader object. After the shader object is created, a shader program object is created using the following set of functions in the following sequence:



Note that after the shader program has been linked, we can safely delete the shader object.

There's more…

In the GLSLShader class, the first four steps are handled in the LoadFromString function and the later four steps are handled by the CreateAndLinkProgram member function. After the shader program object has been created, we can set the program for execution on the GPU. This process is called shader binding. This is carried out by the glUseProgram function which is called through the Use/UnUse functions in the GLSLShader class.

To enable communication between the application and the shader, there are two different kinds of fields available in the shader. The first are the attributes which may change during shader execution across different shader stages. All per-vertex attributes fall in this category. The second are the uniforms which remain constant throughout the shader execution. Typical examples include the modelview matrix and the texture samplers.

In order to communicate with the shader program, the application must obtain the location of an attribute/uniform after the shader program is bound. The location identifies the attribute/uniform. In the GLSLShader class, for convenience, we store the locations of attributes and uniforms in two separate std::map objects.

For accessing any attribute/uniform location, we provide an indexer in the GLSLShader class. In cases where there is an error in the compilation or linking stage, the shader log is printed to the console. Say for example, our GLSLshader object is called shader and our shader contains a uniform called MVP. We can first add it to the map of GLSLShader by calling shader.AddUniform("MVP"). This function adds the uniform's location to the map. Then when we want to access the uniform, we directly call shader("MVP") and it returns the location of our uniform.