September 18, 2015

OpenGL ES 2.0

The underlying framework for spacedust is based on what I learned from the book Beginning Android Games, which uses OpenGL ES 1.0. With the help of the book OpenGL ES 2 for Android, I have updated my framework to use OpenGL ES 2.0 instead!

I am in no way an OpenGL expert, so there may be better ways to do some of these things, but I wanted to document the main changes I made to my code.

AndroidManifest.xml

Add the following line to AndroidManifest.xml to require version 2.0.

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

GLSurfaceView

Call the following function on a GLSurfaceView to tell it to use version 2.

GLSurfaceView view;

view.setEGLContextClientVersion(2);

Functions

The following function calls…

GL10 gl;

gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

gl.glDrawElements(GL10.GL_TRIANGLES,  numVertices,  GL10.GL_UNSIGNED_SHORT,  indices);

gl.glGenTextures(1, ids, 0);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

gl.glDeleteTextures(1, ids, 0);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);

… all have equivalent version 2 functions.

GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

GLES20.glEnable(GLES20.GL_TEXTURE_2D);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

GLES20.glDrawElements(GLES20.GL_TRIANGLES,  numVertices,  GLES20.GL_UNSIGNED_SHORT,  indices);

GLES20.glGenTextures(1, ids, 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

GLES20.glDeleteTextures(1, ids, 0);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

glColor4f

One of the main differences I ran into was no longer being able to use the glColor4f function. I mainly used this function to fade textures in and out by changing the alpha value.

GL10 gl;

gl.glColor4f(1, 1, 1, 0.5);

In version 2, shaders are required to accomplish the same functionality.

Viewport & Projection Matrix

With version 1, setting up the viewport and projection matrix looks something like this:

GL10 gl;

gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrthof(0, right, bottom, 0, 1, -1);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();

To do something similar in version 2, it looks like this:

float[] projectionMatrix = new float[16];

GLES20.glViewport(0, 0, width, height);
Matrix.orthoM(projectionMatrix, 0, 0, right, bottom, 0, 1, -1);

Shaders

The main difference between version 1 and 2 is shaders. When drawing a vertex, OpenGL uses two shaders. One to control the position (vertex shader) and one to control the color (fragment shader).

A simple vertex shader:

uniform mat4 u_Matrix;

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()
{
  v_TextureCoordinates = a_TextureCoordinates;
  gl_Position = u_Matrix * a_Position;
}

A simple fragment shader:

precision mediump float;

uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

void main()
{
  gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}

Setting up the shaders looks something like this:

int vid = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
int fid = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);

GLES20.glShaderSource(vid, vertex_shader_source);
GLES20.glShaderSource(fid, fragment_shader_source);
GLES20.glCompileShader(vid);
GLES20.glCompileShader(fid);

int pid = GLES20.glCreateProgram();

GLES20.glAttachShader(pid, vid);
GLES20.glAttachShader(pid, fid);
GLES20.glLinkProgram(pid);
GLES20.glUseProgram(pid);

uMatrixLocation             = GLES20.glGetUniformLocation(pid, "u_Matrix");
uTextureUnitLocation        = GLES20.glGetUniformLocation(pid, "u_TextureUnit");
aPositionLocation           = GLES20.glGetAttribLocation(pid, "a_Position");
aTextureCoordinatesLocation = GLES20.glGetAttribLocation(pid, "a_TextureCoordinates");

Texture Binding

GL10 gl;

gl.glBindTexture(GL10.GL_TEXTURE_2D, id);

When binding a texture, version 2 requires a few more function calls to coordinate the information passed to the shaders.

GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id);
GLES20.glUniform1i(uTextureUnitLocation, 0);

Vertices

In version 1, function calls tell OpenGL how to find the texture and position information in an array of vertex information.

GL10 gl;
FloatBuffer vertices;

vertices.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL10.GL_FLOAT, vertexSize, vertices);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

vertices.position(2);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, vertexSize, vertices);

# unbind
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

In version 2, different functions are called to pass the same kind of information to the shaders instead.

FloatBuffer vertices;

vertices.position(0);
GLES20.glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, vertexSize, vertices);
GLES20.glEnableVertexAttribArray(aPositionLocation);

vertices.position(2);
GLES20.glVertexAttribPointer(aTextureCoordinatesLocation, 2, GL_FLOAT, false, vertexSize, 
GLES20.glEnableVertexAttribArray(aTextureCoordinatesLocation);

Conclusion

Converting my code to use OpenGL ES 2.0 went better than I expected. It was complicated but took fewer code changes than I was anticipating.

I am looking forward to what I can do with shaders!


tagged: resource code