4.6. Cube mapping

This tutorial combines:

Open the 8-Cube Mapping shader example:

  1. Example 4.40 shows the standard initialization code. The textureCube provides the environment that contains the shape:

    Example 4.40. Initialization for the cube mapping application

    ...
    #include <mde/mde.h>
    using namespace MDE;
    
    static const float PI = 3.14159265;
    
    /** 
     * Function that calculates the camera position given an angle and a radius. This function
     * enables the camera to rotate around the scene object.
     */
    inline vec3 calculateCamPos(float radius, float angle)
    {
    	float angleRad = angle / 180 * PI;
    	return vec3(radius * cos(angleRad), 0.0f, radius * sin(angleRad));
    }
    
    /**
     * Function that loads 6 textures from the hard drive and creates a cubemap from them.
     */
    Managed<TextureCube> loadCubeMap(Managed<Context> context, Proxy &proxy)
    {
    	Managed<TextureCube> textureCube = context->createTextureCube();
    
    	MDE::Bitmap2DAsset* tmpTex = NULL;
    
    	tmpTex = proxy.getBitmap2DAsset("data/3.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	tmpTex = proxy.getBitmap2DAsset("data/opp.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	tmpTex = proxy.getBitmap2DAsset("data/4.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	tmpTex = proxy.getBitmap2DAsset("data/1.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	tmpTex = proxy.getBitmap2DAsset("data/ned.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	tmpTex = proxy.getBitmap2DAsset("data/2.png");
    	textureCube->buildMipmaps(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 
    		tmpTex->getWidth(), tmpTex->getHeight(), 
    		tmpTex->getPixelFormat(), tmpTex->getPixels() );
    
    	return textureCube;
    }
    ...
    

    The cube map uses the following bitmaps:

    1.pgn

    This is the image for wall one, see Figure 4.15:

    Figure 4.15. Environment bitmap 1.png

    Environment bitmap 1.png

    2.pgn

    This is the image for wall two, see Figure 4.16:

    Figure 4.16. Environment bitmap 2.png

    Environment bitmap 2.png

    3.pgn

    This is the image for wall three, see Figure 4.17:

    Figure 4.17. Environment bitmap 3.png

    Environment bitmap 3.png

    4.pgn

    This is the image for wall four, see Figure 4.18:

    Figure 4.18. Environment bitmap 4.png

    Environment bitmap 4.png

    opp.pgn

    This is the image for the environment ceiling, see Figure 4.19:

    Figure 4.19. Environment bitmap opp.png

    Environment bitmap opp.png

    ned.pgn

    This is the image for the environment floor, see Figure 4.20:

    Figure 4.20. Environment bitmap ned.png

    Environment bitmap ned.png

  2. Previous examples specified the locations for each vertex in the shape. Example 4.41 shows how to load an asset file that contains the information:

    Example 4.41. Load the assets for cube mapping

    ...
    int main(int argc, char * argv[])
    {
        try
        {
            // Initializing
            Managed<System> system = create_system_backend();
            Managed<Context> context = system->createContext(320, 240);
            #ifdef MDE_OS_PC_LINUX
                Managed<Keyboard> keyboard = system->createKeyboard(context);
            #else
                Managed<Keyboard> keyboard = system->createKeyboard();
            #endif
            Managed<FileSystem> filesystem = system->createFileSystem(".");
            Managed<Timer> timer = system->createTimer();
    
            Proxy proxy(filesystem, context);
    
            // Load programs
            Program* cube_reflection =
                proxy.getProgram("shaders/cube_reflection_mapping.vert;shaders/cube_reflection_mapping.frag");
            
            Program* skybox = proxy.getProgram("shaders/skybox.vert;shaders/skybox.frag");
    
            // Load scene
            SceneAsset* scene = proxy.getSceneAsset("data/teapot.mba");
            GeometryAsset* teapot = 
                static_cast<GeometryAsset*>(scene->getAsset(Asset::TYPE_GEOMETRY, 0));
    
            // Load cubemap
            Managed<TextureCube> textureCube = loadCubeMap(context, proxy);            
            textureCube->setWrapMode(GL_CLAMP_TO_EDGE);
            textureCube->setFilterMode(GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR);
    
    ...
    

  3. There are four shaders:

    skybox.vert

    The vertex shader in Example 4.42 calculates the vertex coordinate to use in the fragment shader:

    Example 4.42. skybox.vert shader for cube mapping

    uniform mat4 WORLD_VIEW_PROJECTION;
    attribute vec4 POSITION;
    attribute vec3 NORMAL;
    
    varying vec3 vTexCoord;
    
    void main(void)
    {
        gl_Position = WORLD_VIEW_PROJECTION * POSITION;
        vTexCoord = POSITION.xyz;
    }
    

    skybox.frag

    The fragment shader in Example 4.43 maps the environment bitmap to the skybox cube.

    Example 4.43. skybox.frag shader for cube mapping

    uniform samplerCube skyboxCubeMap;
    varying vec3 vTexCoord;
    void main(void)
    {
        gl_FragColor = textureCube(skyboxCubeMap, vTexCoord);
    }
    

    cube_reflection_mapping.vert

    The vertex code in Example 4.44 calculates the view vector based on the normal and the camera view:

    Example 4.44. cube_reflection_mapping.vert shader for cube mapping

    uniform mat4 WORLD_VIEW_PROJECTION;
    uniform mat3 WORLD;
    uniform vec3 CAMERA_POSITION;
     
    attribute vec4 POSITION;
    attribute vec3 NORMAL;
    
    varying vec3 vNormal;
    varying vec3 vViewVector;
    
    void main(void)
    {
        gl_Position = WORLD_VIEW_PROJECTION * POSITION;
    
    // multiply the normal by the model matrix so that the teapot can
    // be rotated
        vNormal = WORLD * NORMAL;
        vViewVector = CAMERA_POSITION - (WORLD * POSITION.xyz);
    }
    

    cube_reflection_mapping.frag

    The code in Example 4.45 uses the normal vector to copy the cube map values to the teapot surface. This creates a mirror effect.

    Example 4.45. cube_reflection_mapping.frag shader for cube mapping

    uniform samplerCube environmentCubeMap;
    varying vec3 vNormal;
    varying vec3 vViewVector;
    
    void main(void)
    {
    // normalize the normals as the interpolation changes their length
        vec3 normal = normalize(vNormal);
        vec3 viewVector = normalize(vViewVector);
        vec3 reflectionVector = reflect(-viewVector, normal);
        gl_FragColor = textureCube(environmentCubeMap,
            reflectionVector.xyz);
    }
    

  4. Example 4.46 shows the vertexDeclaration code. The coordinates for the skybox are entered manually:

    Example 4.46. Load the assets and set the skybox parameters

    ...
            // Set up position data and index data of the skybox manually
            GLfloat vertexData[] =
            {
                -0.5f, -0.5f, -0.5f, // 0
                 0.5f, -0.5f, -0.5f, // 1
                 0.5f,  0.5f, -0.5f, // 2
                -0.5f,  0.5f, -0.5f, // 3
                -0.5f, -0.5f,  0.5f, // 4
                 0.5f, -0.5f,  0.5f, // 5
                 0.5f,  0.5f,  0.5f, // 6
                -0.5f,  0.5f,  0.5f  // 7
            };
    
            GLushort indexData[] = 
            {
                0, 1, 2, //front
                0, 2, 3,
                1, 5, 6, //right
                1, 6, 2,
                5, 4, 7, //back
                5, 7, 6,
                4, 0, 3, //left
                4, 3, 7,
                3, 2, 6, //top
                3, 6, 7,
                4, 5, 1, //bottom
                4, 1, 0
            };
    
            // Set up vertex buffer and index buffer
            Buffer* vertexBuffer = 
                context->createBuffer(GL_ARRAY_BUFFER, sizeof(vertexData), sizeof(GLfloat)*3);
            vertexBuffer->setData(0, sizeof(vertexData), vertexData);
    

  5. Example 4.47 shows how to create an element buffer that contains the index lists:

    Example 4.47. Creating the element buffer

            
            Buffer* indexBuffer = 
                context->createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), sizeof(unsigned short));
            indexBuffer->setData(0, sizeof(indexData), indexData);
    
            // Set up vertex declaration for the vertex data stream.
            VertexElement elements[1];
            elements[0].components = 3;
            elements[0].offset = 0;
            elements[0].semantic = POSITION;
            elements[0].stream = 0;
            elements[0].type = GL_FLOAT;
    
            Managed<VertexDeclaration> vertexDeclaration = 
                context->createVertexDeclaration(elements, 1);
    ...
    

    To standardize creation of vertex buffers and index buffers, use Context::createBuffer(). When creating a buffer, specify the target as either:

    • GL_ARRAY_BUFFER for vertex buffers that will contain vertex attributes

    • GL_ELEMENT_ARRAY_BUFFER for index buffers that will contain index lists for glDrawElements.

  6. Example 4.48 shows the camera, projection, view, and world setup:

    Example 4.48. Set the camera, projection, view, and world for cube mapping

    ...
            // The camera position, target and up vector for use when creating the view matrix
            float cameraAngle = 0.0f;
            vec3 camPos = calculateCamPos(75.0f, cameraAngle);
            vec3 camTarget = vec3(0.0f, 0.0f, 0.0f);
            vec3 upVector = vec3(0.0f, 1.0f, 0.0f);
    
            // Set up the view, projection and world matrices. We don't precalculate the 
            // modelviewprojection-matrices because it will have to be changed for each frame
            // as we change the view matrix to rotate the camera.
            mat4 view = mat4::lookAt(camPos, camTarget, upVector);
            mat4 proj = mat4::perspective(60.0f, 4.0f/3.0f, 1.0f, 300.0f);
            
            // Set up the world matrix for the object.
            mat4 world_object = mat4::rotation(90.0, vec3(-1.0f, 0.0f, 0.0f)) *
                mat4::rotation(90.0, vec3(0.0f, 0.0f, 1.0f));
            mat4 wvpMatrix_object;
        
            // Set up the world matrix for the skybox.
            mat4 world_skybox = mat4::scale(200.0f, 200.0f, 200.0f);
            mat4 wvpMatrix_skybox;
    
            mat4 tmp;
    
            glEnable(GL_DEPTH_TEST);
    ...
    

  7. Example 4.49 shows the part of the draw loop that reacts to keyboard input to change the camera, view, and lighting:

    Example 4.49. Use the to keyboard input to change the cube mapping view

    ...
            do
            {
                glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
    
                // Exit the application
                if (keyboard->getSpecialKeyState(Keyboard::KEY_ESCAPE))
                {
                    return 0;
                }
    
                // Rotate object or camera
                if (keyboard->getSpecialKeyState(Keyboard::KEY_SPACE))
                {
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_LEFT))
                    {
                        camPos = calculateCamPos(75.0f, --cameraAngle);
                        view = mat4::lookAt(camPos, camTarget, upVector);
                    }
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_RIGHT))
                    {
                        camPos = calculateCamPos(75.0f, ++cameraAngle);
                        view = mat4::lookAt(camPos, camTarget, upVector);
                    }
                }
                else 
                {
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_RIGHT))
                    {
                        world_object *= mat4::rotation(1.0f, vec3(0.0f, 0.0f, 1.0f));
                    }
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_LEFT))
                    {
                        world_object *= mat4::rotation(-1.0f, vec3(0.0f, 0.0f, 1.0f));
                    }
                }
    ...
    

  8. Example 4.50 shows the part of the draw loop that sets the uniform values and draws the teapot:

    Example 4.50. Drawing the teapot in the cube mapping environment

    ...
                tmp = proj * view;
                wvpMatrix_object = tmp * world_object;
                wvpMatrix_skybox = tmp * world_skybox;
    
                // Initialize the object's program/uniforms and draw it.
                context->setProgram(cube_reflection);
                context->setUniformMatrix("WORLD_VIEW_PROJECTION", wvpMatrix_object);
                context->setUniformMatrix("WORLD", world_object.toMat3());
                context->setUniform("CAMERA_POSITION", camPos);
                context->setUniformSampler("environmentCubeMap", textureCube);
    
                teapot->draw();
    ...
    

  9. Example 4.51 shows the part of the draw loop that sets the uniform values and draws the environment:

    Example 4.51. Drawing the cube environment

    ...
    
                // Initialize skybox's program/uniforms and draw it.
                context->setProgram(skybox);
                context->setUniformMatrix("WORLD_VIEW_PROJECTION", wvpMatrix_skybox);
                context->setUniformSampler("skyboxCubeMap", textureCube);
    
                context->setVertexDeclaration(vertexDeclaration);
                context->setVertexBuffer(0, vertexBuffer);
                context->drawElements(GL_TRIANGLES, 0, 12, indexBuffer);
            }while ( context->update() );
        }
        catch (Exception &e)
        {
            printf(e.getMessage().getCharString());
        }
    }
    

  10. Rebuild the project and run it. Figure 4.21 shows two views of the shape in the environment:

    Figure 4.21. The mirrored teapot shape with cube mapping for the environment

    The mirrored teapot shape with cube mapping for the environment

  11. Use the keyboard to change the view:

    • press the left or right arrows to rotate the teapot

    • depress the spacebar and then press the left or right arrows to move the camera

      Note

      The parallel lines on the floor are distorted in the first view in Figure 4.21. The cube environment has limitations, but it is much faster than evaluating all of the shapes that this environment would contain.

Copyright © 2010 ARM. All rights reserved.ARM DUI 0527A‑02a
Non-Confidential - Draft - BetaID070710