Using OpenGL ES 2.0 Android

Android began supporting Open GL ES 2.0 in Android 2.2 (API Level 8), although applications that leveraged the Android NDK could use 2.0 features as early as API Level 5 with NDK Release 3. In this section, we discuss the Android Java API support for OpenGL ES 2.0. Support also remains for OpenGL ES 1.x, and for good reason. Open GL ES 2.0 is not backward compatible with OpenGL ES 1.x.The different OpenGL ES versions provide different methods of handling 3D graphics:

  • OpenGL ES 1.x provides a fixed function rendering and texturing pipeline. That is to say, the math used to transform, light, and color a scene is all the same—fixed functions.
  • OpenGL ES 2.0 replaced the fixed functions with vertex and fragment shader programs written, of course, by you, the developer.Writing the shader programs provides much more flexibility, but does incur a bit more overhead on the development side.

The choice of which version of OpenGL ES to use is yours. In this section, we show you how to initialize and get a basic OpenGL ES 2.0 program up and running.

Configuring Your Application for OpenGL ES 2.0

If you’re going to use the Android OpenGL ES 2.0 APIs, and aren’t planning on supporting alternate code paths, you need to specify two items within your manifest file: that your application requires Android 2.2 or higher using the <uses-sdk> tag and that it requires OpenGL ES 2.0 using the <uses-feature> tag.

<uses-sdk
android:targetSdkVersion="8"
android:minSdkVersion="8" />
<uses-feature
android:glEsVersion="0x00020000" />

Requesting an OpenGL ES 2.0 Surface

Start by creating your custom SurfaceView, which you usually do within the Activity class onCreate() method, as follows:

mAndroidSurface = new CustomGL2SurfaceView(this);
setContentView(mAndroidSurface);

Of course, you need to implement the CustomGL2SurfaceView class. In our sample project,we did this as an inner class of the Activity, for convenience:

private class CustomGL2SurfaceView extends GLSurfaceView {
final CustomRenderer renderer;
public CustomGL2SurfaceView(Context context) {
super(context);
setEGLContextClientVersion(2);
renderer = new CustomRenderer();
setRenderer(renderer);
}
}

The most important line of code here is the call to the setEGLContextClientVersion() method. This call is made in order to request an EGL context for OpenGL ES 1.x (when the parameter is 1) or OpenGL ES 2.x (when the parameter is 2).Then the custom renderer is set.

Although it might seem confusing, the Renderer methods take GL10 objects. How, then, are you to make OpenGL ES 2.0 calls? The answer turns out to be simple: The new GLES20 class is entirely static. Just ignore the GL10 parameters and make calls directly to the GLES20 class.

The CustomRenderer class starts out by initializing the vertices, much as we did earlier. Then, when the onSurfaceCreate() method is called, we can initialize the shader programs, as follows:

@Override
public void onSurfaceCreated(GL10 unused, EGLConfig unused2) {
try {
initShaderProgram(R.raw.simple_vertex, R.raw.simple_fragment);
initialized = true;
} catch (Exception e) {
Log.e(DEBUG_TAG, "Failed to init GL");
}
}

The two resource identifiers, simple_vertex and simple_fragment, simply reference two text files stored as a raw resources. Now, let’s look at the initialization of the shaders:

private int shaderProgram = 0;
private void initShaderProgram(int vertexId, int fragmentId)
throws Exception {
int vertexShader =
loadAndCompileShader(GLES20.GL_VERTEX_SHADER, vertexId);
int fragmentShader =
loadAndCompileShader(GLES20.GL_FRAGMENT_SHADER, fragmentId);
shaderProgram = GLES20.glCreateProgram();
if (shaderProgram == 0) {
throw new Exception("Failed to create shader program");
}
// attach the shaders to the program
GLES20.glAttachShader(shaderProgram, vertexShader);
GLES20.glAttachShader(shaderProgram, fragmentShader);
// bind attribute in our vertex shader
GLES20.glBindAttribLocation(shaderProgram, 0, "vPosition");
// link the shaders
GLES20.glLinkProgram(shaderProgram);
// check the linker status
int[] linkerStatus = new int[1];
GLES20.glGetProgramiv(shaderProgram, GLES20.GL_LINK_STATUS,
linkerStatus, 0); if (GLES20.GL_TRUE != linkerStatus[0]) {
Log.e(DEBUG_TAG, "Linker Failure: "
+ GLES20.glGetProgramInfoLog(shaderProgram));
GLES20.glDeleteProgram(shaderProgram);
throw new Exception("Program linker failed");
}
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
}

This process does not change substantially for different shaders. Recall that OpenGL ES 2.0 requires both a vertex shader and a fragment shader. First, we load the text for each shader and compile them. Then, we create a new shader program reference, attach both shaders to it, assign an attribute position to our only input parameter, and link the program. Finally, checks are made to confirm that the program linked successfully.

The loading of each shader is handled by our loadAndCompileShader() method. Here is a sample implementation of this method:

private int loadAndCompileShader(int shaderType, int shaderId)
throws Exception {
InputStream inputStream =
AndroidGL2Activity.this.getResources().openRawResource(shaderId);
String shaderCode = inputStreamToString(inputStream);
int shader = GLES20.glCreateShader(shaderType);
if (shader == 0) {
throw new Exception("Can’t create shader");
}
// hand the code over to GL
GLES20.glShaderSource(shader, shaderCode);
// compile it
GLES20.glCompileShader(shader);
// get compile status
int[] status = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] == 0) {
// failed
Log.e(DEBUG_TAG, "Compiler Failure: "
+ GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
throw new Exception("Shader compilation failed");
}
return shader;
}

The loadAndCompileShader() method reads in the raw resource as a string.Then the source is handed over to GLES20 via a call to the glShaderSource() method. Finally, the shader is compiled with a call to glCompileShader().The result is checked to make sure the compile was successful. OpenGL ES 2.0 holds the binary results internally so that they can be used later during linking.

The onSurfaceChanged() method should look quite familiar—it changes little. The viewport is reconfigured for the new display metrics and then the clear color is set. Note again that you can simply use the static GLES20 calls rather than the GL10 parameter.

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
Log.v(DEBUG_TAG, "onSurfaceChanged");
GLES20.glViewport(0, 0, width, height);
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
}

Finally,we’re ready to render the triangle.The scene is rendered each time the system calls our onDrawFrame() implementation.

@Override
public void onDrawFrame(GL10 unused) {
if (!initialized) {
return;
}
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(shaderProgram);
GLES20.glVertexAttribPointer(0, 3, GLES20.GL_FLOAT, false, 12,
verticesBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}

At this point, the code should also appear familiar. The primary difference here is the call to the glUseProgram() method, where we must pass in the numeric identifier of the program we compiled and linked. The final result is simply a static (motionless) triangle on the screen. It’s not very exciting, considering the amount of code required. The flexibility of the shaders is powerful, but many applications don’t need the extra flexibility that comes with using OpenGL ES 2.0, either.

By now, you might be wondering what the shaders look like. Because the resource system of Android just uses the part of the file name before the extension, we decided to name our shader files very clearly so we could easily tell what they were: simple_vertex.shader and simple_fragment.shader. These are two of the simplest shaders one can define.

First, let’s look at the vertex shader because it’s first in the pipeline:

attribute vec4 vPosition;
void main()
{
gl_Position = vPosition; }

This has a single input, vPosition, which is simply assigned to the output. No transformations are applied, and we’re not doing any texturing. Now let’s turn our attention to the fragment shader:

precision mediump float;
void main()
{
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}

This shader is even simpler. It’s assigning a fixed color to the output. In this case, it’s assigning green to the output.

Shader definitions can be quite complex. Implementing lighting, texturing, fog effects, and other interesting OpenGL ES 2.0 features that can’t be fashioned using the fixed pipeline of OpenGL ES 1.x is far beyond the scope of this book.



Face Book Twitter Google Plus Instagram Youtube Linkedin Myspace Pinterest Soundcloud Wikipedia

All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

Android Topics