Book Image

OpenGL 4 Shading Language Cookbook - Third Edition

By : David Wolff
Book Image

OpenGL 4 Shading Language Cookbook - Third Edition

By: David Wolff

Overview of this book

OpenGL 4 Shading Language Cookbook, Third Edition provides easy-to-follow recipes that first walk you through the theory and background behind each technique, and then proceed to showcase and explain the GLSL and OpenGL code needed to implement them. The book begins by familiarizing you with beginner-level topics such as compiling and linking shader programs, saving and loading shader binaries (including SPIR-V), and using an OpenGL function loader library. We then proceed to cover basic lighting and shading effects. After that, you'll learn to use textures, produce shadows, and use geometry and tessellation shaders. Topics such as particle systems, screen-space ambient occlusion, deferred rendering, depth-based tessellation, and physically based rendering will help you tackle advanced topics. OpenGL 4 Shading Language Cookbook, Third Edition also covers advanced topics such as shadow techniques (including the two of the most common techniques: shadow maps and shadow volumes). You will learn how to use noise in shaders and how to use compute shaders. The book provides examples of modern shading techniques that can be used as a starting point for programmers to expand upon to produce modern, interactive, 3D computer-graphics applications.
Table of Contents (17 chapters)
Title Page
Packt Upsell
Contributors
Preface
Index

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 vertShader and fragShader variables.

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

#version 460 
 
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, perform the following steps:

  1. Create the program object using the following code:
GLuint programHandle = glCreateProgram(); 
if( 0 == programHandle ) 
{ 
  std::cerr << "Error creating program object." << std::endl; 
  exit(EXIT_FAILURE); 
} 
  1. Attach the shaders to the program object as follows:
glAttachShader( programHandle, vertShader ); 
glAttachShader( programHandle, fragShader ); 
  1. Link the program:
glLinkProgram( programHandle );

 

 

  1. Verify the link status:
GLint status; 
glGetProgramiv( programHandle, GL_LINK_STATUS, &status ); 
if( GL_FALSE == status ) {
  std::cerr << "Failed to link shader program!" << std::endl;
  GLint logLen; 
  glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen); 
  if( logLen > 0 ) { 
    std::string(logLen, ' ');
    GLsizei written;
    glGetProgramInfoLog(programHandle, logLen, &written, &log[0]); 
    std::cerr << "Program log: " << std::endl << log;
  } 
} 
  1. If linking is successful, we can install the program into the OpenGL pipeline with glUseProgram:
else
  glUseProgram( programHandle );

Regardless of whether the link was successful, it is a good idea to clean up our shader objects. Once the program is linked, they are not needed anymore:

// Detach and delete shader objects
glDetachShader(programHandle, vertShader);
glDetachShader(programHandle, fragShader);
glDeleteShader(vertShader);
glDeleteShader(fragShader);

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 GL_INFO_LOG_LENGTH parameter. 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.

It is a good idea to detach and delete the shader object, regardless of whether the link is successful. However, if the shader objects might be needed to link another program, you should detach it from this program and skip deletion until later.

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:

Note

For details on how to render the triangle, see Chapter 2, Working with GLSL Programs.

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.

Shader input/output variables

You may have noticed that the Color variable is used to send data from the vertex shader to the fragment shader. There is an output variable (out vec3) in the vertex shader and an input variable (in vec3) in the fragment shader, both with the same name. The value that the fragment shader receives is a value that is interpolated from the values of the corresponding output variable for each of the vertices (hence the blended colors in the earlier image). This interpolation is automatically done by hardware rasterizer before the execution of the fragment stage. 

When linking a shader program, OpenGL makes the connections between input and output variables in the vertex and fragment shaders (among other things). If a vertex shader's output variable has the same name and type as a fragment shader's input variable, OpenGL will automatically link them together.

It is possible to connect (link) variables that do not have the same name or type by using layout qualifiers. With a layout qualifier, we can specify the location for each variable specifically. For example, suppose that I used this set of output variables in my vertex shader:

layout (location=0) out vec4 VertColor;
layout (location=1) out vec3 VertNormal;

I could use these variables in the fragment shader:

layout (location=0) in vec3 Color;
layout (location=1) in vec3 Normal;

 

 

Despite the fact that these have different names (and for Color, types), they will be connected by the linker when the program is linked due to the fact that they are assigned the same locations. In this example, VertColor will be linked to Color, and VertNormal will be linked to Normal. This makes things more convenient. We're not required to use the same names for input/output variables, which gives us the flexibility to use names that might be more descriptive in each shader stage. More importantly, it is part of a larger framework, called separate shader objects. A full example of separate shader objects can be found in the Using program pipelines recipe.

Note

In fact, this use of layout qualifiers to specify variable locations is required when compiling to SPIR-V (see the Loading an SPIR-V shader program recipe).

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. Therefore, as mentioned before, it is a good idea to detach and delete them immediately, as soon as the program is linked, to avoid accidentally leaking shader objects.

See also

  • The chapter01/scenebasic.cpp file in the example code
  • The Compiling a shader recipe
  • The Using program pipelines recipe
  • The Loading an SPIR-V shader program recipe