Monday, 21 April 2014

Shadow Mapping + Tutorial

In my last two blogs I talked about a few different Mapping techniques and showed you how to achieve a few of them, now those mapping techniques are primarily used for the manipulation of geometry or the illusion of manipulation. In this blog I'm going to focus on Shadow Mapping, which is another type of mapping however this one is used in the creation and formation of shadows in a scene.


WHAT IS SHADOW MAPPING?

 

                                  No Shadow Mapping                            Shadow Mapping
 Shadow mapping, otherwise known as projective shadowing, is a 3D computer graphics process where shadows are added into 3D scenes. This concept was introduced in 1978 by Lance Williams, a prominent graphics researcher (You can find more info on him HERE),in his paper entitled "Casting Curved Shadows on Curved Surfaces".

In Shadow Mapping, Shadows are created testing whether a pixel is visible from the light source. This is done by comparing it to a z-buffer or depth image of the light source's view and then stored in the form of a texture.

Algorithm Overview:

When creating Shadow Mapping there are two major steps that are required in this algorithm. The first step produces the shadow map itself, and the second applies it to the scene. Depending on how you do your shadow mapping implementation, and how many lights that are in your scene, you may required multiple additional passes.



Step 1: Creating The Shadow Map


=> In the 1st step the scene is rendered from the light's point of view, which should be a perspective projection
=> In this step the depth buffer is extracted and saved, only the depth info is important at this step so avoid updating the colour buffers and disable all lighting and texture calculations
=> The depth map is stored as a texture

Step 2: Shading The Scene

=> In the 2nd step you draw the scene from the usual camera viewpoint and apply the shadow map
=> There are 3 major components in this step:
                    1. Find the coordinates of the objects as seen from the light
                    2. Test which compares that coordinates against the depth map
                    3. Draw the object in either in shadow or light

Step 3: Light Space Coordinates Test 

=> To test a point against the the depth map, its position in the scene coordinates must be transformed into its equivalent position as seen by the light. This is accomplished by Matrix Multiplication.
=> The object location in the scene is found with coordinate transformation, using a second set of coordinates created to find the object in light space.



Step 4: Depth Map Test

=> After finding the light-space coordinates are found, the x & y values = a location in the depth map, and the z value = depth can now be tested against the depth map.
=>If the z value > the valued stored in the depth at an (x,y) location the object is behind an occluding object and drawn in shadow.
=> If the (x,y) location is outside of the the depth map, the programmer decides whether or the surface is light or shadowed by default.

Step 5: Drawing The Scene

=> There are a few ways to draw the scene
            1.  If Shaders are available, the depth map may be performed by a fragment shader which simply draws in shadow or light in a single pass.
            2. If Shaders are unavailable, implementation must be done through hardware extension (ex. GL_ARB_shadow) which does not allow the choice between being lit or shadowed and required more rendering passes.

More Info:

For more information about Shadow Mapping, here are a few links that will help:

SHADOW MAPPING TUTORIAL

 

In this section I will go over what is necessary in your GLSL Shadow Mapping shaders, now these shaders are done in version 330 and may not work in your code and should only be used as a guideline in your own code. Also note that this version of Shadow Mapping also has projected textures implemented as well.
                                                             No Shadow Mapping

Fragment Shader:


#version 330 core

in vertex
{
    vec3 positionObj;
    vec3 normalObj;

    vec2 texcoord;

    vec4 positionLt;
} data;

uniform vec3 eyePos;
uniform vec3 lightPos;
uniform sampler2D diffuseMap;
uniform sampler2D shadowMap;
uniform sampler2D projTex;//projective textures

layout (location=0) out vec4 fragColour;


vec3 PhongLighting(in vec3 fragPos, in vec3 fragNorm,
    in vec3 diffuseColour, in vec3 specularColour)
{
    // ...this is generally a dark constant
    const vec3 ambient = vec3(0.00, 0.00, 0.05);


    // diffuse component
    vec3 N = normalize(fragNorm);
    vec3 L = normalize(lightPos - fragPos);
    float Lambert = max(0.0, dot(L, N));


    // specular coefficient
    vec3 E = normalize(eyePos - fragPos);
    vec3 R = reflect(L, N);
    float Phong = max(0.0, dot(R, -E));
    // pro-tip: doing this is faster than calling the pow() function...
    Phong *= Phong; //^2
    Phong *= Phong; //^4
    Phong *= Phong;    //^8


    // add components
    vec3 diffuse = Lambert * diffuseColour;
    vec3 specular = Phong * specularColour;
    return ( diffuse + specular + ambient );
}


