GLSL Tutorial: Communicating with Shaders

From LWJGL
Jump to: navigation, search

Although the pre-defined variables in GLSL are extremely versatile in their possible uses, sometimes you will need to give your GLSL shader a little bit extra information. This can be accomplished via two very similar means but for very different roles.

Uniform Variables

The role of uniform variables exist to pass extra data to a shader for a specific primitive (Quad, Triangle, Polygon etc.). This means that the value of these cannot be altered in the middle of a glBegin, glEnd pair. Bearing this is mind, one of the potential uses for this could be in specifying a drawing mode for your shader. This is something I use a lot for debugging purposes, but if for example your game had a regular mode and a night vision mode, uniform variables could be how you accomplish this. First of all, an example of using uniform variables in a shader:

#version 110

struct Light{
    float intensity;
    vec3 color;
    vec3 position;
    vec3 direction;
}
uniform int renderMode; 
uniform vec3 playerPos;
uniform Light light1;
uniform int anArrayOfInts[5];
uniform Light someLights[5];
//uniform variables must be declared in the global space (Outside of any functions). 
//They are declared simply with the key word uniform and then as normal.
//Any valid GLSL type can also be uniform, notice the arrays, structures and arrays of structures.

void main() {
    if(renderMode == 0) {
        //render normally
    } else if(renderMode == 1) {
        //render for your night vision
    }
    //I have only used one of the variables here. The rest could be used elsewhere but are really just examples.
    //Uniform variables are read only (const).
}

So they are very simple in GLSL, now how to assign values to them in openGL. The first job is to get the location of the variable in memory which is just an integer, to do so you will need the program id in which the shader is running and a String of the name of the variable in your GLSL code. After that, there are several methods to set the value at this location. According to the specification it is only necessary to have linked your program but due to particular vendor's implementations, I would always also have your program in use (glUseProgram).


public void setUniformVariables(int programID, int renderMode, Vector v, Light l, int[] someIntegers) {
    //This is using the previous example for the variables.
    //Imagine that Vector is a class holding 3 floats in fields x, y and z, and that Light is class copying the structure defined 
    //above.

    int loc1 = GL20.glGetUniformLocation(programID, "renderMode");
    //This is the location of the renderMode uniform variable in our program.
    GL20.glUniform1i(loc1, renderMode); 
    //in glUniform1i, the number refers to the number of values in the type of the variable. If it was a vec3 this would be 3 and a
    //vec2 would require 2. (Special methods exist for matrices). The i is for integer. As usual can be i or f (for float!).

    int loc2 = GL20.glGetUniformLocation(programID, "playerPos");
    GL20.glUniform3f(loc2, v.x, v.y, v.z);
    //You read the previous comment right?

    int loc3 = GL20.glGetUniformLocation(programID, "light1.color");
    int loc4 = GL20.glGetUniformLocation(programID, "light1.intensity");
    //You cannot set the value of a structure in one go. You have to assign each variable one by one (Just like in c)
    GL20.glUniform3f(loc3, l.color.x, l.color.y, l.color.z);
    GL20.glUniform1f(loc4, l.intensity);

    IntBuffer buffOfIntegers = BufferUtils.createIntBuffer(someIntegers.length);
    buffOfIntegers.put(someIntegers);
    buffOfIntegers.rewind();
    int loc5 = GL20.glGetUniformLocation(programID, "anArrayOfInts");
    GL20.glUniform1(loc4, buffOfIntegers);
    //For an array, a float or int buffer must be used. Do not try to put more values in than you allocated in the shader. Notice
    //that we still use 1, even though it has several elements. This is because the type is int. There is also no i on the method
    //name, because we are using a buffer rather than sequential arguments. Why the i isn't there I couldn't say.

    int loc6 = GL20.glGetUniformLocation(programID, "anArrayOfInts[2]");
    GL20.glUniform1i(loc4, renderMode);
    //Did you see what I did there? You can assign a specific element of the array, and you access it just as if you were in the 
    //shader. Not sure why we would want the renderMode there but...

    int loc7 = GL20.glGetUniformLocation(programID, "someLights[0].direction");
    GL20.glUniform3f(loc7, l.direction.x, l.direction.y, l.direction.z);
    //And the piece de resistance, arrays of structures. I believe that the only way to 
    //do this is to set each variable in each element of the array individually, but hey maybe you could use a for loop for that. 
    //Hmmmm. Remember in Java you can work with strings as if they were numbers. (String s2 = s1 + " Marvelous";). 
}


Attribute Variables

The following section describes features deprecated in GLSL 1.3 and removed in GLSL 1.4 upwards.

Attribute variables are very similar to uniform variables but for two vital differences. They are specified on a per vertex basis and they are only directly readable by the vertex shader. It is possible to use them in a fragment shader and I will show you how soon. The example below gives each vertex the option to be drawn with an alternate colour. If you want the original colour to be used then you set the red component to -1. Not overly useful but simple.

#version 110

attribute int anAttributeInteger;
attribute vec4 altColour;
//Unlike uniform variables, attribute arrays are not allowed, otherwise all types can be attribute.

void main() {
    if(altColour.r != -1) {
        //render with alternate colour
    } else {
        //render with gl_Color
    }
}

