Loading and using GLSL shaders

Shader programs must be compiled before use. Fortunately, OpenGL offers an interface to load shader programs in text form. The shader source code uses a syntax similar to the C code with several limitations. For instance, you can't perform recursive function calls. After compilation, you can check whether there were any errors in the process.

Shaders can use input values from your application. These input values are called uniforms. You can use these values in any part of the rendering pipeline, which consists of several shader program stages:

  • Vertex shader: This performs operations on vertex attributes: vertex color, position, normal vector and many others
  • Tessellation control shader: This controls tessellation amount on polygons
  • Tessellation evaluation shader: This computes the interpolated vertex positions after tessellation
  • Geometry shader: This performs per vertex operations on polygons
  • Fragment shader: This operates on fragments after the rasterization process; the results are stored into the frame buffer, the depth buffer, or the stencil buffer

Only vertex and fragment shaders are mandatory for basic rendering of operations. The following diagram shows the complete rendering pipeline:

Loading and using GLSL shaders

The red parts are mandatory shaders; the optional shaders are in orange. Blue and white parts present steps that aren't fully controllable by the user.

Getting ready

Before using GLSL shaders, you should always check whether the current graphic card supports them. For this, you can use the gl.IsSupported function. It accepts one string parameter that consists of the OpenGL extension names and version names. For example, the following code tests whether there is support for OpenGL 3.0, vertex and fragment shaders in the current system:

assert(gl.IsSupported("GL_VERSION_3_0 GL_ARB_vertex_shader GL_ARB_fragment_shader"))

Each string part is delimited with one space and always starts with the GL_ prefix. After this check, you can be confident using GLSL shaders or any other extension. Otherwise, you might end up producing memory access violation or segmentation fault, as the required functions aren't available.

A list of valid extension names can be found at http://glew.sourceforge.net/glew.html.

You'll need the valid shader source code. You can use the following example of the vertex shader source code:

local shader_source = [[
#version 330 //use GLSL specification version 3.3
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec4 VertexColor;
layout (location = 2) in vec2 VertexTexCoord;

out vec4 Color;
out vec2 TexCoord;

void main(){
  gl_Position = vec4(VertexPosition.xyz, 1.0);
  Color = vec4(VertexColor.rgba);
  TexCoord = vec2(VertexTexCoord.xy);
}
]]

This vertex shader uses GLSL version 3.3 and does basic preparation of vertex attributes for the next stage.

How to do it…

GLSL shaders and programs use special OpenGL objects. These must be created before using. You can create the shader object with the gl.CreateShader function. It accepts the shader stage identifier and results in a numerical object identifier. Let's assume that this shader object identifier is stored in the shader_object variable with the following code:

local shader_stage = gl_enum.GL_VERTEX_SHADER
local shader_object = gl.CreateShader(shader_stage)

Now you can use this shader object to load your shader's source code:

gl.ShaderSource(shader_object, shader_source)

After this step, you can compile the shader with the gl.CompileShader function. You can check the shader compilation status with this code:

local compilation_status = ""
local status = gl.GetShaderiv(shader_object, gl_enum.GL_COMPILE_STATUS)
if status == gl_enum.GL_FALSE then
  compilation_status = gl.GetShaderInfoLog(shader_object)
end

The status variable contains a numerical value, which is set to GL_TRUE if the compilation is successful. Otherwise, it's set to GL_FALSE and you can obtain the textual error message with the gl.GetShaderInfoLog function.

After successful compilation, you can link shader objects into shader programs, but first you must create one with the gl.CreateProgram function. It returns a numerical identifier for the shader program. Let's store this value into the shader_program value as shown in the following code:

local shader_program = gl.CreateProgram()

Now you can attach the shader objects into the shader program with the following command:

gl.AttachShader(shader_program, shader_object)

With this step done, you can finally link shaders into the program with the command:

gl.LinkProgram(shader_program)

You should always check for the last linking operation status with the following code:

local link_status = ""
local status = gl.GetProgramiv(shader_program, gl_enum.GL_LINK_STATUS)
if status == gl_enum.GL_FALSE then
  link_status = gl.GetProgramInfoLog(shader_program)
end

After the shader program is linked, the shader objects are not needed anymore and you can safely delete them with:

gl.DeleteShader(shader_object)

The shader program can be used with the following code:

gl.UseProgram(shader_program)

If there's no need for the shader program, you can delete it with the following code:

gl.DeleteProgram(shader_program)

How it works…

The GLSL shader loading process consists of two steps. The first step is the shader stage compilation into the shader object. It works in a similar fashion as in a C compiler, where the source code is compiled into binary object files. The compilation is followed by the linking process. Shader objects are linked into one shader program. This presents the final result of the GLSL shader preparation process. Of course, your application might contain more than one shader program and you can switch between them. On some rare occasions, it's better to merge more shaders into one and separate them with conditional blocks. This approach introduces additional overhead to the shader code especially in fragment shader, but this might be better than switching shaders. There's no general rule for this, so you'll need to experiment.

When you're writing your own shaders, you should always take into account the amount of shader runs for each element. For instance, the vertex shader is used on every vertex, whereas the fragment shader is almost always used many more times as it operates on fragment elements. You can think of fragments as pixels on the frame buffer. So, whenever you're writing a program for the fragment shader, try to think about implementing it in the vertex shader first. This way you can further optimize your shaders, especially if you intend to use them in an application on mobile devices.

See also

  • The Using uniform variables with shaders recipe
  • The Writing a vertex shader recipe
  • The Writing a fragment (pixel) shader recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.136