3D C/C++ tutorials - OpenGL 2.1 - GLSL shadow mapping
3D C/C++ tutorials -> OpenGL 2.1 -> GLSL shadow 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
opengl_21_tutorials_win32_framework.h
...

#define SHADOW_MAP_SIZE 1024

class COpenGLRenderer
{
protected:
    ...
    mat4x4 ..., ViewMatrixInverse, ..., ProjectionMatrixInverse;

protected:
    CTexture Texture[2];
    CShaderProgram ShadowMapping;
    GLuint VBO, ShadowMap, FBO;
    mat4x4 LightViewMatrix, LightProjectionMatrix, ShadowMatrix;

public:
    bool Texturing, ShowShadowMap, Pause;
    vec2 CursorPosition;

public:
    ...

protected:
    void InitArrayBuffers();
};

...
opengl_21_tutorials_win32_framework.cpp
...

COpenGLRenderer::COpenGLRenderer()
{
    Texturing = true;
    ShowShadowMap = false;
    Pause = false;

    Camera.SetViewMatrixPointer(&ViewMatrix, &ViewMatrixInverse);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

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

    bool Error = false;

    // check OpenGL extensions ------------------------------------------------------------------------------------------------

    if(!GL_ARB_depth_texture)
    {
        ErrorLog.Append("GL_ARB_depth_texture not supported!\r\n");
        Error = true;
    }

    if(!GLEW_EXT_framebuffer_object)
    {
        ErrorLog.Append("GL_EXT_framebuffer_object not supported!\r\n");
        Error = true;
    }

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

    Error |= !Texture[0].LoadTexture2D("floor.jpg");
    Error |= !Texture[1].LoadTexture2D("cube.jpg");

    Error |= !ShadowMapping.Load("shadowmapping.vs", "shadowmapping.fs");

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

    if(Error)
    {
        return false;
    }

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

    ShadowMapping.UniformLocations = new GLuint[2];
    ShadowMapping.UniformLocations[0] = glGetUniformLocation(ShadowMapping, "Texturing");
    ShadowMapping.UniformLocations[1] = glGetUniformLocation(ShadowMapping, "ShadowMatrix");

    // set constant uniforms --------------------------------------------------------------------------------------------------
    
    glUseProgram(ShadowMapping);
    glUniform1i(glGetUniformLocation(ShadowMapping, "Texture"), 0);
    glUniform1i(glGetUniformLocation(ShadowMapping, "ShadowMap"), 1);
    glUseProgram(0);

    // init array buffers -----------------------------------------------------------------------------------------------------

    ...

    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, 1664, Data, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // set light projection matrix --------------------------------------------------------------------------------------------
    
    LightProjectionMatrix = perspective(35.0f, 1.0f, 4.0f, 16.0f);

    // generate shadow map texture --------------------------------------------------------------------------------------------

    glGenTextures(1, &ShadowMap);
    glBindTexture(GL_TEXTURE_2D, ShadowMap);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
    // glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);

    // generate FBO -----------------------------------------------------------------------------------------------------------

    glGenFramebuffersEXT(1, &FBO);

    // set light --------------------------------------------------------------------------------------------------------------
    
    vec3 LightColor = vec3(1.0f, 1.0f, 1.0f);
    
    glLightfv(GL_LIGHT0, GL_AMBIENT, &vec4(LightColor * 0.25f, 1.0f));
    glLightfv(GL_LIGHT0, GL_DIFFUSE, &vec4(LightColor * 0.75f, 1.0f));
    glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0f / 128.0f);
    glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f / 256.0f);

    // set camera -------------------------------------------------------------------------------------------------------------
    
    Camera.Look(vec3(1.75f, 1.75f, 5.0f), vec3(0.0f, 0.0f, 0.0f));

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

    return true;
}

