| 4. Enter libGDX - Shaders

Step 4 -  Create and load shaders

Now we need to set up the code that runs on the GPU. These are the vertex and fragment shader code blocks. A vertex shader program processes the geometry we put in the VBO in step #2, a fragment shader handles the coloring of shapes we draw. For more detailed information regarding shaders, see here, or here (both links are in "Useful Reading" in the sidebar). The original two shader programs from section 1 are:

vertexShader.glsl

1
2
3
4
5
6
7
8
attribute vec3 position;
uniform mat4 uMVP;

void main() {
 vec4 vertex = vec4( position, 1.0 );
        gl_Position = uMVP * vertex;
        gl_PointSize = 15.0;
}

fragmentShader.glsl

1
2
3
4
5
6
7
8
9
#ifdef GL_FRAGMENT_PRECISION_HIGH
 precision highp float;
#else
 precision mediump float;
#endif

void main() {
 gl_FragColor = vec4( 1.0 );
}


and the OGL code to load, compile, link, etc. them is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int vertexShader = loadShader( GLES20.GL_VERTEX_SHADER, vertexShaderCode );
int fragmentShader = loadShader( GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode );
shaderProgram = GLES20.glCreateProgram();
GLES20.glAttachShader( shaderProgram, vertexShader );
GLES20.glAttachShader( shaderProgram, fragmentShader );
GLES20.glLinkProgram( shaderProgram );

//Where loadShader() function is:

private int loadShader( int type, String source ){
     int shader = GLES20.glCreateShader( type );
     GLES20.glShaderSource( shader, source );
     GLES20.glCompileShader( shader );
     return shader;
}

In comparison, libGDX handles creating, compiling, attaching and linking shader programs like this:

1
2
3
vertexShader = Gdx.files.internal( "vertexShader.glsl" ).readString();
fragmentShader = Gdx.files.internal( "fragmentShader.glsl" ).readString();
shaderProgram = new ShaderProgram( vertexShader, fragmentShader );

The ShaderProgram.java constructor internally calls all of the OGL functions in the top listing. Specifically, the constructor calls compileShaders(), which calls loadShader() [which calls .glCreateShader(), .glShaderSource(), and .glCompileShader()] and then linkProgram() [which calls .glCreateProgram(), .glAttachShader(), and .glLinkProgram()]. To dive deeper into the ShaderProgram.java class and how it is used with custom libGDX's shaders (created using Shader.javago here. Now that we've got our shader code loaded, we need to pass data from our program to the shader code variables so the shaders have something to work with. This leads us to the next step.

Step 5 - Get pointers to shader code variables

First we want to jump back to the code from step #3, VertexBufferObject.java's bind() function. This function takes the shader program object we just created and uses it to access the variables we defined in the .glsl files and-- IMPORTANT! --labeled with the VertexAttribute.java object's 'alias' variable. The 'alias' name and the .glsl variable name MUST match. For example:
VertexAttribute vA = new VertexAttribute(VertexAttributes.Usage.Position, 3, "position")
the "position" alias matches the "position" variable in the above vertexShader.glsl file. We mentioned this briefly at the end of step #1.

Lastly, we get pointers to two uniform variables we want to load data into. These two variables will make an appearance in the final shader code listing in the next section. We will also use these references in the next (and final) step. LibGDX handles this task like this:

1
2
u_projViewTrans = shaderProgram.getUniformLocation( "u_projViewTrans" );
u_worldTrans = shaderProgram.getUniformLocation( "u_worldTrans" );

Before we move on, just a reminder that if any of the jargon associated with shaders is foreign to you I suggest reading up on shaders with either a web search or use the sources in the "Useful Reading" link in the sidebar.

Next.