O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Reflections with Cube Maps

You might recall a technique presented in Chapter 6, where we rendered an upside-down object to simulate reflection. This was sufficient for reflecting a limited number of objects onto a flat plane, but if you’d like the surface of a 3D object to reflect a richly detailed environment, as shown in Figure 8-6, a cube map is required. Cube maps are special textures composed from six individual images: one for each of the six axis-aligned directions in 3D space. Cube maps are supported only in OpenGL ES 2.0.

Reflection sample

Figure 8-6. Reflection sample

Cube maps are often visualized using a cross shape that looks like an unfolded box, as shown in Figure 8-7.

Cube map cross of the Minnehaha Falls (courtesy of Edward Fink)

Figure 8-7. Cube map cross of the Minnehaha Falls (courtesy of Edward Fink)

The cross shape is for the benefit of humans only; OpenGL does expect it when you give it the image data for a cube map. Rather, it requires you to upload each of the six faces individually, like this:

glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, mip, format, 
             w, h, 0, format, type, data[0]);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, mip, format,
             w, h, 0, format, type, data[1]);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, mip, format, 
             w, h, 0, format, type, data[2]);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, mip, format,
             w, h, 0, format, type, data[3]);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, mip, format,
             w, h, 0, format, type, data[4]);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, mip, format,
             w, h, 0, format, type, data[5]);

Note that, for the first time, we’re using a texture target other than GL_TEXTURE_2D. This can be a bit confusing because the function call name still has the 2D suffix. It helps to think of each face as being 2D, although the texture object itself is not.

The enumerants for the six faces have contiguous values, so it’s more common to upload the faces of a cube map using a loop. For an example of this, see Example 8-8, which creates and populates a complete mipmapped cube map.

Example 8-8. CreateCubemap function

GLuint CreateCubemap(GLvoid** faceData, int size, GLenum format, GLenum type)
{
    GLuint textureObject;
    glGenTextures(1, &textureObject);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureObject);
    for (int f = 0; f < 6; ++f) {
        GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + f;
        glTexImage2D(face, 0, format, size, size, 0, format, type, faceData[f]);
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, 
                    GL_TEXTURE_MIN_FILTER, 
                    GL_LINEAR_MIPMAP_LINEAR); 
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    return textureObject;
}

Note

Example 8-8 is part of the rendering engine in a sample app in this book’s downloadable source code (see How to Contact Us).

In Example 8-8, the passed-in size parameter is the width (or height) of each cube map face. Cube map faces must be square. Additionally, on the iPhone, they must have a size that’s a power-of-two.

Example 8-9 shows the vertex shader that can be used for cube map reflection.

Example 8-9. Vertex shader (cube map sample)

attribute vec4 Position;
attribute vec3 Normal;

uniform mat4 Projection;
uniform mat4 Modelview;
uniform mat3 Model;
uniform vec3  EyePosition;

varying vec3 ReflectDir;

void main(void)
{
    gl_Position = Projection * Modelview * Position;
    
    // Compute eye direction in object space:
    mediump vec3 eyeDir = normalize(Position.xyz - EyePosition);

    // Reflect eye direction over normal and transform to world space:
    ReflectDir = Model * reflect(eyeDir, Normal);
}

Newly introduced in Example 8-9 is GLSL’s built-in reflect function, which is defined like this:

float reflect(float I, float N)
{
    return I - 2.0 * dot(N, I) * N;
}

N is the surface normal; I is the incident vector, which is the vector that strikes the surface at the point of interest (see Figure 8-8).

The GLSL “reflect” function

Figure 8-8. The GLSL “reflect” function

Note

Cube maps can also be used for refraction, which is useful for creating glass or other transparent media. GLSL provides a refract function to help with this.

The fragment shader for our cube mapping example is fairly simple; see Example 8-10.

Example 8-10. Fragment shader (cube map sample)

varying mediump vec3 ReflectDir;

uniform samplerCube Sampler;

void main(void)
{
    gl_FragColor = textureCube(Sampler, ReflectDir);
}

Newly introduced in Example 8-10 is a new uniform type called samplerCube. Full-blown desktop OpenGL has many sampler types, but the only two sampler types supported on the iPhone are samplerCube and sampler2D. Remember, when setting a sampler from within your application, set it to the stage index, not the texture handle!

The sampler function in Example 8-10 is also new: textureCube differs from texture2D in that it takes a vec3 texture coordinate rather than a vec2. You can think of it as a direction vector emanating from the center of a cube. OpenGL finds which of the three components have the largest magnitude and uses that to determine which face to sample from.

A common gotcha with cube maps is incorrect face orientation. I find that the best way to test for this issue is to render a sphere with a simplified version of the vertex shader that does not perform true reflection:

//ReflectDir = Model * reflect(eyeDir, Normal);
ReflectDir = Model * Position.xyz; // Test the face orientation. 

Using this technique, you’ll easily notice seams if one of your cube map faces needs to be flipped, as shown on the left in Figure 8-9. Note that only five faces are visible at a time, so I suggest testing with a negated Position vector as well.

From left to right: incorrect face orientation, corrected faces, corrected faces with reflection

Figure 8-9. From left to right: incorrect face orientation, corrected faces, corrected faces with reflection

Render to Cube Map

Instead of using a presupplied cube map texture, it’s possible to generate a cube map texture in real time from the 3D scene itself. This can be done by rerendering the scene six different times, each time using a different model-view matrix. Recall the function call that attached an FBO to a texture, first presented in A Super Simple Sample App for Supersampling:

GLenum attachment = GL_COLOR_ATTACHMENT0;
GLenum textureTarget = GL_TEXTURE_2D;
GLuint textureHandle = myTextureObject;
GLint mipmapLevel = 0;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, 
                       textureTarget, textureHandle, mipmapLevel);

The textureTarget parameter is not limited to GL_TEXTURE_2D; it can be any of the six face enumerants (GL_TEXTURE_CUBE_MAP_POSITIVE_X and so on). See Example 8-11 for a high-level overview of a render method that draws a 3D scene into a cube map. (This code is hypothetical, not used in any samples in the book’s downloadable source code.)

Example 8-11. Rendering to a cube map

glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
glViewport(0, 0, fboWidth, fboHeight);

for (face = 0; face < 6; face++) {

    // Change the FBO attachment to the current face: 
    GLenum textureTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                           textureTarget, textureHandle, 0);

    // Set the model-view matrix to point toward the current face:
    ...

    // Render the scene:
    ...
}

Warning

Rendering to a cube map texture is supported only in iPhone OS 3.1 and newer.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required