3D C/C++ tutorials - OpenGL 2.1 - Fast realistic soft penumbra shadows
3D C/C++ tutorials -> OpenGL 2.1 -> Fast realistic soft penumbra shadows
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:
    ...

protected:
    CTexture Texture;
    CShaderProgram PenumbraShadowMapping;
    GLuint VBO, ShadowMap, FBO;
    vec3 LightPosition;
    mat4x4 LightViewMatrices[5], LightProjectionMatrix, LightTextureMatrices[5];

public:
    bool Pause;
    float Radius;

public:
    ...

public:
    ...
    void RenderShadowMap();
    ...
};

...
opengl_21_tutorials_win32_framework.cpp
...

COpenGLRenderer::COpenGLRenderer()
{
    Pause = false;

    Radius = 0.05f;

    Camera.SetViewMatrixPointer(&ViewMatrix);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

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

    bool Error = false;

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

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

    if(!GLEW_EXT_texture_array)
    {
        ErrorLog.Append("GL_EXT_texture_array 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.LoadTexture2D("texture.jpg");

    Error |= !PenumbraShadowMapping.Load("penumbra_shadow_mapping.vs", "penumbra_shadow_mapping.fs");
    
    // if an error occurred, return false -------------------------------------------------------------------------------------

    if(Error)
    {
        return false;
    }

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

    PenumbraShadowMapping.UniformLocations = new GLuint[3];
    PenumbraShadowMapping.UniformLocations[0] = glGetUniformLocation(PenumbraShadowMapping, "LightPosition");
    PenumbraShadowMapping.UniformLocations[1] = glGetUniformLocation(PenumbraShadowMapping, "CameraPosition");
    PenumbraShadowMapping.UniformLocations[2] = glGetUniformLocation(PenumbraShadowMapping, "LightTextureMatrices");

    // set constant uniforms --------------------------------------------------------------------------------------------------

    glUseProgram(PenumbraShadowMapping);
    glUniform1i(glGetUniformLocation(PenumbraShadowMapping, "Texture"), 0);
    glUniform1i(glGetUniformLocation(PenumbraShadowMapping, "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);

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

    glGenTextures(1, &ShadowMap);
    glBindTexture(GL_TEXTURE_2D_ARRAY, ShadowMap);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT24, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 5, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glBindTexture(GL_TEXTURE_2D_ARRAY, 0);

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

    glGenFramebuffersEXT(1, &FBO);

    // set light position and projection matrix -------------------------------------------------------------------------------

    LightPosition = vec3(0.0f, 2.5f, -5.0f);

    LightProjectionMatrix = perspective(45.0f, 1.0f, 0.125f, 512.0f);

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

    Camera.Look(vec3(0.0f, 1.25f, 5.0f), vec3(0.0f, -0.5f, 0.0f));

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

    return true;
}

void COpenGLRenderer::RenderShadowMap()
{
    // calculate light matrices -----------------------------------------------------------------------------------------------

    LightViewMatrices[0] = look(LightPosition, vec3(0.0f), vec3(0.0f, 1.0f, 0.0f));
    LightTextureMatrices[0] = BiasMatrix * LightProjectionMatrix * LightViewMatrices[0];

    vec3 lp = vec3(0.0f);

    lp += vec3(LightViewMatrices[0][0], LightViewMatrices[0][4], LightViewMatrices[0][8]);
    lp += vec3(LightViewMatrices[0][1], LightViewMatrices[0][5], LightViewMatrices[0][9]);
    lp = normalize(lp);
    lp *= Radius;
    lp += LightPosition;
    
    for(int i = 1; i < 5; i++)
    {
        LightViewMatrices[i] = look(lp, vec3(0.0f), vec3(0.0f, 1.0f, 0.0f));
        LightTextureMatrices[i] = BiasMatrix * LightProjectionMatrix * LightViewMatrices[i];
        
        lp = rotate(lp, 90.0f, vec3(LightViewMatrices[0][2], LightViewMatrices[0][6], LightViewMatrices[0][10]));
    }

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

    glViewport(0, 0, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE);

    for(int i = 0; i < 5; i++)
    {
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBO);
        glDrawBuffers(0, NULL); glReadBuffer(GL_NONE);
        glFramebufferTextureLayerEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ShadowMap, 0, i);

        glClear(GL_DEPTH_BUFFER_BIT);

        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf(&LightProjectionMatrix);

        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixf(&LightViewMatrices[i]);

        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);

        glCullFace(GL_BACK);

        glDisable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST);

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    }
}

