Tutorial 3-Texturing and multitexturing
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Tutorial 3: Texturing
This tutorial will cover one of the most important features of every 3D API: texturing. Without it, our applications won‟t look realistic, because we would have to use smoothed colors for all things, and a lot of triangles. This is not really a good solution, so this is why texturing was …invented‟.
After a brief theoretical introduction about texture objects, texture creation, and texture mapping, we are going to write a simple program that loads a texture from file and maps it over a mesh (the same cube of the tutorial 2). Then we will learn how to use a new capacity: multitexturing. Multitexture means, apply two or more textures over the same triangle at the same time, combining them in different manners. Let‟s start.
First we‟v e removed the handling code for the clicks management, at WndProc function. We won‟t change from ortho to perspective anymore.
We need a function that takes a path to a texture image and load it as an OpenGL ES texture. We will load TGA textures, but only those ones that has 24 bits and are uncompressed.
In the render.h file, this is new function:
bool LoadTexture(const char*fileName, GLuint *id); //function to load 24-bit uncompressed TGA textures
Let‟s talk about this new function for a while. This fu nction will receive a texture filename and a pointer to a texture identifier. This identifier will be initialized from inside the function (if proceed). This function will return true if the texture was successfully loaded, otherwise will return false.
bool LoadTexture(const char *fileName, GLuint *id)
{
FILE *f = fopen(fileName, "rb");
GLubyte *pixels = NULL;
if(!f) return false;
WORD width = 0, height = 0;
byte headerLength = 0;
byte imageType = 0;
byte bits = 0;
int format= 0;
int lineWidth = 0;
fread(&headerLength, sizeof(byte), 1, f);
//skip next byte
fseek(f,1,SEEK_CUR);
//read in the imageType (RLE, RGB, etc...)
fread(&imageType, sizeof(byte), 1, f);
//skip information we don't care about
fseek(f, 9, SEEK_CUR);
/*read the width, height and bits per pixel (16, 24 or 32). We only will take care of 24 bits uncompressed TGAs*/
fread(&width, sizeof(WORD), 1, f);
fread(&height, sizeof(WORD), 1, f);
fread(&bits, sizeof(byte), 1, f);
//move the file pointer to the pixel data
fseek(f, headerLength + 1, SEEK_CUR);
//check if the image is not compressed.
if(imageType != 10)
{
//check if the image is a 24
if(bits == 24)
{
/*Another (faster) way to divide between 8. We want to know the pixel size in bytes.*/
format = bits >> 3;
lineWidth = format * width;
pixels = new GLubyte[lineWidth * height];
//we are going to load the pixel data line by line
for(int y = 0; y < height; y++)
{
//Read current line of pixels
GLubyte *line = &(pixels[lineWidth * y]);
fread(line, lineWidth, 1, f);
/*Because the TGA is BGR instead of RGB, we must swap the R
and G components (OGL ES does not have the
GL_BGR_EXT extension*/
for(int i=0;i<lineWidth ; i+=format)
{
int temp = line[i];
line[i] = line[i+2];
line[i+2] = temp;
}
}
}
else
{
fclose(f);
*id = 0;
return false;
}
}
fclose(f);
/*Creation of the OpenGL Texture:
OpenGL ES texturing only allows 2D textures (1D textures could be
easily achieved, using '1' as height parameter in the glTexImage2D call).
Allowed filtering modes are:
GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST,
GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR
and GL_LINEAR_MIPMAP_LINEAR.
Because OpenGL ES is written against OpenGL 1.5, it supports
automatic mipmap generation, using the next call:
glTexParameterx(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
Allowed clamping modes are:
GL_REPEAT and GL_CLAMP_TO_EDGE (GL_CLAMP, GL_CLAMP_TO_BORDER and
GL_MIRRORED_REPEAT are not supported)
Allowed formats are:
Texture borders are not suported at all
glTexParameter{if|v} was removed and only are supported the float and the (new) fixed point version (that is what we will use) */ //Ask for a free texture name
glGenTextures(1, id);
/*first binding implies the texture object creation*/
glBindTexture(GL_TEXTURE_2D, *id);
/*Set texture properties: filtering and clamping modes*/
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
/*Upload pixels to the texture object*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
GL_UNSIGNED_BYTE, pixels);
/*Our copy in main main memory is no longer needed*/
delete [] pixels;
return true;
}
With this we have the texture ready to be used. Let‟s see how to.
Add this line at the end of the InitOGLES function:
In the top of the render.cpp file (under all other variable declarations), declare this variable:
Gluint texture1 = 0;
bool result = LoadTexture("./resources/door128.tga",&texture1);
We call the texture handle “texture1” because, later, we will learn how to use multitexture, and for that, we will need another texture (texture2)
This call to LoadTexture will search for the texture filename that we are passing into, and will load and update the texture and it‟s texture handle (texture1).
Well, it is the perfect moment to talk about how to map a texture onto a polygon using texture coordinates. If you already know that, you can jump around the next section safely.
Texture mapping
Textures are rectangular (and it‟s dimensions must be power of two), but usually we use triangles to draw, or even worse, complex meshes. Anyway, rare times the mesh has the same dimensions that the texture has, so, how could we …glue‟ a set of texels (texture pixels) over an arbi trary mesh?. The answer are the texture coordinates. We will parameterize our textures between 0 and 1. Let‟s see an example:
This is our texture, with its dimensions at each vertex.
Now, this is our texture with its dimensions parameterized:
As you can see, we‟ve converted the dimensions to a number between 0 and 1 (0 = origin in this axis, 1 = “end” in this axis. All intermediate numbers represent the intermediate values, for example, if we map from 0 to 0.5 we will see only the texture …s left-half portion). This allow us to use textures without taking care of it‟s real size. If the texture from above would have a dimensions of 1024x1024 instead of 128x128, the texture coordinates would be the same: 0 or 1 (or a fractional number between them) Giving texture coordinates at each vertex, OpenGL ES can take the correct texels and paint correctly the texture over the primitive. If we use a quad as an example, and we want create texture coordinates for it, we can use directly those that are in the last image of this page . If we use a triangle, it is obvious that we cannot map the entire texture over the triangle, so we have four possibilities:
Conclusion, when we want draw a texture over a primitive, we have to specify, texture coordinates at each vertex. This can be done manually but it is a really hesitating work. In most cases, the modeling program used to create the mesh, provides it with a set of texture coordinates.
The only thing that left to see is how pass to OpenGL ES the texture coordinates, so, let‟s examine the new Render function.
void Render()
{
static int rotation = 0;
static GLubyte front[] = {2,1,3,0}; //front face
static GLubyte back[] = {5,6,4,7}; //back face
static GLubyte top[] = {6,2,7,3}; //top face
static GLubyte bottom[] = {1,5,0,4}; //bottom face
static GLubyte left[] = {3,0,7,4}; //left face
static GLubyte right[] = {6,5,2,1}; //right face
static GLshort vertices[] = {-5,-5,-5, 5,-5,-5, 5,5,-5, -5,5,-5, -5,-5,5, 5,-5,5, 5,5,5, -5,5,5};
static GLfixed texCoords[] = {0,0, ONE,0, ONE,ONE, 0,ONE,
ONE,ONE , 0,ONE, 0,0, ONE,0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatex(0, 0, FixedFromInt(-30));
glRotatex(FixedFromInt(45), ONE, 0, 0);
glRotatex(FixedFromInt(rotation++), 0, ONE,0);
//Enable the vertices array
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_SHORT, 0, vertices);
//3 = XYZ coordinates, GL_SHORT = data type, 0 = 0 stride bytes
/*enable texture coordinates array. By this way we pass the texture coordinates to OpenGL ES*/
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
//enable texturing
glEnable(GL_TEXTURE_2D);
//enable our first texture
glBindTexture(GL_TEXTURE_2D, texture1);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, front);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, back);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, top);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, bottom);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, left);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, right);
//disable texturing
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
eglSwapBuffers(glesDisplay, glesSurface);
}
This render function should generate a render like this:
In render you can see, in the top face how the texture is twisted. It is because two vertices has the same texture coordinate, or has two texture coordinates swapped (I did that intentionally, to show the effect of what happen when the texture coordinates aren‟t rightly configured).
Now, you know all about texturing (single texturing, of course). In the next section we will modify the code slightly to blend two textures using multitexture.
One can thing: “Well, I can put a texture over my mesh, but if I put another one, won‟t this last texture overlap the first one?”. The answer is yes, but only if you want that behavior. There are many options, one of them is the decal mode. In this mode the second texture will overlap the first one. Another mode is blending. We can blend both textures using some operations: add, multiply, etc. Let‟s see how to add both textures.
There are one thing called Texture Unit (forwards: TU) TU are the …places‟ where textures are …placed‟. When OpenGL ES applies a texture, it applies all textures placed in the, currently enabled, TU, using the current formula (decal, blending, etc) In order to achieve multitexture we must setup correctly all TU, give the blending mode (decal could be though as an special case of blending) and enable the TU‟s we are going to use.
By default we work over the TU0 (a minimum of 2 texture units are provided with OpenGL ES. You can know how many TU has your implementation with the right glGet and GL_MAX_TEXTURE_UNITS), so we must change to another texture unit. This is done with glActiveTexture(GL_TEXTURE0 + I) and glClientActiveTexture(GL_TEXTURE0 + I), where I is the number of the TU we want change to (we can use GL_TEXTURE1, GL_TEXTURE2… etc). After activating the right TU with glEnable(GL_TEXTURE_2D); , we must bind the texture object that we want store in this TU.
Also we must specify the texture coordinates for each TU, with glEnableClientState and glTexCoordArray calls. (Remember back to TU0 when finish the drawing)
The code should looks like this:
glActiveTexture(GL_TEXTURE0);
glClientActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
glActiveTexture(GL_TEXTURE1);
glClientActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture2);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
Also, we will enable the ADD mode. This mode will add both textures, component by component.
glTexEnvx(GL_TEXTURE_ENV , GL_TEXTURE_ENV_MODE, GL_ADD);
We can make a nice animation effect over the textures, simply modifying the texture coordinates in each frame, adding a variable value, in horizontal or
in vertical axis. There are many ways. One of them could be do it manually, modifying the texcoord vector, or another, the best one, modifying the texture matrix associated to the TU (every TU has it‟s texture matrix). The texture matrix works exactly like the modelview matrix. It holds some transformations that will be applied to all texture coordinates.
To do this, first, active the TU you want to …animate‟, then change to texture matrix, and perform the animation:
static float offset = 0.0f; //I’m aware that this is bad habit glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslatex(FixedFromFloat(offset+=0.01f),0,0);
When finish, go back to modelview matrix:
glMatrixMode(GL_MODELVIEW);
Here is the Render function modified to achieve multitexturing:
void Render()
{
static int rotation = 0;
static GLubyte front[] = {2,1,3,0}; //front face
static GLubyte back[] = {5,6,4,7}; //back face
static GLubyte top[] = {6,2,7,3}; //top face
static GLubyte bottom[] = {1,5,0,4}; //bottom face
static GLubyte left[] = {3,0,7,4}; //left face
static GLubyte right[] = {6,5,2,1}; //right face
static GLshort vertices[] = {-5,-5,-5, 5,-5,-5, 5,5,-5, -5,5,-5, -5,-5,5, 5,-5,5, 5,5,5, -5,5,5};
static GLfixed texCoords[] = {0,0, ONE,0, ONE,ONE, 0,ONE, ONE,ONE ,0,ONE, 0,0, ONE,0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatex(0, 0, FixedFromInt(-30));
glRotatex(FixedFromInt(45), ONE, 0, 0);
glRotatex(FixedFromInt(rotation++), 0, ONE,0);
//Enable the vertices array
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_SHORT, 0, vertices);
//3 = XYZ coordinates, GL_SHORT = data type, 0 = 0 stride bytes
glActiveTexture(GL_TEXTURE0);
glClientActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
glActiveTexture(GL_TEXTURE1);
glClientActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture2);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
static float offset = 0.0f;
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslatex(FixedFromFloat(offset+=0.01f),0,0);
glMatrixMode(GL_MODELVIEW);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, front);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, back);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, top);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, bottom); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, left);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, right);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glActiveTexture(GL_TEXTURE0);
glClientActiveTexture(GL_TEXTURE0);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
eglSwapBuffers(glesDisplay, glesSurface);
}
Typhoon Labs OpenGL ES tutorials
Real Time Technologies
TyphoonLabs typhoonlabs@ As a final word, remember upload the texture files to the PDA or the emulator to the ./resources folder. Otherwise, won‟t work.
Textures used:
Render:。