As with uniform variables we must first find the location of the variable in memory using it's name and the program and we can then give our vertex a new value. Again the program must be linked and I advise having it in use before doing this.

    int loc1 = GL20.glGetAttribLocation(programID, "altColour");
    GL11.glBegin(GL11.GL_QUADS);
    GL20.glVertexAttrib4f(loc1, 1, 1, 1, 1); 
    //The name of this method is slightly different, otherwise mostly the same.
    GL11.glVertex3f(-100, -100, 0);
    GL20.glVertexAttrib4f(loc1, 1, 0, 0, 1); 
    GL11.glVertex3f(100, -100, 0);
    GL20.glVertexAttrib4f(loc1, 0, 1, 0, 1); 
    GL11.glVertex3f(100, 100, 0);
    GL20.glVertexAttrib4f(loc1, 0, 0, 1, 1); 
    GL11.glVertex3f(-100, 100, 0);
    GL11.glEnd();
    //The above block of code draws a quad about the origin with different alternate colours specified to the vertex shader.

    int loc2 = GL20.glGetAttribLocation(programID, "anAttributeInteger");
    GL20.glVertexAttrib1s(loc2, 4); 
    //The reason I'm showing this is to illustrate that this method does NOT use integer arguments, it uses shorts. If the integers
    //you wish to give to your shader are larger than the maximum for a short value you can use the float method for an integer 
    //attribute variable.

If you like your openGL to be a little more advanced and use VBOs for your primitive rendering (There is a very nice tutorial already on this wiki) then you can even specify attribute variables within VBOs like so.

//For the following example, assume vertex data is defined in the VBO in the following order: xPos, yPos, zPos, xNorm, yNorm, 
//zNorm, red, green, blue, alpha, altRed, altBlue, altGreen, altAlpha and that they are all floats.

int loc = GL20.glGetAttribLocation(programID, "altColour");
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
GL12.glEnableVertexAttribArray(loc);
//Obviously only do the above once at the beginning of the program.

GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vertexBufferId);
GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);  

GL11.glVertexPointer(3, GL11.GL_FLOAT, (3 + 3 + 4 + 4)*4, 0);
GL11.glNormalPointer(GL11.GL_FLOAT, (3 + 3 + 4 + 4)*4, 3 * 4);
GL11.glColorPointer(4, GL11.GL_FLOAT, (3 + 3 + 4 + 4)*4, (3 + 3) * 4);
GL20.glVertexAttribPointer(loc, 4, GL11.GL_FLOAT, false, (3 + 3 + 4 + 4)*4, (3 + 3 + 4) * 4);
//The above method is very similar to everything else but with two extra arguments. The first is the location of the variable (It 
//is called in the documentation index, do not be fooled. I know that it essentially is an index but location makes much more sense
//) The argument after the type of the data is a boolean specifying whether you want the data normalised, isn't openGl helpful. 
//NB I have written the stride and offset as expressions rather than numbers to make it easier to understand. If you do not 
//understand the above and want to, read the tutorial on VBOs on this site.

GL12.glDrawRangeElements(GL11.GL_QUADS, 0, 4, 4, GL11.GL_UNSIGNED_INT, 0);
//Finally draw the quad.


Varying Variables

The following section describes features deprecated in GLSL 1.3 and removed in GLSL 1.4 upwards.

Don't they have a silly name. Remember when I said you couldn't access attribute variables directly from a fragment shader, well this is how we do it as well as some other things. These variables are used to communicate between vertex shaders and fragment shaders. You define a variable in both the vertex and fragment shader with the keyword varying. You can then write to them in the vertex and read from them in fragments. However the value in the fragment will not be the value from the vertex, it will be an interpolation of the values in the vertexs that make up this fragment. I will use the same example as for the attributes, but this time we want to use both the colour and altColour in the fragment shader.

The vertex shader.

#version 110

attribute vec4 altColour;
varying vec4 vAltColour;
varying float anArrayOfFloats[6];
//GLSL is oddly restrictive in what types can be varying variables. As far as I know, you can use anything except integers, 
//booleans and structures. So floats, arrays, vectors and matrices.

void main() {
    vAltColour = altColour;
    //Set the varying to the attribute;

    //Now do everything else, maybe work out some other values to give to the fragment, varying variables don't have to come from
    //attributes. 
}

The fragment shader.

#version 110

varying vec4 vAltColour;
//Define the varying variable with the same type and name from the vertex shader.

//Notice I have not declared all of the varying variables from the vertex shader and you do not need to. You can even declare 
//varying variables that weren't in your vertex shader. (Can't see why you would) When you do I believe it sets all the values to 0
//Bear this in mind if your primitives are all being drawn black and are using your varying variables.

void main() {
    //Draw using either the altColour or the regular colour.
}


A Little Appendix

If you read my previous GLSL tutorial, I promised to tell you how to give shaders a variable number of lights (or anything else). It is not really GLSL specific but is not something us Java programmers are used to so... With uniform arrays, there is no reason you have to set all the indexes of the array on each rendering iteration. Hence you could use a uniform array with the length of the maximum number of lights you will ever need to draw (This would probably be a performance cap as well as software limitation). Then you also have a uniform integer specifying how many lights you are using this iteration. You could even choose what the maximum number of lights is at runtime by replacing the number in your GLSL source before compiling the shader. Just another benefit of runtime compiling. I know this feels a bit like cheating but the only other alternative I know of is to use GLSLs sampler structures which are meant to be used for textures and that feels even more like cheating.

I very much hope you have found this tutorial helpful. In the future I may write a tutorial for more recent GLSL features (But I personally tend to use earlier versions of everything to ensure everyone can use it) or I may write a little walkthrough for writing a simple shader with custom lighting. Again for buckets more info check out The Lighthouse3D.