OpenGL ES Tutorial for Android-Part VI-Textures
December 30th, 2010
Per-Erik Bergman-android,
Embedded,
Java
Last Tutorial we worked a bit more on meshes and we have also talked about adding colors to our mesh. the most common way of adding colors to your mesh is to add a texture. there is a couple of different steps involved with adding a texture to the mesh I
Will try to go through them all and explain the basics about them.
In the previous tutorial, we generated some models and we already know how to color the models. However, the most common coloring method is to add textures. There are several different steps to add a texture to a model. I will expand them one by one.
Loading bitmaps
First step wocould be to get a bitmap to generate a texture from. you can get hold of a bitmap in your different ways from downloading, generating or simply just load one from the resources. i'm going with the simplest one for this example witch is loading
From the resources.
Step 1: We need to get the texture image. There are many ways to do this. You can download, generate, or simply load from a resource. I use the last one: load from a resource file.
Bitmap bitmap = BitmapFactory. decodeResource (contect. getResources (),
R. drawable. icon );
One other thing about textures is that some hardware requires that the height and width are in the power of 2 (1, 2, 4, 8, 16, 32, 64 ...). if you run a texture with a size of 30x30 pixels on a hardware that don't support it you will just get a white square
(Unless you change the default color ).
Note that, on some hardware, the image size required by the texture must be 2 to the Npower (, 16, 32 ...). If your image is 30x30 and the hardware is not supported, you can only see one white box (unless you change the default color)
Generating a texture
After we have loaded the bitmap we need to tell OpenGL to actually create the texture.
After the image is loaded, you can tell OpenGL to generate the texture.
First thing we need to do is to let OpenGL generate some texture id's that we will use as handles to the textures later on. In this example we will only have one texture.
The first thing to do is to generate texture IDs for OpenGL, which will be used later. In this example, we only have one texture.
// Create an int array with the number of textures we want,
// In this case 1.
Int [] textures =NewInt [1];
// Tell OpenGL to generate textures.
Gl. glGenTextures (1, textures, 0 );
With the same parameters you can delete the textures:
// Delete a texture.
Gl. glDeleteTextures (1, textures, 0)
Now when the texture id's are generated we need to just like everything else tell OpenGL what to work with. With textures we use the command glBindTexture:
After IDs are generated, We need to bind these IDs in glBindTexture mode.
Gl. glBindTexture (GL10.GL _ TEXTURE_2D, textures [0]);
From this point all commands we call on regarding textures will be applied on to your texture with the generated id.
After that, we will use the generated ID to call the texture.
GlTexParameter
There is a couple of parameters we need to set on the texture, the first one is to tell OpenGL what to do if the texture need to be shrunk or magnified to match the rendered image.
In texture ing, we need to set several parameters. The first one is to tell OpenGL how to zoom out or zoom in to fit the image size.
If the texture is smaller it needs to be magnified that is done with the magnification function:
If the texture is small, use the zoom function to zoom in.
// Scale up if the texture if smaller.
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D,
GL10.GL _ TEXTURE_MAG_FILTER,
GL10.GL _ LINEAR );
And how to scale if the texture needs to be scaled down using the minification function.
Similarly, when there are too many textures, use the compression function to narrow down.
// Scale linearly when image smalled than texture
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D,
GL10.GL _ TEXTURE_MIN_FILTER,
GL10.GL _ LINEAR );
You need to pass an argument to these functions. I'm only going to show you two of them the rest you can investigate your self
Take a look at the above function. You can study the parameters that should be passed to it.
If you want a crisp and clean rendering like this image you need to use the GL10.GL _ NEAREST parameter.
If you want a clear rendering effect, you can use GL10.GL _ NEAREST.
If you rather want a blurred image you shoshould use the GL10.GL _ LINEAR parameter.
If you prefer to blur a bit, use GL10.GL _ LINEAR
UV Mapping
We will also need to tell OpenGL how to map this image onto the mesh this is done in two steps, fist we need to assign UV coordinates
Below we need to tell OpenGL how to map the image to the model, there are two steps. First, we specify a UV coordinate.
UV mapping is the way we map the pixels on the bitmap to the vertices in our mesh. the UV coordinates are 0, 0 in the upper left and 1, 1 is the bottom right, like the left image below. the right image below has strates how our plane is built. to get the texture
Mapped correctly we need to map the lower left part of the texture (0, 1) to the lower left vertex (0) in our plane and we need to map the bottom right (1, 1) in the texture to the bottom right (1) to the bottom right in our plane and... you get the idea.
We use UV ing to map each pixel of an image to the vertex of the model. In the UV coordinate, the upper left corner is 0, 0, and the lower right corner is 1, 1. See the left half. The right half is the plane we want to create. To ensure correct ing, we map the lower left corner of the texture to vertex 0 in the lower left corner, and the lower right corner to vertex 1... And so on.
Note: In the OpenGL tutorial, the lower left corner of the image is 0, 0. But here we use Android OpenGL ES. Maybe Android has some changes in interface encapsulation.
We put this mapping into a float array like this:
The texture coordinate array is defined as follows:
Float textureCoordinates [] = {0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f };
If we instead used 0.5 instead of 1.0 like this:
Float textureCoordinates [] = {0.0f, 0.5f,
0.5f, 0.5f,
0.0f, 0.0f,
0.5f, 0.0f };
The texture will be mapped so the plane will have the upper left part of it.
Map the upper left corner of the image to the plane.
Back to the gltexparameterf, if we go the other way and uses values higher then 1.0 like this:
Recall the gltexparameterf function. If we scale 1.0 to 2.0
Float texturecoordinates [] = {0.0f, 2.0f,
2.0f, 2.0f,
0.0f, 0.0f,
2.0f, 0.0f };
We actually tell OpenGL to use part of the texture that does not exist so we need to tell OpenGL what to do with the part that does not exist.
So what should OpenGL do if it exceeds the image position? This is exactly what we will discuss below.
We use the glTexParameterf function to tell OpenGL what to do with the texture. By default OpenGL uses something cal_repeat.
We use the glTexParameterf function to tell OpenGL how to map images. The default parameter item is GL_REPEAT.
GL_REPEATMeans that OpenGL shocould repeat the texture beyond 1.0.
GL_REPEAT means that OpenGL should repeat more than 1.0 of the textures
GL_CLAMP_TO_EDGEMeans that OpenGL only will draw the image once and after that just repeat the last pixel line the rest of the image.
GL_CLAMP_TO_EDGE indicates that OpenGL only draws an image once, and the remaining part will be repeated in pixels in the last line of the image.
Since we are working with a 2D texture so we need to tell OpenGL what to do in two ctions: GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T.
For a 2D texture, we also need to tell them the direction.
Below you see a chart with the 4 combinations of GL_REPEAT and GL_CLAMP_TO_EDGE.
See the four combinations below (the picture corresponding to the third combination is incorrect .)
|
|
WRAP_S: GL_REPEAT WRAP_T: GL_REPEAT |
WRAP_S: GL_REPEAT WRAP_T: GL_CLAMP_TO_EDGE |
|
|
Wrap_s: gl_repeat Wrap_t: gl_clamp_to_edge |
Wrap_s: gl_clamp_to_edge Wrap_t: gl_clamp_to_edge |
This is how we use the glTexParameterf function:
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D,
GL10.GL _ TEXTURE_WRAP_S,
GL10.GL _ REPEAT );
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D,
GL10.GL _ TEXTURE_WRAP_T,
GL10.GL _ REPEAT );
The last thing we need to do is to bind the bitmap we loaded to the texture id we created.
GLUtils. texImage2D (GL10.GL _ TEXTURE_2D, 0, bitmap, 0 );
Using the texture
To be able to use the texture we need just like with everything else create a byte buffer with the UV coordinates:
We also use byte buffering for UV coordinates.
FloatBuffer byteBuf = ByteBuffer. allocateDirect (texture. length * 4 );
ByteBuf. order (ByteOrder. nativeOrder ());
TextureBuffer = byteBuf. asFloatBuffer ();
TextureBuffer. put (textureCoordinates );
TextureBuffer. position (0 );
Rendering
// Telling OpenGL to enable textures.
Gl. glable (GL10.GL _ TEXTURE_2D );
// Tell OpenGL where our texture is located.
Gl. glBindTexture (GL10.GL _ TEXTURE_2D, textures [0]);
// Tell OpenGL to enable the use of UV coordinates.
Gl. glableclientstate (GL10.GL _ TEXTURE_COORD_ARRAY );
// Telling OpenGL where our UV coordinates are.
Gl. glTexCoordPointer (2, GL10.GL _ FLOAT, 0, textureBuffer );
//... Here goes the rendering of the mesh...
// Disable the use of UV coordinates.
Gl. glDisableClientState (GL10.GL _ TEXTURE_COORD_ARRAY );
// Disable the use of textures.
Gl. glDisable (GL10.GL _ TEXTURE_2D );
Putting it all together
I'm using a modified version of the code from the previous tutorial. the different is mostly that I renamed some variables and funables and added more comments and all code is now under Apache License. to make the code easier to understand I removed
Previous plane and added a new easier one called SimplePlane.
Updating the mesh class
The first thing we need to do is to update the Mesh class (se. jayway. opengl. tutorial. mesh. Mesh). We need to add the functionality to load and render a texture.
We need to be able to set and store the UV coordinates.
// Our UV Texture buffer.
PrivateFloatBuffer mTextureBuffer;
/**
* Set the texture coordinates.
*
* @ Param texturecoords
*/
ProtectedVoid setTextureCoordinates (float [] textureCoords ){
// Float is 4 bytes, therefore we multiply the number if
// Vertices with 4.
ByteBuffer byteBuf = ByteBuffer. allocateDirect (
TextureCoords. length * 4 );
ByteBuf. order (ByteOrder. nativeOrder ());
MTextureBuffer = byteBuf. asFloatBuffer ();
MTextureBuffer. put (textureCoords );
MTextureBuffer. position (0 );
}
We also need to add functions to set the bitmap and create the texture.
// Our texture ID.
PrivateInt mTextureId =-1;
// The bitmap we want to load as a texture.
PrivateBitmap mBitmap;
/**
* Set the bitmap to load into a texture.
*
* @ Param bitmap
*/
PublicVoid loadBitmap (Bitmap bitmap ){
This. MBitmap = bitmap;
MShouldLoadTexture =True;
}
/**
* Loads the texture.
*
* @ Param GL
*/
PrivateVoid loadGLTexture (GL10 gl ){
// Generate one texture pointer...
Int [] textures =NewInt [1];
Gl. glGenTextures (1, textures, 0 );
MTextureId = textures [0];
//... And bind it to our array
Gl. glBindTexture (GL10.GL _ TEXTURE_2D, mTextureId );
// Create nearest filtered texture
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D, GL10.GL _ TEXTURE_MIN_FILTER,
GL10.GL _ LINEAR );
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D, GL10.GL _ TEXTURE_MAG_FILTER,
GL10.GL _ LINEAR );
// Different possible texture parameters, e.g. gl10.gl _ clamp_to_edge
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D, GL10.GL _ TEXTURE_WRAP_S,
GL10.GL _ CLAMP_TO_EDGE );
Gl. glTexParameterf (GL10.GL _ TEXTURE_2D, GL10.GL _ TEXTURE_WRAP_T,
GL10.GL _ REPEAT );
// Use the android glutils to specify a two-dimen1_texture Image
// From our bitmap
GLUtils. texImage2D (GL10.GL _ TEXTURE_2D, 0, mBitmap, 0 );
}
And finally we need to add the call to the texture loading and to actually tell OpenGL to render with this texture. I removed some code so the page wocould not be so long but you will find the code complete in the attached zip file.
// Indicates if we need to load the texture.
PrivateBoolean mShouldLoadTexture =False;
/**
* Render the mesh.
*
* @ Param GL
* The OpenGL context to render.
*/
PublicVoid draw (GL10 gl ){
...
// Smooth color
If (mColorBuffer! =Null){
// Enable the color array buffer to be used during rendering.
Gl. glableclientstate (GL10.GL _ COLOR_ARRAY );
Gl. glColorPointer (4, GL10.GL _ FLOAT, 0, mColorBuffer );
}
If (mShouldLoadTexture ){
LoadGLTexture (gl );
MShouldLoadTexture =False;
}
If (mTextureId! =-1 & mTextureBuffer! =Null){
Gl. glable (GL10.GL _ TEXTURE_2D );
// Enable the texture state
Gl. glableclientstate (gl10.gl _ texture_coord_array );
// Point to our buffers
Gl. gltexcoordpointer (2, gl10.gl _ float, 0, mtexturebuffer );
Gl. glbindtexture (gl10.gl _ texture_2d, mtextureid );
}
Gl. gltranslatef (x, y, z );
...
// Point out the where the color buffer is.
Gl. glDrawElements (GL10.GL _ TRIANGLES, mNumOfIndices,
GL10.GL _ UNSIGNED_SHORT, mIndicesBuffer );
...
If (mTextureId! =-1 & mTextureBuffer! =Null){
Gl. glDisableClientState (GL10.GL _ TEXTURE_COORD_ARRAY );
}
...
}
Creating the SimplePlane class
We also need to create the SimplePlane. java. The code is pretty simple and self-explaining if you have read my previous tutorials. The new element is the textureCoordinates variable.
PackageSe. jayway. opengl. tutorial. mesh;
/**
* SimplePlane is a setup class for Mesh that creates a plane mesh.
*
* @ Author Per-Erik Bergman (per-erik.bergman@jayway.com)
*
*/
Public classSimplePlaneExtendsMesh {
/**
* Create a plane with a default with and height of 1 unit.
*/
PublicSimplePlane (){
This(1, 1 );
}
/**
* Create a plane.
*
* @ Param width
* The width of the plane.
* @ Param height
* The height of the plane.
*/
PublicSimplePlane (float width, float height ){
// Mapping coordinates for the vertices
Float textureCoordinates [] = {0.0f, 2.0f,//
2.0f, 2.0f,//
0.0f, 0.0f,//
2.0f, 0.0f,//
};
Short [] indices =NewShort [] {0, 1, 2, 1, 3, 2 };
Float [] vertices =NewFloat [] {-0.5f,-0.5f, 0.0f,
0.5f,-0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f };
SetIndices (indices );
SetVertices (vertices );
SetTextureCoordinates (textureCoordinates );
}
}
References
The info used in this tutorial is collected from:
Android Developers
OpenGL ES 1.1 Reference Pages
You can download the source for this tutorial here:
Tutorial_Part_VI
You can also checkout the code from:
Code.google.com
Previous tutorial:
OpenGL ES Tutorial for Android-Part V-More on Meshes
Per-Erik Bergman
Consultant at Jayway