3D C/C++ tutorials - OpenGL 2.1 - GLSL cube mapping
3D C/C++ tutorials -> OpenGL 2.1 -> GLSL cube mapping
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
GLSL cube mapping
Welcome to the GLSL cube mapping tutorial. We'll explain some things and point out some issues with cube mapping in OpenGL.
The sky box mesh is a 2.0 x 2.0 x 2.0 sized cube rendered as 6 quads.
bool COpenGLRenderer::Init()
{
    ...

    float SkyBoxVertices[] =
    {    // x, y, z, x, y, z, x, y, z, x, y, z
         1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f, // +X
        -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f, // -X
        -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, // +Y
        -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, // -Y
         1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, // +Z
        -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f  // -Z
    };

    glGenBuffers(1, &SkyBoxVBO);
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glBufferData(GL_ARRAY_BUFFER, 288, SkyBoxVertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    ...
}
The sky box mesh is textured using cube mapping. The 6 images of the cube map texture should intuitively correspond to the faces of the sky box mesh in this order:
Imagine it in world space:

  • the center is at vec3(0.0, 0.0, 0.0)
  • the left bottom back corner is at vec3(-1.0, -1.0, 1.0)
  • the right top front corner is at vec3(1.0, 1.0, -1.0)
  • the sun in the right.jpg image is close to the right top back corner at vec3(1.0, 1.0, 1.0)
If you load the 6 images into the cube map texture
bool COpenGLRenderer::Init()
{
    ...

    char *CubeMapTextureFiles[6] = {"right.jpg", "left.jpg", "top.jpg", "bottom.jpg", "front.jpg", "back.jpg"};

    Error |= !CubeMap.LoadTextureCubeMap(CubeMapTextureFiles);

    Error |= !Sky.Load("sky.vs", "sky.fs");

    ...
}
and render it
void COpenGLRenderer::Render(float FrameTime)
{
    ...

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(&ViewMatrix);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glBindTexture(GL_TEXTURE_CUBE_MAP, CubeMap);
    glUseProgram(Sky);
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 12, (void*)0);
    glDrawArrays(GL_QUADS, 0, 24);
    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);

    ...
}
with this sky.vs vertex shader
#version 120