void COpenGLRenderer::Render(float FrameTime)
{
    static vec3 LightPosition = vec3(0.0f, 2.5f, 5.0f);

    // render shadow map ------------------------------------------------------------------------------------------------------

    if(!Pause)
    {
        glViewport(0, 0, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE);

        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf(&LightProjectionMatrix);

        LightViewMatrix = look(LightPosition, vec3(0.0f), vec3(0.0f, 1.0f, 0.0f));

        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf(&LightViewMatrix);

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBO);
        glDrawBuffers(0, NULL); glReadBuffer(GL_NONE);
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, ShadowMap, 0);

        glClear(GL_DEPTH_BUFFER_BIT);

        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);

        glCullFace(GL_FRONT);

        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(3, GL_FLOAT, 32, (void*)20);
        glDrawArrays(GL_QUADS, 0, 52);
        glDisableClientState(GL_VERTEX_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glLoadMatrixf(&LightViewMatrix);
        glTranslatef(0.0f, -0.375f, 0.5f);
        glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
        glutSolidTorus(0.125f, 0.25f, 64, 64);

        glCullFace(GL_BACK);

        glLoadMatrixf(&LightViewMatrix);
        glTranslatef(0.0f, -0.31f, -0.5f);
        glutSolidTeapot(0.25f);

        glDisable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST);

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    }

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

    glViewport(0, 0, Width, Height);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&ProjectionMatrix);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(&ViewMatrix);

    glLightfv(GL_LIGHT0, GL_POSITION, &vec4(LightPosition, 1.0f));

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glUseProgram(ShadowMapping);
    ShadowMatrix = BiasMatrix * LightProjectionMatrix * LightViewMatrix * ViewMatrixInverse;
    glUniformMatrix4fv(ShadowMapping.UniformLocations[1], 1, GL_FALSE, &ShadowMatrix);

    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, ShadowMap);
    
    if(Texturing)
    {
        glUniform1i(ShadowMapping.UniformLocations[0], 1);
    }

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, 32, (void*)0);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 32, (void*)8);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 32, (void*)20);
    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture[0]);
    glColor3f(1.0f, 1.0f, 1.0f);
    glDrawArrays(GL_QUADS, 0, 4);
    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture[1]);
    glColor3f(1.0f, 1.0f, 1.0f);
    glDrawArrays(GL_QUADS, 4, 48);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glUniform1i(ShadowMapping.UniformLocations[0], 0);

    glLoadMatrixf(&ViewMatrix);
    glTranslatef(0.0f, -0.375f, 0.5f);
    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    glutSolidTorus(0.125f, 0.25f, 64, 64);

    glDisable(GL_CULL_FACE);

    glLoadMatrixf(&ViewMatrix);
    glTranslatef(0.0f, -0.31f, -0.5f);
    glutSolidTeapot(0.25f);

    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0);

    glUseProgram(0);

    glDisable(GL_DEPTH_TEST);
    
    // display shadow map -----------------------------------------------------------------------------------------------------

    if(ShowShadowMap)
    {
        glViewport(0, 0, 512, 512);

        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf(&ortho(0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f));

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, ShadowMap);
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, 1.0f);
        glEnd();
        glBindTexture(GL_TEXTURE_2D, 0);
        glDisable(GL_TEXTURE_2D);

        // shadow depth test visualization ------------------------------------------------------------------------------------

        glViewport(0, 0, Width, Height);

        float Depth;

        // read depth of the pixel under the cursor from the depth buffer
        glReadPixels((int)CursorPosition.x, (int)CursorPosition.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &Depth);

        // get NDC space position of the pixel under the cursor
        vec4 NDCSpaceCursorPosition = vec4(CursorPosition.x / (float)(Width - 1), CursorPosition.y / (float)(Height - 1), Depth, 1.0f) * 2.0f - 1.0f;

        // get view space position of the pixel under the cursor
        vec4 Position = ProjectionMatrixInverse * NDCSpaceCursorPosition;
        Position /= Position.w;

        // multiply ShadowMatrix by Position
        vec4 ShadowMapTexCoord = ShadowMatrix * Position;

        if(ShadowMapTexCoord.w > 0.0)
        {
            // get shadow map texture space position of the pixel under the cursor
            vec4 ShadowMapTexCoordProj = ShadowMapTexCoord / ShadowMapTexCoord.w;

            if(ShadowMapTexCoordProj.x >= 0.0 && ShadowMapTexCoordProj.x < 1.0 && ShadowMapTexCoordProj.y >= 0.0 && ShadowMapTexCoordProj.y < 1.0 && ShadowMapTexCoordProj.z >= 0.0 && ShadowMapTexCoordProj.z < 1.0)
            {
                // read depth from the shadow map depth texture
                glViewport(0, 0, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE);
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBO);
                glDrawBuffers(0, NULL); glReadBuffer(GL_NONE);
                glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, ShadowMap, 0);
                glReadPixels((int)(ShadowMapTexCoordProj.x * SHADOW_MAP_SIZE), (int)(ShadowMapTexCoordProj.y * SHADOW_MAP_SIZE), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &Depth);
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

                glViewport(0, 0, 512, 512);

                if(Depth <= ShadowMapTexCoordProj.z)
                {
                    // if the pixel under the cursor is in shadow, the cross will be green
                    glColor3f(0.0f, 1.0f, 0.0f);
                }
                else
                {
                    // otherwise the cross will be red
                    glColor3f(1.0f, 0.0f, 0.0f);
                }

                glBegin(GL_LINES);
                    glVertex2f(ShadowMapTexCoordProj.x - 0.0078125f, ShadowMapTexCoordProj.y);
                    glVertex2f(ShadowMapTexCoordProj.x + 0.0078125f, ShadowMapTexCoordProj.y);
                    glVertex2f(ShadowMapTexCoordProj.x, ShadowMapTexCoordProj.y - 0.0078125f);
                    glVertex2f(ShadowMapTexCoordProj.x, ShadowMapTexCoordProj.y + 0.0078125f);
                glEnd();
            }
        }
    }

    // move light -------------------------------------------------------------------------------------------------------------

    if(!Pause)
    {
        LightPosition = rotate(LightPosition, 2.8125f * FrameTime, vec3(0.0f, 1.0f, 0.0f));
    }
}

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

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

