Schabby's Blog
OpenGL, Java, Cassandra and other stuff that totally makes the world go round

In this tutorial, we will discuss how to load a shader in OpenGL.

In order to load a shader in OpenGL, you basically need to do the following:

  1. Tell OpenGL to create a program.
  2. load and compile vertex shader
  3. load and compile fragment shader
  4. attach vertex and fragment shader to program
  5. link program

Now let us see how this looks in code.

int programId = glCreateProgram();

We first ask OpenGL to create a so-called program for us. The method glCreateProgram() does exactly that and returns a handle that we can later use to reference and use the program.

Then we are loading the actual shaders, ie. the vertex shader and the fragment shader. The routine for both shader types is very similar such that it makes sense to wrape it in a method. We start with the fragment shader.

int handle = glCreateShader(GL_VERTEX_SHADER)

This creates a handle for a shader of type GL_VERTEX_SHADER. The handle used as a reference.

glShaderSource(handle, code);

glShaderSource uploads the source code of the shader into the OpenGL driver. Note that code is a string containing the source code and we omit how we actually fill the string. In most cases, the shader is loaded from a text file.

glCompileShader(handle);

Then we compile the shader source. This is done within OpenGL. Yes, OpenGL (or rather it's platform dependent driver) contains an entire compiler and linker. If the compilation stage is done (without errors), we can ...

glAttachShader(programId, handle);

... attach the compiled shader to the program.

Until now, we have only uploaded our vertex shader, but not the fragment shader. Since uploading and compiling the fragment shader is very similar to the vertex shader, we rush through it without much explaining.

First, ask OpenGL for the shader handle. ...

int handle = glCreateShader(GL_FRAGMENT_SHADER)

then we upload the fragment shader source (eg. source from file), ...

glShaderSource(handle, code);

and compile...

glCompileShader(handle);

and attach it to the program.

glAttachShader(programId, handle);

ok, good. We now got the compiled vertex and fragment shader attached to the program. The next and final step is linking them together:

glLinkProgram(programId);

In larger games, it happes that you need several shaders to draw different aspects of the game. For example you may have a different shader for water, terrain and units. To switch between shader programs, you need to tell OpenGL which program to use.

glUseProgram(programId);

But please be aware that switching shaders is usually computationally expensive, because OpenGL needs to flush caches and synchronize the rendering pipeline (modern graphics chips try to process as much as possible in parallel, that's where the speed comes from).

Source Code

First off, you need two text files where your shaders go in. Put them in your classpath (for example in the package de/schabby/tutorial/loadingshader2d/).

The first file simple.vertex contains the vertex shader:

// VERTEX SHADER
 
// input a 2-dimensional float array
in vec2 vertexPosition;
 
void main()
{
    gl_Position = vec4(vertexPosition, 0, 1.0);
}

The second file simple.fragment contains the fragment shader:

// FRAGEMENT SHADER
 
// specify version
#version 400
 
// output color for this fragment (pixel)
out vec4 FragColor;
 
void main()
{
	// assign vertex color to pixel color
    FragColor = vec4(1.0, 0.3, 1.0, 1.0);
}

In order to resolve the dependencies necessary for this tutorial, please check out my former tutorial on how to set up LWJGL.

package de.schabby.tutorial.loadingshader2d;
 
import static org.lwjgl.opengl.GL20.*;
 
import java.io.BufferedReader;
import java.io.FileReader;
 
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.Util;
 
import de.schabby.tutorial.settinguplwjgl.BasicLwjglExample;
 
public class Shader2dExample extends BasicLwjglExample
{
 
	public void run()
	{
		// create the shader program. If OK, create vertex and fragment shaders
		int programId = glCreateProgram();
 
		// load and compile the two shaders. The loading routines for vertex and 
		// fragment shaders are very similar, that's why it is handy to re-use the
		// same code for loading and compiling.
		int vertShader = loadAndCompileShader("src/de/schabby/tutorial/loadingshader2d/simple.vertex", GL_VERTEX_SHADER);
		int fragShader = loadAndCompileShader("src/de/schabby/tutorial/loadingshader2d/simple.fragment", GL_FRAGMENT_SHADER);
 
		// attach the compiled shaders to the program.
		glAttachShader(programId, vertShader);
		glAttachShader(programId, fragShader);
 
		// now link the program
		glLinkProgram(programId);
 
		// check OpenGL error flag. This is not necessary, but generally good practice
		// (fail early, fail hard)
		Util.checkGLError();
 
		// game loop
		while( Display.isCloseRequested() == false )
		{
			// Even though we successfully loaded, compiled and
			// linked a shader, we don't use it just yet. :)
 
			// swap buffers and sync frame rate to 60 fps
			Display.update();
			Display.sync(60);
		}
 
		// close Display and stop internal update thread
		Display.destroy();
	}
 
 
   /*
    * With the exception of syntax, setting up vertex and fragment shaders
    * is the same.
    * @param the name and path to the vertex shader
    */
	private int loadAndCompileShader(String filename, int shaderType)
    {
		//vertShader will be non zero if succefully created
		int handle = glCreateShader(shaderType);
 
		// load code from file into String
		String code = loadFile(filename);
 
		// upload code to OpenGL and associate code with shader
		glShaderSource(handle, code);
 
		// compile source code into binary
		glCompileShader(handle);
 
		// acquire compilation status
		int shaderStatus = glGetShader(handle, GL20.GL_COMPILE_STATUS);
 
		// check whether compilation was successful
		if( shaderStatus == GL11.GL_FALSE)
		{
			throw new IllegalStateException("compilation error for shader ["+filename+"]. Reason: " + glGetShaderInfoLog(handle, 1000));
		}
 
		return handle;
    }
 
	/**
	 * Load a text file and return its contents as a String.
	 */
	private String loadFile(String filename)
	{
		StringBuilder vertexCode = new StringBuilder();
		String line = null ;
		try
		{
		    BufferedReader reader = new BufferedReader(new FileReader(filename));
		    while( (line = reader.readLine()) !=null )
		    {
		    	vertexCode.append(line);
		    	vertexCode.append('\n');
		    }
		}
		catch(Exception e)
		{
			throw new IllegalArgumentException("unable to load shader from file ["+filename+"]", e);
		}
 
		return vertexCode.toString();
	}
 
 
	public static void main(String[] args)
	{
		Shader2dExample example = new Shader2dExample();
		example.initAndRun("Shader 2D Example");
	}
 
}

Eine Antwort

  1. Theodor says:

    Great tutorial! This really helped me, please continue this kind of tutorials.

Post Comment

Please notice: Comments are moderated by an Admin.