Saturday, December 13, 2008

NeHe Tutorials, Lesson 6 Ported to iPhone (sort of)

I've ported NeHe Lesson 06. Sort of.

You see, OpenGL ES 1.1 doesn't support quads. NeHe selected a pyramid and a cube because they are easy shapes under OpenGL proper. However, the only polygon that OpenGL ES 1.1 knows about is the triangle. There are several variations on it - triangle strips, triangle fans, and triangles. As you saw when we ported Lesson 5, we had to split each quad into two triangles. That wasn't so bad when we were just drawing flat colors, but now that we're looking to map textures, it becomes a whole heck of a lot harder, because mapping a square texture to a triangle is, quite frankly, a pain in the ass.

Here is the link for the ported Lesson 6 project.

I only mapped two of the sides - front and back, the other aren't done and won't display right. It took me the better part of an hour before I finally gave up mapping all six sides. I hate releasing this unfinished, but I just can't afford the time right now to manually map the textures to all the triangle that make up the cube. Fortunately, there's rarely a need to manually map texture coordinates any more. Most 3D programs will export the texture coordinates for you, so all we really need to take from Lesson 06 is how to load the textures into memory and how to make them active. You should read the NeHe tutorial so you understand how texture mapping works, but it doesn't make sense to spend a lot of time on the process. In a future posting, we'll see how to take a 3D object created in Blender and import the texture coordinates along with the vertex data. In practice, it would be rare for you to manually map textures to individual polygons like this.

In Lesson 06, we have our first instance variable in the controller class. Because I needed to access the textures array from multiple methods, I couldn't just make another static variable. So, we now have an array of integers, and these integers will each refer to a texture loaded into OpenGL.
GLuint  texture[1];      // Storage For One Texture ( NEW ) 
In our setupView method, we prepare our texture just like is done in the NeHe Lesson 06
 glGenTextures(1, &texture[0]);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
We have to vary a little from the plan to actually load the texture from file. Instead of a .bmp file, we're going to load that compressed texture file I had you create in this blog posting. If you didn't do that, go do it now. You'll want to drag the .pvr4 file into the /Resources group of your project.

Once you've got the project in your texture, we can load it in our setupView method:
 NSString *path = [[NSBundle mainBundle] pathForResource:@"NeHe" ofType:@"pvr4"];
NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
// Instead of glTexImage2D, we have to use glCompressedTexImage2D
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 256.0, 256.0, 0, (256.0 * 256.0) / 2, [texData bytes]);
[texData release];
A couple of things to notice - first, because our texture image is compressed, we had to use glCompressedTexImage2D() rather than glTexImage2D() as was done in the NeHe tutorial. The other thing is that we release the image data after we feed it to OpenGL - it creates its own copy of the texture, so we have to release to avoid leaking memory.

After that, the rest of the setup is pretty much the same as in the original tutorial:
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// Enable blending
glEnable(GL_BLEND);
Where things get really different is in the drawView method. Some of you probably saw this coming, but we can't use glTexCoord2f() because we can't use glBegin() and glEnd() on the iPhone.

The solution is the same as before - we stuff all those texture coordinates into an array called (wait for it) a texture coordinate array. Texture coordinates in the array are just like the values you would pass to glTexCoord2f() - values between 0.0 and 1.0. Refer to the original tutorial for a description of how that mapping works. It's a little hard to grok. If you don't fully get it, don't worry - before long, we're going to write an object-loader that will handle that for us, and then we'll never have to remember how it works again. Here is the texture coordinate array I created for this lesson, but remember that only the first 16 values (corresponding to the first four polygons that make up the front and back of the cube) are actually valid and mapped.
 const GLfloat cubeTextureCoords[] = {
1.0, 1.0, // Front
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,

0.0, 0.0, // Back
0.0, 1.0,
1.0, 1.0,
1.0, 0.0,

0.0, 0.0, // Top
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,

0.0, 0.0, // Bottom
0.0, 1.0,
1.0, 1.0,
1.0, 1.0,

0.0, 0.0, // Left
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,

0.0, 0.0, // Right
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
};
Before we make any calls to glDrawElements(), we have to tell OpenGL about the texture coordinates, and we do that using a function called glTexCoordPointer(), like so:
glTexCoordPointer(2, GL_FLOAT, 0, cubeTextureCoords);
The first argument to this function says that we have two mapping values for each element to be drawn. The second argument tells OpenGL that the texture array contains floating point values, the third is that parameter I keep telling you to ignore and just provide 0 for, and the last is the pointer to the actual texture coordinate array.

Before you can call glDrawElements, you have to enable texture coordinate arrays, by making this call:
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
Like the other enable functions, you can call this once during setup, or you can enable and disable it during application execution. Because only one of our two objects is getting mapped, we enable it before we draw the cube, and disable it afterwards, which will keep the pyramid from getting mapped as well.

In order for texture mapping to work, we must also call
glEnable(GL_TEXTURE_2D);
This only has to be called once in setup, or you can also enable and disable it during application execution, though typically it's just turned on at the beginning.

That's pretty much it. When we call glDrawElements(), it will automatically fetch the coordinate data and map the last texture that was bound. We only have one, which we bound during setup by calling glGenTextures() and glBindTexture().

If we had a lot of textures (which you will in most situations), you would typically load them all in during setup, and then activate different ones by making subsequent calls to glBindTexture(), which does double duty, both creating, and activating existing textures. The last one that was passed to glBindTextures() is the one that will get mapped when you draw.

