Random techie babbling...

LibGDX & OGLES2 – My First Triangle (pt.1)

I recently bought the OpenGL ES 2.0 Programming Guide, seems pretty good so far. Unfortunately it’s quite iPhone centric, but the basic principles and OGLES2 code is the same for Android. If you’ve done any OGLES1 work before you’ll find a lot has changed, it’s moved from a fixed pipeline to being completely shader based so requires quite a bit of re-learning.

I’m using LibGDX to ease the pain of getting started, but unfortunately the ‘My First Triangle’ tutorial on the LibGDX wiki is still only OGLES1 based. So, after a few false starts (mostly due to being on a Mac, which appears to be the worst platform for learning OGLES on) I thought it’d be useful to re-write the tutorial focusing on OGLES2, so here we go…

Please note : this is intended to be a direct re-write of the wiki tutorial and is as similar as possible to aid learning, the only differences being those introduced by using OGLES2

What you’ll need:

Create a desktop prototype version first…

One of the nice things about LibGDX is the ability to build a complete prototype on the desktop first then quickly transition to an Android application. So we’ll build a desktop version first, then look at how to run the same code base on Android.

Most of the code, including all of the game logic, will reside in a regular Java project. In Eclipse, click :

File -> New -> Java Project

Use MyFirstTriangle as the project name. In the JRE section, select JavaSE-1.6, then click Finish.

Download libgdx-0.8.zip from the link above and uncompress it to a directory somewhere, we’ll name it libgdx-0.8 in this tutorial.

Lets copy libraries contain all the necessary libgdx classes and methods into our workspace. In the Package Explorer view, right-click on the my-first-triangle folder, select:
New -> Folder
and name the new folder libs. From the libgdx-0.8 directory, copy the following files into libs:

  • gdx-backend-jogl.jar
  • gdx-sources.jar
  • gdx.dll
  • gdx.jar
  • libgdx.so
  • libgdx-64.so

If you are using OSX you’ll also need to drop the libgdx.dylib you downloaded in here too if it’s not there already.

Now we’ll add the libraries to our project so that our Java code can reference the classes in the libgdx libraries. Right click on the MyFirstTriangle folder again and select Properties. Go to the Java Build Path section, select the Libraries tab, click Add JARs…, navigate to the MyFirstTriangle/libs directory and select the two files:

gdx-backend-jogl.jar
gdx.jar

Eclipse can also show you the javadoc documention for the libgdx classes and methods you use. While still in the Libraries tab, expand the gdx.jar listing and double click on the entry named Source Attachment. Click Workspace and navigate to the MyFirstTriangle/libs directory again, this time selecting gdx-sources.jar.

While gdx.jar is still expanded, double-click on the entry named Native library location and select the MyFirstTriangle/libs directory. If you omit this you will get an UnsatisfiedLinkError as the libgdx.so/gdx.dll/libgdx.dylib shared libraries can not be found by the VM otherwise.

Now we can create a class that will draw our triangle.

Expand the MyFirstTriangle folder, right-click on the src source directory and select: New -> Package
Name it anything you want, I’m using com.dustypixels.tutorials.myfirsttriangle. Right-click on the new package, and select: New -> Class, use the name MyFirstTriangle. In the Interfaces section, click Add and select the com.badlogic.gdx.ApplicationListener interface, then click Finish.

You will have a new class file created with stubs for all the methods we need to control our application flow. We will soon add logic to MyFirstTriangle to draw a triangle. It will be capable of being run in either the desktop or Android environment.

Lets create the desktop entry point now. In the same package as the MyFirstTriangle class, create another class, this time named MyFirstTriangleDesktop. Put a main method in the class so that it looks something like this:

package com.dustypixels.tutorials.myfirsttriangle;
 
import com.badlogic.gdx.backends.jogl.JoglApplication;
 
public class MyFirstTriangleDesktop {
        public static void main (String[] argv) {
                new JoglApplication(new MyFirstTriangle(), "My First Triangle", 480, 320, true);               
        }
}

The MyFirstTriangleDesktop class gives a starting point to launch a desktop application. It does that by creating a JoglApplication, passing in an instance of the MyFirstTriangle class to perform the rendering.

Note that the only change to this class from the OGLES1 version is the last parameter to the constructor, ‘true’, which tells the JoglApplication we want to use OGLES2.

You can run the application right now by right-clicking on the MyFirstTriangleDesktop class in the Package Explorer and selecting Run As -> Java Application, although it won’t look very exciting until we add some more code to MyFirstTriangle.

Rendering a Triangle

The application shows a blank screen because our MyFirstTriangle class contains no rendering logic. Now it’s time to finally create the code to draw a triangle. Replace the contents of the MyFirstTriangle class with this:

public class MyFirstTriangle implements ApplicationListener {
 
	private ShaderProgram shaderProgram;
	private Mesh mesh;
 