void main()
{
    gl_TexCoord[0].stp = gl_Vertex.xyz;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
and with this sky.fs fragment shader
#version 120

uniform samplerCube CubeMap;

void main()
{
    gl_FragColor = textureCube(CubeMap, gl_TexCoord[0].stp);
}
and with the camera position at vec3(0.0, 0.0, z > 1.0), you will be surprised with this result:
Yes, the vertices of the sky box mesh are used as cube map texture coordinates, but it shouldn't be the problem.
If you don't know why, read the chapter 3.8.6 of the OpenGL 2.1 specification and the documentation of the textureCube function in the chapter 8.7 of the GLSL 1.2 specification.
Obviously, the right, left, front and back images are rotated upside down and the front and back images are swapped.
We could rotate the right, left, front and back images in an image editor and swap the front and back images.
But it's easier to swap the top and bottom images:
bool COpenGLRenderer::Init()
{
    ...

    char *CubeMapTextureFiles[6] = {"right.jpg", "left.jpg", "bottom.jpg", "top.jpg", "front.jpg", "back.jpg"};

    Error |= !CubeMap.LoadTextureCubeMap(CubeMapTextureFiles);

    ...
}
Now the cube map is just upside down.
The solution to this situation is to rotate the cube map texture coordinates 180 degrees around the X axis in the sky.vs vertex shader.
#version 120

void main()
{
    gl_TexCoord[0].stp = vec3(gl_Vertex.x, -gl_Vertex.yz);
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
This is correct, because the sun in the right.jpg image really is close to the right top back corner of the sky box mesh.
All of this makes sense if cube mapping is implemented in hardware for Direct3D.
More details on Direct3D cube mapping can be found at the Cubic environment mapping (Direct3D 9) page.
We need to compare Direct3D and OpenGL world and texture spaces.
The +Z world space axis in OpenGL is in the opposite direction than the +Z world space axis in Direct3D, so we need to set "gl_TexCoord[0].p" to "-gl_Vertex.z" and because +Z and -Z world space axes are swapped, we don't need to swap +Z and -Z images explicitly.
The +Y (t) texture space axis in OpenGL is in the opposite direction than the +Y (v) texture space axis in Direct3D, so we need to set "gl_TexCoord[0].t" to "-gl_Vertex.y" and because +Y and -Y world space axes aren't swapped, we need to swap +Y and -Y images.
Direct3D:
0 = +X = right
1 = -X = left
2 = +Y = top
3 = -Y = bottom
4 = +Z = front
5 = -Z = back

     (+y)           |     (2)         |      (to)
(-x) (+z) (+x) (-z) | (1) (4) (0) (5) | (le) (fr) (ri) (ba)
     (-y)           |     (3)         |      (bo)
OpenGL:
Swap +Y and -Y textures, +Z and -Z axes are swapped, thus +Z and -Z textures are swapped implicitly.
0 = +X = right
1 = -X = left
2 = +Y = bottom
3 = -Y = top
4 = +Z = front
5 = -Z = back

     (+y)           |     (2)         |      (bo)
(-x) (-z) (+x) (+z) | (1) (5) (0) (4) | (le) (ba) (ri) (fr)
     (-y)           |     (3)         |      (to)
Rotated 180 degrees around X axis:
     (-y)           |     (3)         |      (to)
(-x) (+z) (+x) (-z) | (1) (4) (0) (5) | (le) (fr) (ri) (ba)
     (+y)           |     (2)         |      (bo)
opengl_21_tutorials_win32_framework.h
...

class COpenGLRenderer
{
protected:
    ...

protected:
    CTexture CubeMap;
    CShaderProgram Sky, Reflection;
    GLuint SkyBoxVBO, TorusVBO, TorusVerticesCount;

public:
    bool WireFrame, Pause;

public:
    ...
};

...
opengl_21_tutorials_win32_framework.cpp
...

COpenGLRenderer::COpenGLRenderer()
{
    WireFrame = false;
    Pause = false;

    Camera.SetViewMatrixPointer(&ViewMatrix);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

bool COpenGLRenderer::Init()
{
    // ------------------------------------------------------------------------------------------------------------------------

    bool Error = false;

    // load textures and shaders ----------------------------------------------------------------------------------------------

    char *CubeMapTextureFiles[6] = {"right.jpg", "left.jpg", "bottom.jpg", "top.jpg", "front.jpg", "back.jpg"};

    Error |= !CubeMap.LoadTextureCubeMap(CubeMapTextureFiles);

    Error |= !Sky.Load("sky.vs", "sky.fs");
    Error |= !Reflection.Load("reflection.vs", "reflection.fs");

    // if an error occurred, return false -------------------------------------------------------------------------------------

    if(Error)
    {
        return false;
    }

    // get uniform locations --------------------------------------------------------------------------------------------------

    Sky.UniformLocations = new GLuint[1];
    Sky.UniformLocations[0] = glGetUniformLocation(Sky, "CameraPosition");

    Reflection.UniformLocations = new GLuint[3];
    Reflection.UniformLocations[0] = glGetUniformLocation(Reflection, "CameraPosition"); 
    Reflection.UniformLocations[1] = glGetUniformLocation(Reflection, "NormalMatrix"); 
    Reflection.UniformLocations[2] = glGetUniformLocation(Reflection, "ModelMatrix"); 

    // init vertex arrays -----------------------------------------------------------------------------------------------------

    float SkyBoxVertices[] =
    {    // x, y, z, x, y, z, x, y, z, x, y, z
         1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f, // +X
        -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f, // -X
        -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, // +Y
        -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, // -Y
         1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, // +Z
        -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f  // -Z
    };

    glGenBuffers(1, &SkyBoxVBO);
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glBufferData(GL_ARRAY_BUFFER, 288, SkyBoxVertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // ------------------------------------------------------------------------------------------------------------------------

    CBuffer Buffer;

    int ures = 64, vres = 32;
    float radius = 2.0f, thickness = 2.0f, uinc = 360.0f / ures, vinc = 360.0f / vres;

    vec3 z = vec3(0.0f, 0.0f, 1.0f);
    vec3 x1 = vec3(thickness / 2.0f, 0.0f, 0.0f), x2 = rotate(x1, uinc, z);
    vec3 m1 = vec3(radius, 0.0f, 0.0f), m2 = rotate(m1, uinc, z);
    vec3 y1 = vec3(0.0f, 1.0f, 0.0f), y2 = rotate(y1, uinc, z);

    vec3 Normal, Vertex;

    for(float u = 0.0f; u < 360.0f; u += 360.0f / ures)
    {
        for(float v = 0.0f; v < 360.0f; v += 360.0f / vres)
        {
            vec3 a = x1, b = rotate(x1, vinc, y1), c = rotate(x2, vinc, y2), d = x2;

            Normal = normalize(a); Buffer.AddData(&Normal, 12); Vertex = m1 + a; Buffer.AddData(&Vertex, 12);
            Normal = normalize(b); Buffer.AddData(&Normal, 12); Vertex = m1 + b; Buffer.AddData(&Vertex, 12);
            Normal = normalize(c); Buffer.AddData(&Normal, 12); Vertex = m2 + c; Buffer.AddData(&Vertex, 12);
            Normal = normalize(d); Buffer.AddData(&Normal, 12); Vertex = m2 + d; Buffer.AddData(&Vertex, 12);

            x1 = rotate(x1, vinc, y1); x2 = rotate(x2, vinc, y2);
        }

        m1 = m2; m2 = rotate(m1, uinc, z);
        x1 = x2; x2 = rotate(x1, uinc, z);
        y1 = y2; y2 = rotate(y1, uinc, z);
    }

    glGenBuffers(1, &TorusVBO);
    glBindBuffer(GL_ARRAY_BUFFER, TorusVBO);
    glBufferData(GL_ARRAY_BUFFER, Buffer.GetDataSize(), Buffer.GetData(), GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    TorusVerticesCount = Buffer.GetDataSize() / 24;

    Buffer.Empty();

    // set camera -------------------------------------------------------------------------------------------------------------

    Camera.Look(vec3(0.0f, 0.0f, 10.0f), vec3(0.0f, 0.0f, 0.0f), true);

    // ------------------------------------------------------------------------------------------------------------------------

    return true;
}

void COpenGLRenderer::Render(float FrameTime)
{
    // ------------------------------------------------------------------------------------------------------------------------

    static float a = 0.0f;

    // render scene -----------------------------------------------------------------------------------------------------------

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(&ViewMatrix);

    // render sky - depth test must be disabled -------------------------------------------------------------------------------

    glBindTexture(GL_TEXTURE_CUBE_MAP, CubeMap);
    glUseProgram(Sky);
    glUniform3fv(Sky.UniformLocations[0], 1, &Camera.Position); 
    glBindBuffer(GL_ARRAY_BUFFER, SkyBoxVBO);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 12, (void*)0);
    glDrawArrays(GL_QUADS, 0, 24);
    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    // render torus -----------------------------------------------------------------------------------------------------------

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    if(WireFrame)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_CULL_FACE);
    }

    glBindTexture(GL_TEXTURE_CUBE_MAP, CubeMap);
    glUseProgram(Reflection);
    glUniform3fv(Reflection.UniformLocations[0], 1, &Camera.Position); 
    glUniformMatrix3fv(Reflection.UniformLocations[1], 1, GL_FALSE, &NormalMatrix); 
    glUniformMatrix4fv(Reflection.UniformLocations[2], 1, GL_FALSE, &ModelMatrix); 
    glBindBuffer(GL_ARRAY_BUFFER, TorusVBO);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 24, (void*)0);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 24, (void*)12);
    glDrawArrays(GL_QUADS, 0, TorusVerticesCount);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

    if(WireFrame)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    if(!WireFrame)
    {
        glDisable(GL_CULL_FACE);
    }

    glDisable(GL_DEPTH_TEST);

    // rotate torus -----------------------------------------------------------------------------------------------------------

    if(!Pause)
    {
        ModelMatrix = rotate(a, vec3(1.0f, 0.0f, 0.0f)) * rotate(a * 2.0f, vec3(0.0f, 1.0f, 0.0f));
        NormalMatrix = transpose(inverse(mat3x3(ModelMatrix)));
        a += 11.25f * FrameTime;
    }
}

