Saturday, May 24, 2008

Working with textures in android's OpenGL/ES.


As you may recall, I use textures to draw the moon backdrop for my android application, monolithandroid. Originally, I only used one texture and I used some code from Ed Burnette's forthcoming book "Hello, Android". However, I decided to add more textures in order to improve the game's graphics. So I created a class named GLTextures, that can be used to make working with multiple textures easier.
Here's the code:

package org.teacake.monolith.apk;
import javax.microedition.khronos.opengles.*;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.lang.Integer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
public class GLTextures {
public GLTextures(GL10 gl,Context context)
{
this.gl = gl;
this.context = context;
this.textureMap = new java.util.HashMap ();
}

public void loadTextures()
{
int[] tmp_tex = new int[textureFiles.length];
gl.glGenTextures(textureFiles.length, tmp_tex, 0);
textures = tmp_tex;
for(int i=0;i {
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), textureFiles[i]);
ByteBuffer bb = extract(bmp);
// Get a new texture name
// Load it up
this.textureMap.put(new Integer(textureFiles[i]),new Integer(i));
int tex = tmp_tex[i];
int width = bmp.getWidth();
int height = bmp.getHeight();
gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA,width, height, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bb);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

}
}
public void setTexture(int id)
{
try
{
int textureid = this.textureMap.get(new Integer(id)).intValue();
gl.glBindTexture(GL10.GL_TEXTURE_2D, this.textures[textureid]);

}
catch(Exception e)
{
return;
}
}
private static ByteBuffer extract(Bitmap bmp)
{
ByteBuffer bb = ByteBuffer.allocateDirect(bmp.height() * bmp.width() * 4);
bb.order(ByteOrder.BIG_ENDIAN);
IntBuffer ib = bb.asIntBuffer();
// Convert ARGB -> RGBA
for (int y = bmp.height() - 1; y > -1; y--)
{

for (int x = 0; x < bmp.width(); x++)
{
int pix = bmp.getPixel(x, bmp.getHeight() - y - 1);
int alpha = ((pix >> 24) & 0xFF);
int red = ((pix >> 16) & 0xFF);
int green = ((pix >> 8) & 0xFF);
int blue = ((pix) & 0xFF);

// Make up alpha for interesting effect

//ib.put(red << 24 | green << 16 | blue << 8 | ((red + blue + green) / 3));
ib.put(red << 24 | green << 16 | blue << 8 | alpha);
}
}
bb.position(0);
return bb;
}



public void add(int resource)
{
if(textureFiles==null)
{
textureFiles = new int[1];
textureFiles[0]=resource;
}
else
{
int[] newarray = new int[textureFiles.length+1];
for(int i=0;i {
newarray[i]=textureFiles[i];
}
newarray[textureFiles.length]=resource;
textureFiles = newarray;
}
}
private java.util.HashMap textureMap;
private int[] textureFiles;
private GL10 gl;
private Context context;
private int[] textures;
}


So, what can you use this code for?
This code enables you to load resources as textures.
At first you create a GLTextures object like this:

GLTextures textures = new GLTextures(gl, context);

Then you add the resource images you want to use as textures:

this.textures.add(R.drawable.moon);
this.textures.add(R.drawable.earth);

And then you loadup the textures:

textures.loadTextures();

When you want to use the texture in your OpenGL code, you can use:

textures.setTexture(R.drawable.moon);
.
.
//OpenGL drawing code
.
.

Actually the setTexture() method calls glBindTexture() which sets the current texture to the correct one.
Of course, you have to enable textures in your OpenGL code in order to do that! If you want more details and examples study the classes GLThread, Square and GLTextures found at http://code.google.com/p/monolithandroid
As you may find, loading pictures and converting them to textures can take a lot of time, so one improvement that you can make is to create a caching scheme. You can, for example, store the textures in a file, after converting them from a picture for the first time, so the next time the application is run, it will load the textures without having to do a conversion. Happy hacking!

7 comments:

Guru said...

Hi ther,

I am tring with the given code of texture mapping in my app. but it is not happening, also m confused tht how actually the bytes buffer is getting mapped to the object. As in opengl using vc++, we used to write mapping calls using vertexf(..., but here i found no such call, could u help me out,becos m not able to map the texture i.e. a BMP file on my object.

Guru

Τάσος said...

have a look at http://code.google.com/p/monolithandroid/source/browse/trunk/monolithandroid/src/org/teacake/monolith/apk/Square.java?r=55

The Moon and Earth images are two squares with images of earth and moon texture mapped
Have a look at these lines.

public Square()
{

int one = 0x10000;
int texCoords[] = {

//0, one, one, one, 0, 0, one, 0
one, 0, one, one, 0, 0, 0, one
};

int vertices[] = {
-one, -one, one, one, -one, one,
-one, one, one, one, one, one


};

int colors[] = {
one, one, one, one,
one, one, one, one,
one, one, one, one,
one, one, one, one,

};

byte indices[] = {
0, 1, 2, 1, 2, 3
};

// Buffers to be passed to gl*Pointer() functions
// must be direct, i.e., they must be placed on the
// native heap where the garbage collector cannot
// move them.
//
// Buffers with multi-byte datatypes (e.g., short, int, float)
// must have their byte order set to native order

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asIntBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);

ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asIntBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);

mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);

ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4);
tbb.order(ByteOrder.nativeOrder());
mTextureBuffer = tbb.asIntBuffer();
mTextureBuffer.put(texCoords);
mTextureBuffer.position(0);

Unknown said...

for sharing, looks like some really handy code :)

DedRed said...

Very nice object, please keep posting more related to opengl on the android. One thing though, after:
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), textureFiles[i]);
ByteBuffer bb = extract(bmp);

...add:
bmp.recycle();

Unknown said...

This code works ok for small textures but the loading time for larger ones (e.g. 1024x512) was taking too long. I ended up using this code in the end:

http://blog.poweredbytoast.com/loading-opengl-textures-in-android

Bob said...

Some of the code got lost in the formatting:

for(int i=0;i {

Android app development said...
This comment has been removed by a blog administrator.

Search the web