void COpenGLRenderer::Render(float FrameTime)
{
    // render shadow map ------------------------------------------------------------------------------------------------------

    if(!Pause)
    {
        RenderShadowMap();
    }

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

    glViewport(0, 0, Width, Height);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&ProjectionMatrix);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(&ViewMatrix);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture);
    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D_ARRAY, ShadowMap);

    glUseProgram(PenumbraShadowMapping);

    glUniform3fv(PenumbraShadowMapping.UniformLocations[0], 1, &LightPosition);
    glUniform3fv(PenumbraShadowMapping.UniformLocations[1], 1, &Camera.Position);
    glUniformMatrix4fv(PenumbraShadowMapping.UniformLocations[2], 5, GL_FALSE, (GLfloat*)LightTextureMatrices);

    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);
    glDrawArrays(GL_QUADS, 0, 52);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glUseProgram(0);

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

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);

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

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

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

    Text.Set("Radius: %.03f", Radius);
}

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);
}

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

    PenumbraShadowMapping.Destroy();

    glDeleteBuffers(1, &VBO);

    glDeleteTextures(1, &ShadowMap);

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

...

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

        case VK_ADD:
            OpenGLRenderer.Radius += 0.001f;
            if(OpenGLRenderer.Pause) OpenGLRenderer.RenderShadowMap();
            break;

        case VK_SUBTRACT:
            OpenGLRenderer.Radius -= 0.001f;
            if(OpenGLRenderer.Radius < 0.0f) OpenGLRenderer.Radius = 0.0f;
            if(OpenGLRenderer.Pause) OpenGLRenderer.RenderShadowMap();
            break;
    }
}

...
penumbra_shadow_mapping.vs
#version 120

uniform vec3 LightPosition, CameraPosition;
uniform mat4x4 LightTextureMatrices[5];

varying vec3 LightDirection, LightDirectionReflected, CameraDirection, Normal;
varying vec4 ShadowTexCoord[5];

void main()
{
    LightDirection = LightPosition - gl_Vertex.xyz;
    LightDirectionReflected = reflect(-LightDirection, gl_Normal);
    CameraDirection = CameraPosition - gl_Vertex.xyz;
    Normal = gl_Normal;
    gl_TexCoord[0] = gl_MultiTexCoord0;
    for(int i = 0; i < 5; i++) ShadowTexCoord[i] = LightTextureMatrices[i] * gl_Vertex;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
penumbra_shadow_mapping.fs
#version 120

#extension GL_EXT_texture_array : enable

uniform sampler2D Texture;
uniform sampler2DArrayShadow ShadowMap;

varying vec3 LightDirection, LightDirectionReflected, CameraDirection, Normal;
varying vec4 ShadowTexCoord[5];

void main()
{
    float Shadow = 0.0;
    
    for(int i = 0; i < 5; i++)
    {
        Shadow += shadow2DArray(ShadowMap, vec4(ShadowTexCoord[i].xy / ShadowTexCoord[i].w, i, ShadowTexCoord[i].z / ShadowTexCoord[i].w)).r;
    }
    
    Shadow /= 5.0;

    float NdotLD = max(dot(normalize(LightDirection), Normal), 0.0) * Shadow;
    float Spec = pow(max(dot(normalize(LightDirectionReflected), normalize(CameraDirection)), 0.0), 32.0) * Shadow;
    
    gl_FragColor = vec4(texture2D(Texture, gl_TexCoord[0].st).rgb * (0.25 + NdotLD * 0.75 + Spec), 1.0);
}
Download

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