void COpenGLRenderer::Destroy()
{
    Texture[0].Destroy();
    Texture[1].Destroy();

    ShadowMapping.Destroy();

    glDeleteBuffers(1, &VBO);

    glDeleteTextures(1, &ShadowMap);

    if(GLEW_EXT_framebuffer_object)
    {
        glDeleteFramebuffersEXT(1, &FBO);
    }
}

...

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

        case VK_F2:
            OpenGLRenderer.ShowShadowMap = !OpenGLRenderer.ShowShadowMap;
            break;

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

void COpenGLView::OnMouseMove(int X, int Y)
{
    ...

    OpenGLRenderer.CursorPosition = vec2((float)X, (float)(Height - 1 - Y));
}

...
shadowmapping.vs
#version 120

uniform mat4x4 ShadowMatrix;

varying vec4 ShadowMapTexCoord;
varying vec3 Normal, LightDirection;

void main()
{
    vec4 Position = gl_ModelViewMatrix * gl_Vertex;
    ShadowMapTexCoord = ShadowMatrix * Position;
    Normal = gl_NormalMatrix * gl_Normal;
    LightDirection = gl_LightSource[0].position.xyz - Position.xyz;
    gl_FrontColor = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = gl_ProjectionMatrix * Position;
}
shadowmapping.fs
#version 120

uniform sampler2D Texture;
uniform sampler2D/*Shadow*/ ShadowMap;
uniform int Texturing;

varying vec4 ShadowMapTexCoord;
varying vec3 Normal, LightDirection;

void main()
{
    float LightDistance2 = dot(LightDirection, LightDirection);
    float LightDistance = sqrt(LightDistance2);
    float NdotLD = max(dot(normalize(Normal), LightDirection / LightDistance), 0.0);
    float Attenuation = gl_LightSource[0].constantAttenuation;
    Attenuation += gl_LightSource[0].linearAttenuation * LightDistance;
    Attenuation += gl_LightSource[0].quadraticAttenuation * LightDistance2;
    
    // NdotLD *= shadow2DProj(ShadowMap, ShadowMapTexCoord).r;
    
    if(ShadowMapTexCoord.w > 0.0)
    {
        vec3 ShadowMapTexCoordProj = ShadowMapTexCoord.xyz / ShadowMapTexCoord.w;
        
        if(ShadowMapTexCoordProj.x >= 0.0 && ShadowMapTexCoordProj.x < 1.0 &&
           ShadowMapTexCoordProj.y >= 0.0 && ShadowMapTexCoordProj.y < 1.0 &&
           ShadowMapTexCoordProj.z >= 0.0 && ShadowMapTexCoordProj.z < 1.0)
        {
            if(texture2D(ShadowMap, ShadowMapTexCoordProj.xy).r <= ShadowMapTexCoordProj.z)
            {
                NdotLD = 0.0;
            }
        }
    }
    
    gl_FragColor = gl_Color;
    if(Texturing == 1) gl_FragColor *= texture2D(Texture, gl_TexCoord[0].st);
    gl_FragColor.rgb *= (gl_LightSource[0].ambient.rgb + gl_LightSource[0].diffuse.rgb * NdotLD) / Attenuation;
}

Shadow map
Download
glsl_shadow_mapping.zip (Visual Studio 2005 Professional)
glsl_shadow_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.