OpenGL ES 2.0 for iPhone Tutorial Part 2: Textures

In this tutorial series, our aim is to take the mystery and difficulty out of OpenGL ES 2.0, by giving you hands-on experience using it from the ground up! In the first part of the series, we covered the basics of initializing OpenGL, creating some simple vertex and fragment shaders, and presenting a simple rotating […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Using the Texture Data

Now that we have a helper method to load an image, send it to OpenGL, and return to us its texture name, let’s make use of this to skin our cube.

Let’s start with our vertex and fragment shaders. Open up SimpleVertex.glsl and replace it with the following:

attribute vec4 Position; 
attribute vec4 SourceColor; 

varying vec4 DestinationColor; 

uniform mat4 Projection;
uniform mat4 Modelview;

attribute vec2 TexCoordIn; // New
varying vec2 TexCoordOut; // New

void main(void) { 
    DestinationColor = SourceColor; 
    gl_Position = Projection * Modelview * Position;
    TexCoordOut = TexCoordIn; // New
}

Here we declare a new attribute called TexCoordIn. Remember that an attribute is a value that you can set for each vertex. So for each vertex, we’ll specify the coordinate on the texture that it should map to.

Texture Coordinates are kind of weird in that they’re always in the range of 0-1. So (0,0) would be the bottom left of the texture, and (1,1) would be the upper right of the texture.

Or it would be, but Core Graphics flips images when you load them in. So in the code here (0,1) is actually the bottom left and (0, 0) is the top left, oddly enough.

We also declare a new varying called TexCoordOut, and set it to TexCoordIn. Remember that a varying is a value that OpenGL will automatically interpolate for us by the time it gets to the fragment shader. So for example if we set the bottom left corner of a square we’re texturing to (0,0) and the bottom right to (1, 0), if we’re rendering the pixel in-between on the bottom, our fragment shader will be automatically passed (0.5, 0).

Next replace SimpleFragment.glsl with the following:

varying lowp vec4 DestinationColor;

varying lowp vec2 TexCoordOut; // New
uniform sampler2D Texture; // New

void main(void) {
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut); // New
}

We used to set the output color to just the destination color – now we multiply the color by whatever is in the texture at the specified coordinate. texture2D is a built-in GLSL function that samples a texture for us.

Now that our new shaders are ready to go, let’s make use of them! Open up OpenGLView.h and add the following new instance variables:

GLuint _floorTexture;
GLuint _fishTexture;
GLuint _texCoordSlot;
GLuint _textureUniform;

These will keep track of the texture names for our two textures, the new input attribute slot, and the new texture uniform slot.

Then open up OpenGLView.m and make the following changes:

// Add texture coordinates to Vertex structure as follows
typedef struct {
    float Position[3];
    float Color[4];
    float TexCoord[2]; // New
} Vertex;

// Add texture coordinates to Vertices as follows
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, 0}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}},
    {{1, -1, -1}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, -1}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}}
};

// Add to end of compileShaders
_texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn");
glEnableVertexAttribArray(_texCoordSlot);
_textureUniform = glGetUniformLocation(programHandle, "Texture");

// Add to end of initWithFrame
_floorTexture = [self setupTexture:@"tile_floor.png"];
_fishTexture = [self setupTexture:@"item_powerup_fish.png"];

// Add inside render:, right before glDrawElements
glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, 
    sizeof(Vertex), (GLvoid*) (sizeof(float) * 7));    
    
glActiveTexture(GL_TEXTURE0); 
glBindTexture(GL_TEXTURE_2D, _floorTexture);
glUniform1i(_textureUniform, 0);

Based on what we covered in the tutorial series so far, most of this should be straightforward.

The only thing worth mentioning in more detail is the final three lines. This is how we set the “Texture” uniform we defined in our fragment shader to the texture we loaded in code.

First, we activate the texture unit we want to load our texture into. On iOS, we’re guaranteed to have at least 2 texture units, and most of the time 8. This can be good if you need to perform computations on more than one texture at a time. However, for this tutorial, we don’t really need to use more than one texture unit at a time, so we’ll just stick the first texture unit (GL_TEXTURE0).

We then bind the texture into the current texture unit (GL_TEXTURE0). Finally, we set the texture uniform to the index of the texture unit it’s in (0).

Note that lines 1 and 3 aren’t strictly necessary, and a lot of times you’ll see code that doesn’t even include those lines. This is because it’s assuming GL_TEXTURE0 is already the active texture unit, and doesn’t bother setting the uniform because it defaults to 0 anyway. However, I’m including the lines here because I think it makes it a lot easier to understand for beginners.

Compile and run the code, and you’ll see a textured cube!

Reused vertices with same texture coordinates cause strange stretching

Well… sort of. The front of the cube looks good, but the sides of the cube look wonky and stretched – what’s going on with that?

Fixing the Stretch Effect

The problem is we currently are just setting one texture coordinate per vertex, and reusing vertices.

For example, we map the bottom left corner of the front face to (0,0). But on the left side, that same vertex is the bottom right, so it doesn’t make sense to have a (0,0) texture coordinate – it should be (1,0).

In OpenGL, you can’t think of a vertex as just its vertex coordinates – it’s the unique combination of its coordinate, color, texture coordinate, and anything else you have in your structure.

Go ahead and replace your Vertices and Indices arrays with the following, which make a different vertex/color/texture coord combo for each face to ensure the texture coordinates map properly:

#define TEX_COORD_MAX   1

const Vertex Vertices[] = {
    // Front
    {{1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Back
    {{1, 1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{-1, -1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, -1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Left
    {{-1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}}, 
    {{-1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Right
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Top
    {{1, 1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Bottom
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, -1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}}, 
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}}
};

const GLubyte Indices[] = {
    // Front
    0, 1, 2,
    2, 3, 0,
    // Back
    4, 5, 6,
    4, 5, 7,
    // Left
    8, 9, 10,
    10, 11, 8,
    // Right
    12, 13, 14,
    14, 15, 12,
    // Top
    16, 17, 18,
    18, 19, 16,
    // Bottom
    20, 21, 22,
    22, 23, 20
};

Just like last time, I got these by sketching out the cube on paper and figuring them out – it’s a good exercise if you want to try it yourself.

Note we’re repeating a lot more data than we did last time. I don’t know a smart way around this, while still allowing the texture coordinates to be different (but if someone else knows please chime in!)

Compile and run, and now we have a beautiful looking textured cube!

A beautiful textured cube made with OpenGL ES 2.0

Contributors

Over 300 content creators. Join our team.