When I started using the OpenGL SuperBible book to learn OpenGL, I noticed that the first examples from the book used some classes made by the author to encapsulate some complexity that would be explained later. As I was translating the examples do Java code, I had to figure out what those classes did before reading and testing the examples from the book.
One of those classes is the GLShaderManager, which I actually transformed in 2 classes. The first is the GLShader.java
. The objective of GLShader.java
is to receive 2 shader programs: a vertex program and a fragment program, and compile the programs, link them and extract the uniform and attribute ids into a Map on the client side for easier use later.
The code in the example is available at: http://code.google.com/p/opengl-superbible-java/
I rewrote the examples using LWJGL.
The main tricks of the class are on the constructor. It receives 2 strings as parameters, the vertexShaderSource
and the fragmentShaderSource
.
So, the first step is compiling the shader sources. The code for compiling the Vertex Shader is as follows:
int vertexShader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); GL20.glShaderSource(vertexShader, vertexShaderSource); GL20.glCompileShader(vertexShader); String vertexShaderErrorLog = GL20.glGetShaderInfoLog(vertexShader, 65536); if (vertexShaderErrorLog.length() != 0) { System.err.println( "Vertex shader compile log: \n" + vertexShaderErrorLog); }
First, a shader pointer is created with glCreateShader
. The parameter indicates which type of shader to create. The second line attaches the shader source the the shader id. And, on the 3rd line, the shader is compiled.
The rest of the code checks if the compilation of the shader was OK and displays a message if something went wrong. (May be a good place to throw an exception).
Next thing to do is compiling the Fragment Shader.
int fragmentShader = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); GL20.glShaderSource(fragmentShader, fragmentShaderSource); GL20.glCompileShader(fragmentShader); String fragmentShaderErrorLog = GL20.glGetShaderInfoLog(fragmentShader, 65536); if (fragmentShaderErrorLog.length() != 0) { System.err.println("Fragment shader compile log: \n" + fragmentShaderErrorLog); }
The code is almost the same as compiling the vertex shader, but passing GL_FRAGMENT_SHADER
as a parameter to glCreateShader
.
Now that we've compiled both the vertex shader and fragment shader, we have to link them together in a program.
program = GL20.glCreateProgram(); GL20.glAttachShader(program, vertexShader); GL20.glAttachShader(program, fragmentShader); GL20.glLinkProgram(program); String log = GL20.glGetProgramInfoLog(program, 65536); if (log.length() != 0) { System.err.println("Program link log:\n" + log); }
The first line creates a pointer to the program. Then, both the vertex shader and fragment shaders are attached to the program. The last step is to link them. Again, after linking the program we check if something went wrong.
By now, your shaders are compiled, linked and ready to use. But we may do something else. Each uniform or attribute created on your vertex and shader programs receives a pointer to be use the client code. But you have to find out which is which. Heres the code to identify the attribute ids:
int numAttributes = GL20.glGetProgram(program, GL20.GL_ACTIVE_ATTRIBUTES); int maxAttributeLength = GL20.glGetProgram(program, GL20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH); for (int i = 0; i < numAttributes; i++) { String name = GL20.glGetActiveAttrib(program, i, maxAttributeLength); int location = GL20.glGetAttribLocation(program, name); System.out.println(name + ":" + location); attributeLocations.put(name, location); }
First, we find out how many attributes we have. Then the size of the larges attribute name. Then, we loop for each attribute, get its name and its pointer and put it inside a map. The code for the uniform IDs is almost the same:
int numUniforms = GL20.glGetProgram(program, GL20.GL_ACTIVE_UNIFORMS); int maxUniformLength = GL20.glGetProgram(program, GL20.GL_ACTIVE_UNIFORM_MAX_LENGTH); for (int i = 0; i < numUniforms; i++) { String name = GL20.glGetActiveUniform(program, i, maxUniformLength); int location = GL20.glGetUniformLocation(program, name); uniformLocations.put(name, location); System.out.println(name + ":" + location); }
The code to activate the shader is pretty simple:
public void useShader() { //Enable shader GL20.glUseProgram(program); }
And this is how we encapsulate setting a uniform value. There, we can see the Map we made at the constructor in action. We could opt to not creating the map and getting the location on the fly on this method.
public void setUniformMatrix4(String uniformName, boolean traverse, FloatBuffer matrixdata) { int location = uniformLocations.get(uniformName); GL20.glUniformMatrix4(location, traverse, matrixdata); }
Thats it. Thats the first part of my port of the GLShaderManager class to Java using lwjgl. I've also ported this code to Android (2.2+) with minimal changes.