void main()
{
    //this is for projected textures
    vec3 projective = vec3(1.0);

    // 1) Perform clipping manually from point of view
    //so we can sample from the shadow map
    vec4 positionLtClip = data.positionLt;

    //perspective divide ------> normalize device coordinates
    vec4 positionLtNDC = positionLtClip / positionLtClip.w;

   
    //this is for debugging purposes to check if its working
    //it makes rainbow fog :D
    //fragColour = positionLtNDC;
    //return;

    // 2) clip test
    //if we go beyond these values it will skip over this
    //and just do regular lightinf instead of shadow mapping
    if ((positionLtNDC.x > -1) && (positionLtNDC.x < +1) &&
        (positionLtNDC.y > -1) && (positionLtNDC.y < +1) &&
        (positionLtNDC.z > -1) && (positionLtNDC.z < +1))
        {
        //test
            //fragColour = vec4(1.0);
            //return;
            // 3) serialize for screen space
            vec3 positionLtScreen = positionLtNDC.xyz *0.5 + 0.5;

            //test
            //fragColour.rgb = positionLtScreen;
            //return;

            vec2 shadowTexcoord = positionLtScreen.xy;
            float fragmentDepth = positionLtScreen.z;

            // 4) Shadow Test
            float shadowDepth = texture(shadowMap, shadowTexcoord).r;

            //test
            //fragColour = vec4(shadowDepth);
            //return;
            if (fragmentDepth > shadowDepth)
            {
                fragColour = vec4(0.0);
                return;
            }
            else

            projective = texture(projTex, shadowTexcoord).rgb;

        }

    vec3 diffuseColour = texture(diffuseMap, data.texcoord).rgb;
    const vec3 specularColour = vec3(1.0);

    fragColour.rgb = PhongLighting(data.positionObj, data.normalObj,
        diffuseColour, specularColour);
        fragColour.rgb *= projective;
}


Vertex Shader:

#version 330 core

layout (location=0) in vec4 position;
layout (location=2) in vec3 normal;
layout (location=8) in vec2 texcoord;

uniform mat4 MVP;
uniform mat4 lightMVP;

out vertex
{
    vec3 positionObj;
    vec3 normalObj;

    vec2 texcoord;

    vec4 positionLt;
} data;

void main()
{
    gl_Position = MVP * position;

    data.positionObj = position.xyz;
    data.normalObj = normal;
    data.texcoord = texcoord;

    data.positionLt = lightMVP * position;
}


Main.cpp:

These are few things to remember to include in your main.cpp file:

1. Edits to your FBO

        // FBO for the shadow pass
        const unsigned int useColour = 1;    // ******** change this later...
        shadowMapPass->Initialize(shadowMapSize, shadowMapSize, useColour, 1, 0);


2.  1st Pass: Shadow Pass in your Render Function

// PASS 1: SHADOW PASS
        // ******** (1)
                projectionMat = light0Proj;
        eyeToWorld =
            ObjectLookAt(&light0, light0.position,
                bmath::vec::v3y, bmath::vec::v3zero);
        UpdateCameraMatrices();
        light0ViewProj = viewProjectionMat;

        // proceed with drawing the objects
        shadowMapPass->Activate();
        glViewport(0,0,shadowMapSize,shadowMapSize);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shadowPass->Activate();


        // ******** (3A)
        glCullFace(GL_FRONT);

        DrawObjectShadowPass(&ground, cubeRenderable, 1,0,0);
        DrawOBJLoadedObjectShadowPass(&tower, towerOBJ, 0,1,0);
        DrawOBJLoadedObjectShadowPass(&gumbot, gumbotOBJ, 0,0,1);

       
        // ******** (3B)
        glCullFace(GL_BACK);


3. 2nd Pass: Scene Pass

// PASS 2: SCENE PASS
        // draw scene regularly


        // update camera once per-frame
        projectionMat = camera0Proj;
        eyeToWorld =
            ObjectLookAt(&camera0, camera0.position,
                bmath::vec::v3y, bmath::vec::v3zero);
        UpdateCameraMatrices();
       
       
        scenePass->Activate();
        glViewport(0,0,windowWidth,windowHeight);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
       
        PhongWithShadowMap->Activate();

       
        cbmini::cbfw::FrameBuffer::SetTextureInput(3);
        glBindTexture(GL_TEXTURE_2D, penguins);
        cbmini::cbfw::FrameBuffer::SetTextureInput(2);
        shadowMapPass->BindDepth();

       
        DrawObject(&ground, cubeRenderable, groundDM, 0);
        DrawOBJLoadedObject(&tower, towerOBJ, towerDM, 0);
        DrawOBJLoadedObject(&gumbot, gumbotOBJ, gumbotDM, 0);


4. 3rd Pass: Display Pass

    // PASS 3: DISPLAY PASS
        // just slap a texture on a plane!
        cbmini::cbfw::FrameBuffer::Deactivate();
        glDisable(GL_DEPTH_TEST);

        displayPass->Activate();
   
        cbmini::cbfw::FrameBuffer::SetTextureInput();

        // ******** (2)
        /*shadowMapPass*/scenePass->BindColour/*BindDepth*/();

        fsqPlane->ActivateAndRender();



                                                                 Shadow Mapping  
                    
                                                Shadow Mapping with Projected Texture

No comments:

Post a Comment