void COpenGLRenderer::Resize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    glViewport(0, 0, Width, Height);

    ProjectionMatrix = perspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&ProjectionMatrix);
}

void COpenGLRenderer::Destroy()
{
    CubeMap.Destroy();

    Sky.Destroy();
    Reflection.Destroy();

    glDeleteBuffers(1, &SkyBoxVBO);
    glDeleteBuffers(1, &TorusVBO);
}

...

void COpenGLView::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case VK_F1:
            OpenGLRenderer.WireFrame = !OpenGLRenderer.WireFrame;
            break;

        case VK_SPACE:
            OpenGLRenderer.Pause = !OpenGLRenderer.Pause;
            break;
    }
}

...
sky.vs
#version 120

uniform vec3 CameraPosition;

void main()
{
    gl_TexCoord[0].stp = vec3(gl_Vertex.x, -gl_Vertex.yz);
    gl_Position = gl_ModelViewProjectionMatrix * vec4(gl_Vertex.xyz + CameraPosition, 1.0);
}
sky.fs
#version 120

uniform samplerCube CubeMap;

void main()
{
    gl_FragColor = textureCube(CubeMap, gl_TexCoord[0].stp);
}
reflection.vs
#version 120

uniform vec3 CameraPosition;
uniform mat3x3 NormalMatrix;
uniform mat4x4 ModelMatrix;

void main()
{
    vec3 Normal = NormalMatrix * gl_Normal;
    vec4 Position = ModelMatrix * gl_Vertex;
    vec3 Reflection = reflect(Position.xyz - CameraPosition, normalize(Normal));
    gl_TexCoord[0].stp = vec3(Reflection.x, -Reflection.yz);
    gl_Position = gl_ModelViewProjectionMatrix * Position;
}
reflection.fs
#version 120

uniform samplerCube CubeMap;

void main()
{
    gl_FragColor = textureCube(CubeMap, gl_TexCoord[0].stp);
}
Download
glsl_cube_mapping.zip (Visual Studio 2005 Professional)
glsl_cube_mapping_devcpp4992.zip (Dev-C++ 4.9.9.2)

© 2010 - 2016 Bc. Michal Belanec, michalbelanec (at) centrum (dot) sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.