This was a tough lesson to port. They opted to map the cube because it was easy - square sides, square textures, very simple mapping. We're not that lucky on the iPhone, so I hope you'll forgive the fact that this one is incomplete. In return, I promise to show you how to load objects with texture maps. Probably not for a week or two, though.



6 comments:

Autonaut said...

first let me say Thanks! for these great tutorials - they've been helping me alot.

I think I've figured out why the textures aren't aligning correctly in this example. The problem is that when using glDrawElements() with the indices in cubeVertexFaces[36], opengl es will search for the appropriate texture coordinate in cubeTextureCoords at the index it is using to find the vertice. But you didn't index the texcoords. So opengl has no way to know wich textcoord to use with a given vertex. Regardless, indexing the textcoords like the vertices wouldn't work anyway, because every pair vertex/texcoord is unique.

Bascially what I would suggest is to not use indexing and use glDrawArrays instead. You can generate the vertice array form the indices like so:

GLfloat cubeVertexArray[108];
for(int i = 0; i < 36; i++) {
for(int j = 0; j < 3; j++) {
cubeVertexArray[(i * 3) + j] = cubeVertices[(cubeVertexFaces[i] * 3) + j];
}
}

But you will have to go ahead and define each of the 108 texcoords manually. The first of them are:

const GLfloat cubeTextureCoords[] = {
// Half of front face
0.0, 0.0, // 0
1.0, 0.0, // 1
1.0, 1.0, // 2
// Other half of front face
0.0, 0.0, // 0
0.0, 1.0, // 3
1.0, 1.0, // 2
//etc.

Notice that we need one texcoord for each vertice of each triangle. What is really weird is that texture coordinates are referenced with 0,0 at top left and 1,1 at bottom right. If anybody knows a way to change this, I'd be greatful for a hint...

You then render the box like this:
glTexCoordPointer(2, GL_FLOAT, 0, cubeTextureCoords);
glVertexPointer(3, GL_FLOAT, 0, cubeVertexArray);
glDrawArrays(GL_TRIANGLES, 0, 36);

Autonaut said...

ahh... just saw you posted abour this here

CLaFrieda said...

static const GLfloat cubeVertices[] = {
-1.0f, 1.0f, -1.0f, // top-upper-right
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,

-1.0f, 1.0f, -1.0f, // top-lower-left
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,

-1.0f, 1.0f, 1.0f, // front-upper-right
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,

-1.0f, 1.0f, 1.0f, // front-lower-left
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,

-1.0f, -1.0f, 1.0f, // bottom-upper-right
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,

-1.0f, -1.0f, 1.0f, // bottom-lower-left
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,

-1.0f, -1.0f, -1.0f, // back-upper-right
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,

-1.0f, -1.0f, -1.0f, // back-lower-left
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,

-1.0f, 1.0f, -1.0f, // left-upper-right
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,

-1.0f, 1.0f, -1.0f, // left-lower-left
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,

1.0f, 1.0f, 1.0f, // right-upper-right
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,

1.0f, 1.0f, 1.0f, // right-lower-left
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
};

const GLfloat cubeTextureCoords[] = {
0.0f, 1.0f, // top-upper-right
1.0f, 0.0f,
0.0f, 0.0f,

0.0f, 1.0f, // top-lower-left
1.0f, 1.0f,
1.0f, 0.0f,

0.0f, 0.0f, // front-upper-right
1.0f, 1.0f,
1.0f, 0.0f,

0.0f, 0.0f, // front-lower-left
0.0f, 1.0f,
1.0f, 1.0f,

0.0f, 0.0f, // bottom-upper-right
1.0f, 1.0f,
1.0f, 0.0f,

0.0f, 0.0f, // bottom-lower-left
0.0f, 1.0f,
1.0f, 1.0f,

0.0f, 1.0f, // back-upper-right
1.0f, 0.0f,
0.0f, 0.0f,

0.0f, 1.0f, // back-lower-left
1.0f, 1.0f,
1.0f, 0.0f,

0.0f, 0.0f, // left-upper-right
1.0f, 1.0f,
1.0f, 0.0f,

0.0f, 0.0f, // left-lower-left
0.0f, 1.0f,
1.0f, 1.0f,

1.0f, 0.0f, // right-upper-right
0.0f, 1.0f,
1.0f, 1.0f,

1.0f, 0.0f, // right-lower-left
0.0f, 0.0f,
0.0f, 1.0f,
};

...

glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
glTexCoordPointer(2, GL_FLOAT, 0, cubeTextureCoords);
glDrawArrays(GL_TRIANGLES, 0, 36);

...

That should set it up so that NeHe is always correctly oriented (I never did the original tutorial, so I guess that is what it should look like). I can confirm that (0,0) is the upper left for the texture coordinate. There may be a clever way to generate the data in a for loop, but it's not too hard to do manually once you draw a diagram.

abraginsky said...

How would one apply this to generating a 2D polygonal mesh for texture mapping? What I want to do is create a texture map and then deform it along both the x and y axes. The problem I'm running into is that building the mesh with arrays ends up duplicating top/bottom vertices and thus you get horizontal bands of constant height that tear the texture when displaced vertically.

See http://www.zeuscmd.com/tutorials/opengles/21-WavingFlag.php for example of how I generated the mesh.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels