To follow this tutorial you need to install Eclipse and the Android SDK. I am quite happy with the new Eclipse Juno release, considering that previous versions often had problems executing OpenGL apps in the emulator. You can find detailed instructions for installing the SDK at the official Android developer Web - developer.android.com. After installing all required tools you have to configure a virtual device. This example works fine with API level 14. Now let's start with the code.
The main activity
The main activity class in the OpenGL app has some differences to a standard Android application. It is necessary to override the 'onCreate' event and create instances of OpenGL related classes. In this case, we don't use a standard layout as is usual in Android. The setContentView method contains a GLSurfaceView instance instead of a layout XML file. The GLSurfaceView is needed to draw and manipulate objects using the OpenGL API. It is also important to include a renderer.
public
void
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this
.requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
GLSurfaceView view =
new
GLSurfaceView(
this
);
renderer =
new
OpenGLRenderer(
this
);
view.setRenderer(renderer);
setContentView(view);
}
Graphic objects
This was the only class that shares some similarities with the standard Android development practices. In the next section, I am going to draw a 3D letter 'F' which requires a few more classes. First we need a class that draws a simple square with textures. We will use it to form a 3D cube with properly distributed squares. Finally, the cube can be used to build a 3D model.
The square class is responsible for positioning the vector and the texture at the screen. We define both the vector structure and the texture mapping in float arrays. They get transformed into buffers to be used in other methods. The two methods for drawing and textures contain standard operations to get our object on the screen.
public
class
Square {
private
FloatBuffer vertexBuffer;
// buffer holding the vertices
private
float
vertices[] = {
-1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
private
FloatBuffer textureBuffer;
// buffer holding the texture coordinates
private
float
texture[] = {
// Mapping coordinates for the vertices
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 1.0f,
1.0f, 0.0f
};
/** The texture pointer */
private
int
[] textures =
new
int
[1];
public
Square() {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
// allocates the memory from the byte buffer
vertexBuffer = byteBuffer.asFloatBuffer();
// fill the vertexBuffer with the vertices
vertexBuffer.put(vertices);
vertexBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
}
public
void
draw(GL10 gl) {
// bind the previously generated texture
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
// Draw the vertices as triangle strip
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
public
void
loadGLTexture(GL10 gl, Context context) {
// loading texture
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.texture_cube1);
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[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_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
http://obviam.net/index.php/texture-mapping-opengl-android-displaying-images-using-opengl-and-squares/
The next step is drawing a cube using the square class. This step is a little easer because we don't have to take care about how to display it on the screen. We just draw six squares (one for every surface) with translations to form a cube. All methods are just transforming the matrix to position the squares. The translate method has three parameters which represent the x,y and z axis. Depending on the parameter value, the object moves in a given direction. The rotate method has a fourth parameter for the rotation angle.
OpenGL has two very useful methods for drawing objects. Those are 'glPushMatrix' and 'glPopMatrix'. The first one remembers the current matrix value or the current coordinate system and puts it on the stack. The popMatrix method returns the latest stack value. Using these methods, we can construct separate models through a series of operations. Now take a look how the cube is constructed using the stack operations.
public
class
Cube {
public
Square square;
public
Cube() {
square =
new
Square();
}
public
void
draw(GL10 gl, Context context) {
gl.glPushMatrix();
gl.glTranslatef(0, 0, 1);
// front surface
square.draw(gl);
// front surface
gl.glPushMatrix();
gl.glTranslatef(0, 0, -1);
gl.glTranslatef(1, 0, 0);
gl.glRotatef(90, 0, 1, 0);
square.draw(gl);
gl.glPopMatrix();
// back surface
gl.glPushMatrix();
gl.glTranslatef(0, 0, -2);
square.draw(gl);
gl.glPopMatrix();
// left surface
gl.glPushMatrix();
gl.glTranslatef(0, 0, -1);
gl.glTranslatef(-1, 0, 0);
gl.glRotatef(90, 0, 1, 0);
square.draw(gl);
gl.glPopMatrix();
// top surface
gl.glPushMatrix();
gl.glTranslatef(0, 0, -1);
gl.glTranslatef(0, 1, 0);
gl.glRotatef(90, 1, 0, 0);
square.draw(gl);
gl.glPopMatrix();
// bottom surface
gl.glPushMatrix();
gl.glTranslatef(0, 0, -1);
gl.glTranslatef(0, -1, 0);
gl.glRotatef(90, 1, 0, 0);
square.draw(gl);
gl.glPopMatrix();
gl.glPopMatrix();
}
Now we have a cube and we can use it to compose a 3D model. In this case I decided to draw a letter âFâ with a rotation animation. As you can see, the following code has a similar structure to the previous class, except the last line which increases the angle by 5. Since the draw method is called for every rendering, we can simulate
image rotation by increasing the angle at every step.
public
class
Logo extends Cube{
public
Cube cube;
private
int
angle;
private
int
increase = 5;
public
Logo() {
cube =
new
Cube();
}
@Override
public
void
draw(GL10 gl, Context context) {
gl.glPushMatrix();
gl.glTranslatef(5, 0, 0);
cube.draw(gl, context);
gl.glTranslatef(0, 2, 0);
cube.draw(gl, context);
gl.glTranslatef(0, 2, 0);
cube.draw(gl, context);
gl.glTranslatef(0, 1, 0);
cube.draw(gl, context);
gl.glTranslatef(0, 3, 0);
gl.glRotatef(angle, 0, 1, 0);
cube.draw(gl, context);
gl.glPopMatrix();
angle+=increase;
}
Now you can see the final result in the picture below. The graphic is fully generated from code and doesn't require any modeling. By following this technique, you can define other base objects as triangles or circles to build more complex graphics. This will be a topic for a future article. For now, we have covered some basics to understand how OpenGL works with Android OS.