| 5. Enter libGDX - Rendering

Step 6 - Tell OpenGL where and how to read the data specified in steps #1 and #3

All the preparation is complete. It is now time to render to the screen. The OGL commands are:

1
2
3
4
5
GLES20.glUseProgram( shaderProgram );
GLES20.glUniformMatrix4fv( uMVPVariableLocation, 1, false, MVPMatrix, 0 );
GLES20.glVertexAttribPointer( positionVariableLocation, 3, GLES20.GL_FLOAT, false, 0, fBuffer );
GLES20.glEnableVertexAttribArray( positionVariableLocation );
GLES20.glDrawArrays( GLES20.GL_POINTS, 0, 3 );

LibGDX accomplishes that this way:

1
2
3
4
5
6
7
8
9
shaderProgram.begin(); // internally calls Gdx.gl.glUseProgram();

shaderProgram.setUniformMatrix( u_projViewTrans, cam.combined );
shaderProgram.setUniformMatrix( u_worldTrans, idt );
Gdx.gl.glEnable( Gdx.gl.GL_VERTEX_PROGRAM_POINT_SIZE );
Gdx.gl.glEnable( Gdx.gl.GL_DEPTH_TEST );
Gdx.gl.glDrawArrays( Gdx.gl.GL_POINTS, 0, vertisees.getNumVertices() );

shaderProgram.end();

There isn't much to break down here since we've already covered the shader program class in the last section. Line 1 in both listings are equivalent. Line 2 in Listing #1 is equivalent to lines 3 and 4 in Listing #2; we are telling the shader program the two matrices (we retrieved their references at the end of the last step) we are using to translate to/from camera coordinates and to/from world coordinates. Lines 3 - 4 in Listing #1 are called internally by the VBO's bind() function which we saw in step #3 (you'll see how it's called in the complete program below). Lines 5 - 8 in Listing #2 are non-libGDX OGL commands. Line 5 in Listing #1 is obviously equivalent to line 7 in Listing #2. And that's everything.

Let's finish with a complete listing of all the libGDX code from the past couple of sections:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.glutils.VertexBufferObject;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;

public class Test extends ApplicationAdapter {

    public float vertices[];
    public VertexBufferObject vertisees;
    public String vertexShader;
    public String fragmentShader;
    public ShaderProgram shaderProgram;

    //transform matrix
    private Matrix4 idt;

    private Camera cam;
    private int u_projViewTrans, u_worldTrans;
    private VertexAttributes vAs;

    @Override
    public void create() {

        //identity matrix used for world transform
        idt = new Matrix4().idt();

        //create some points with color
        vertices = new float[]{
                0.0f, 0.0f, 0.0f, // Vertex 1 (x, y, z), (r, g, b, a)
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                3f, 2f, -2.0f, // Vertex 2 (x, y, z), (r, g, b, a)
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                5f, 5f, 5f,  // Vertex 3 (x, y, z), (...)
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                3, 4, 2,  //Vertex 4 (...), (...)
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                5, 6, 7,  //Vertex 5 ...
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                1, 3, 8, //...
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f),
                2, 2, 2,
                MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f), MathUtils.random(0, 1f)
        };

        //create vertex attributes which define how data is accessed in VBO
        VertexAttribute vA = new VertexAttribute(VertexAttributes.Usage.Position, 3, "a_position");
        VertexAttribute vC = new VertexAttribute(VertexAttributes.Usage.Position, 4, "a_color");
        vAs = new VertexAttributes( new VertexAttribute[]{vA, vC} );

        //create VBO and pass in vertex attributes object
        vertisees = new VertexBufferObject( false, (vertices.length / 7 ), vAs );

        //set up window into our virtual space
        cam = new PerspectiveCamera( 67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight() );
        cam.position.set( 0f, 0f, 20f );
        cam.lookAt( 0, 0, 0 );
        cam.near = 0.1f;
        cam.far = 1000f;
        cam.update();

        //create and compile shaders
        vertexShader = Gdx.files.internal("vertexShader.glsl").readString();
        fragmentShader = Gdx.files.internal("fragmentShader.glsl").readString();
        shaderProgram = new ShaderProgram( vertexShader, fragmentShader );

        //get shader code variable pointers
        u_projViewTrans = shaderProgram.getUniformLocation("u_projViewTrans");
        u_worldTrans = shaderProgram.getUniformLocation("u_worldTrans");

        //let OGL know where vertex data is
        vertisees.bind( shaderProgram ); //gdx.gl.bindbuffer && gdx.gl.glBufferData
        vertisees.setVertices( vertices, 0, vertices.length );
    }

    @Override
    public void render () {
        Gdx.gl.glViewport( 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight() );
        Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT );

        shaderProgram.begin(); //Gdx.gl.glUseProgram( shadProgram );

        shaderProgram.setUniformMatrix( u_projViewTrans, cam.combined );
        shaderProgram.setUniformMatrix( u_worldTrans, idt );

        Gdx.gl.glEnable( Gdx.gl.GL_VERTEX_PROGRAM_POINT_SIZE );
        Gdx.gl.glEnable( Gdx.gl.GL_DEPTH_TEST );

        //draw the points on the screen
        Gdx.gl.glDrawArrays( Gdx.gl.GL_POINTS, 0, vertisees.getNumVertices() );

        shaderProgram.end();
    }

    public void dispose(){
        vertisees.dispose();
        shaderProgram.dispose();
    }
}

And our two .glsl files:

vertexShader.glsl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
attribute vec3 a_position;
attribute vec4 a_color;

uniform mat4 u_worldTrans;
uniform mat4 u_projViewTrans;

varying vec4 v_color;

void main() {
 v_color = a_color;
 gl_Position = u_projViewTrans * u_worldTrans * vec4(a_position, 1.0);
 vec3 ndc = gl_Position.xyz / gl_Position.w ; // perspective divide.

 float zDist = 1.0 - ndc.z; // 1 is close to camera, 0 is far
 gl_PointSize = 500.0 * zDist; // between 0 and 50 now.
}

fragmentShader.glsl

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

varying vec4 v_color;

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

And our output should be seven multi-colored square points. The colors will vary since they are randomly generated:
We have now covered the basics of libGDX's abstraction layer for OGL. For breaking down higher levels of abstraction involving models, cameras, lighting, materials, etc. head over to xoppa's blog which is also linked in the "Useful Reading" link in the sidebar.

Next.