	@Override
	public void create() {
		
		Gdx.app.log("GDX", "create...");
 
		//check we can use GLES2
		if (!Gdx.app.getGraphics().isGL20Available()) {
			throw new GdxRuntimeException("GLES2 Not Available!");
		}
				
		//create shader program
		String vertexShader = 
			"attribute vec4 vPosition; 		\n" + 
			"void main()					\n" +
			"{								\n" +
			"	gl_Position = vPosition;	\n" +
			"}								\n";
		String fragmentShader = 
			"#ifdef GL_ES 								\n"+
			"precision mediump float;					\n"+
			"#endif 									\n"+
			"void main()								\n"+
			"{											\n"+
			"	gl_FragColor = vec4(1.0,0.0,0.0,1.0);	\n"+
			"}											\n";
		shaderProgram = new ShaderProgram(vertexShader, fragmentShader);
		 if (shaderProgram.isCompiled() == false) {
	         Gdx.app.log("ShaderError", shaderProgram.getLog());
	         System.exit(0);
	      }
		 
		mesh = new Mesh(true, 3, 3, 
                new VertexAttribute(VertexAttributes.Usage.Position, 3, "vPosition"));          
 
        mesh.setVertices(new float[] { -0.5f, -0.5f, 0,
                                        0.5f, -0.5f, 0,
                                        0,     0.5f, 0 });   
        mesh.setIndices(new short[] { 0, 1, 2 });       
	}
 
	@Override
	public void dispose() {
        shaderProgram.dispose();
        mesh.dispose();
	}
 
	@Override
	public void pause() {
		// TODO Auto-generated method stub
 
	}
 
	@Override
	public void render() {
		Gdx.app.log("GDX", "render...");
		
		//clear viewport to black
		Gdx.gl20.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
		//draw triangle
		shaderProgram.begin();
		mesh.render(shaderProgram, GL20.GL_TRIANGLES);
		shaderProgram.end();		
	}
 
	@Override
	public void resize(int width, int height) {
		// TODO Auto-generated method stub
 
	}
 
	@Override
	public void resume() {
		Mesh.invalidateAllMeshes();
	}
 
}

Press CTRL+SHIFT+o to correct the import errors, then run the project again (this time you can alternately press Ctrl-F11, which runs the last run configuration). The application should now have a red triangle rendered in the middle of the screen:

Lets look at the changes we made…

create() is called when the the rendering surface is created. First we check GLES2.0 is available to us, if not there’s no point continuing. We then create the most basic of vertex and fragment shader strings and compile them into a ShaderProgram. We check the ShaderProgram compiled or exit, as we can’t draw without a compiled ShaderProgram.

Note : If you are going to use precision modifiers in your fragment shaders remember to wrap them in #IFDEF‘s for use on the desktop. Precision modifiers are not part of the desktop spec and they may not be supported by your desktop graphics driver and cause errors.

We then create a Mesh, which represents an object that will be drawn onscreen. Its constructor takes several parameters, but for now just take note of the VertexAttribute object we pass in. VertexAttribes are passed into the constructor to indicate what type of information the resulting mesh will contain. A mesh can potentially contain information regarding an object’s position, color, texture, and more, but right now we’ll only assign positional information. Since this mesh is a triangle, its position (and shape) is made up of three vertices, each denoted by three values (x, y, z) in the Cartesian coordiante. The three vertices for this mesh are located at (-0.5f, -0.5f, 0), (0.5f, -0.5f, 0), and (0, 0.5f, 0). We then set the mesh’s indices, which is the order of the vertices, sort of like the order in a game of connecting the dots. In a simple shape like a triangle, the order doesn’t matter because no matter which order you connect the vertices, it will always form a triangle. With more vertices, the order will greatly affect the final shape of the object.

When the application starts running the render() method is called again and again, basically repainting the screen as fast as it can. First we clear the screen (using OpenGLES calls directly), then we call begin() on our ShaderProgram object, tell the the mesh object we created earlier to render iteself, then end() the ShaderProgram.

For simplicity, we’ve skipped over many details of how different parameters change the behavior of the methods we used. These we’ll go over in more detail in a later tutorials. In the mean time, you can see the javadoc of any method simply by hovering over the method name in the editor.

In part 2 we’ll look at how to run this same code base on Android…

0saves
If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.

3 Comments

Got something to say? Feel free, I want to hear from you! Leave a Comment

  1. badlogic says:

    Hey awesome! Might i suggest you update this to 0.81. As you know 0.8 had some, shall we say, “problems” :)

  2. ibrahim says:

    Hi, great article. Thanks.

  3. Matt says:

    Thank you very much. I was looking for a ‘Hello World’ using OpenGL-ES 2.0 after spending some time using LibGdx with a fixed function pipeline. This is just the thing I was after!

Leave a Comment

Let us know your thoughts on this post but remember to place nicely folks!