OpenGL:Tutorials:Loading and using GLSL shaders

From GPWiki
Jump to: navigation, search

An OpenGL Shader Tutorial

This tutorial is intended to demonstrate the coding, loading and application of a OpenGL Shader program. Familiarity with OpenGL and its extension system is assumed. The code contained here is C++, OpenGL Version 1.4 with the use of ARB Extensions. For OpenGL Version 2.0 and higher, the ARB suffix on identifiers may be removed. The end result should look something like Media: Whiteshadedteapot.png.

Final Result

Writing a Simple Shader File

Before attempting to load a shader, it is beneficial to understand some basic shader writing concepts. Shader Files, in OpenGL, are text files. They can be edited with any text editor. There is no required extension, as long as the source data contained within is valid.

Trivial Vertex Shader

In order to demonstrate how to load and use shader programs, a trivial vertex shader is needed. This shader will be saved as "transform.vert".

void main()
{
    gl_Position = gl_ModelViewMatrix * gl_ProjectionMatrix * gl_Vertex;
}

Alternatively, the vertex shader can be expressed as

void main()
{
    gl_Position = ftransform();
}

ftransform() is a built in GLSL function, and will most likely be faster than straight multiplication. ftransform() is ensured to provide the same transformation as the fixed function pipeline, but is not guaranteed to be the same vertex as doing the ModelView * Projection * vertex calculations due to floating point errors, among other things, so it is best to be consistent in the method of transformation across multiple passes of shader effects.

All vertex shaders must have one and only one main function, which returns void. This means that the vertex shader does not return the result of its transformations. Instead, it must write the resulting, transformed vertex into gl_Position. The vertex shader must write something to gl_Position, or else the vertex shader will not be a valid vertex shader.

This shader takes the vertex as input, given as gl_Vertex, and transforms it by multiplying it with the model view matrix and projection matrix. This gives a properly transformed position, which then is written out to gl_Position.

Trivial Fragment Shader

Then, a trivial fragment shader. This file will be saved as "white.frag"

void main()
{
    gl_FragColor = vec4(1, 1, 1, 1)
}

The fragment shader must also have one and only one main function, which returns nothing. It does not have to write anything to any specific variable.

This shader sets the fragment color, named gl_FragColor, and sets it to {1, 1, 1, 1}, which corresponds to a pure white color. The fragment shader is not required to output anything, and can actually choose to discard fragments if so required.

Loading the Files

In order to compile and link the shaders, we first must load their data into the program.

//First, let us load the vertex shader.
std::fstream vertexShaderFile("transform.vert", std::ios::in);
std::string vertexShader;
 
if (vertexShaderFile.is_open())
{
	//This is to help store the file's buffer.
	std::stringstream buffer;
	//Here, get all of the file's data by streaming the file's stream into our buffer stream.
	buffer << vertexShaderFile.rdbuf();
	//Now, buffer contains the vertex shader's data
	vertexShader = buffer.str();
}
 
//Now load the fragment shader.
std::fstream fragmentShaderFile("white.frag", std::ios::in);
std::string fragmentShader;
 
if (fragmentShaderFile.is_open())
{
	//Use the same techniqiue to load the fragment shader.
	std::stringstream buffer;
	buffer << fragmentShaderFile.rdbuf();
	fragmentShader = buffer.str();
}

With the vertex and fragment shader data in vertexShader and fragmentShader, respectivly, creation of the shader objects is possible. In order to have OpenGL use these shaders, we must create shader objects.

Creating Shader Objects

OpenGL uses GLhandleARB to abstract a handle to a shader object. In order to create a valid shader handle, the program must either rely on OpenGL 2.0, or use extensions. Such implementation details are not covered here.

//Note: Creating a fragment shader is the same exact process, except that GL_FRAGMENT_SHADER is passed to 
//glCreateShaderObjectARB, and fragmentShader's code is passed to the source code call. For this reason,
//the code to load a fragment shader is omitted, and left as an exercize for the reader.
 
//Create a handle.
GLhandleARB vertexHandle;
 
//Obtain a valid handle to a vertex shader object.
vertexHandle = glCreateShaderObjectARB(GL_VERTEX_SHADER);
if (!vertexHandle)
{
	//We have failed creating the vertex shader object.
	std::cout<<"Failed creating vertex shader object";
}
 
//Now, compile the shader source. 
//Note that glShaderSource takes an array of chars. This is so that one can load multiple vertex shader files at once.
//This is similar in function to linking multiple C++ files together. Note also that there can only be one "void main" definition
//In all of the linked source files that are compiling with this funciton.
glShaderSourceARB(
	vertexHandle, //The handle to our shader
	1, //The number of files.
	&vertexShader.c_str(), //An array of const char * data, which represents the source code of theshaders
	NULL); //An array of string lengths. For have null terminated strings, pass NULL.
 
//Attempt to compile the shader.
glCompileShaderARB(vertexShader);
 
//Error checking.
int result;
glGetObjectParameterivARB(vertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &result);
if (!result)
{
	//We failed to compile.
	std::cout<<"Vertex shader failed compilation.\n";
	//Attempt to get the length of our error log.
	int length;
	glGetObjectParameterivARB(vertexShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &length);
	//Create a buffer.
	std::vector<char> buffer;
	buffer.resize(length);
 
	//Used to get the final length of the log.
	int final;
	glGetInfoLogARB(vertexShader, length, &final, &buffer[0]);
	//Convert our buffer into a string.
	std::string message(&buffer[0], length);
	std::cout<<message;
	if (final > length)
	{
		//The buffer does not contain all the shader log information.
		std::cout<<"Shader Log contained more information!"
	}
}

Creating the Program

Though all the shaders have compiled, before use, the shader objects must be linked into a shader program.

GLhandleARB program;
 
//Create a program handle.
program = glCreateProgramObjectARB();
 
//Attach the shaders. Here, assume that fragmentHandle is a handle to a fragment shader object, 
//and that vertexHandle is a handle to a vertex shader object.
glAttachObjectARB(program, fragmentHandle);
glAttachObjectARB(program, vertexHandle);
 
//Link the program.
glLinkProgramARB(program);
 
//Now the program can be used.

Using the shader

In order to use a shader program, the program must call glUseProgramObjectARB(GLhandleARB), where the handle is to a valid program object. The program can revert back to the fixed function pipeline by calling glUseProgramObjectARB(0).