Search code examples
c++openglshadowcascadeglm-math

Cascaded Shadow Map shimmering


I am currently trying to learn how cascaded shadow maps work so I've been trying to get one shadow map to fit to the view frustum without shimmering. I'm using a near/far plane of 1 to 10000 for my camera projection and this is the way I calculate the orthographic matrix for the light:

GLfloat far = -INFINITY;
GLfloat near = INFINITY;

//Multiply all the world space frustum corners with the view matrix of the light
Frustum cameraFrustum = CameraMan.getActiveCamera()->mFrustum;
lightViewMatrix = glm::lookAt((cameraFrustum.frustumCenter - glm::vec3(-0.447213620f, -0.89442790f, 0.0f)), cameraFrustum.frustumCenter, glm::vec3(0.0f, 0.0f, 1.0f));

glm::vec3 arr[8];
for (unsigned int i = 0; i < 8; ++i)
    arr[i] = glm::vec3(lightViewMatrix * glm::vec4(cameraFrustum.frustumCorners[i], 1.0f));

glm::vec3 minO = glm::vec3(INFINITY, INFINITY, INFINITY);
glm::vec3 maxO = glm::vec3(-INFINITY, -INFINITY, -INFINITY);

for (auto& vec : arr)
{
    minO = glm::min(minO, vec);
    maxO = glm::max(maxO, vec);
}

far = maxO.z;
near = minO.z;

//Get the longest diagonal of the frustum, this along with texel sized increments is used to keep the shadows from shimmering
//far top right - near bottom left
glm::vec3 longestDiagonal = cameraFrustum.frustumCorners[0] - cameraFrustum.frustumCorners[6];
GLfloat lengthOfDiagonal = glm::length(longestDiagonal);
longestDiagonal = glm::vec3(lengthOfDiagonal);

glm::vec3 borderOffset = (longestDiagonal - (maxO - minO)) * glm::vec3(0.5f, 0.5f, 0.5f);

borderOffset *= glm::vec3(1.0f, 1.0f, 0.0f);

maxO += borderOffset;
minO -= borderOffset;

GLfloat worldUnitsPerTexel = lengthOfDiagonal / 1024.0f;
glm::vec3 vWorldUnitsPerTexel = glm::vec3(worldUnitsPerTexel, worldUnitsPerTexel, 0.0f);
minO /= vWorldUnitsPerTexel;
minO = glm::floor(minO);
minO *= vWorldUnitsPerTexel;

maxO /= vWorldUnitsPerTexel;
maxO = glm::floor(maxO);
maxO *= vWorldUnitsPerTexel; 

lightOrthoMatrix = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, near, far);

The use of the longest diagonal to offset the frustum seems to be working as the shadow map doesn't seem to shrink/scale when looking around, however using the texel sized increments described by https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx has no effect whatsoever. I am using a pretty large scene for testing, which results in a low resolution on my shadow maps, but I wanted to get a stabilized shadow that fits a view frustum before I move on to splitting the frustum up. It's hard to tell from images, but the shimmering effect isn't reduced by the solution that microsoft presented: enter image description here


Solution

  • Ended up using this solution:

    //Calculate the viewMatrix from the frustum center and light direction
    Frustum cameraFrustum = CameraMan.getActiveCamera()->mFrustum;
    glm::vec3 lightDirection = glm::normalize(glm::vec3(-0.447213620f, -0.89442790f, 0.0f));
    lightViewMatrix = glm::lookAt((cameraFrustum.frustumCenter - lightDirection), cameraFrustum.frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f));
    
    //Get the longest radius in world space
    GLfloat radius = glm::length(cameraFrustum.frustumCenter - cameraFrustum.frustumCorners[6]);
    for (unsigned int i = 0; i < 8; ++i)
    {
        GLfloat distance = glm::length(cameraFrustum.frustumCorners[i] - cameraFrustum.frustumCenter);
        radius = glm::max(radius, distance);
    
    }
    radius = std::ceil(radius);
    
    //Create the AABB from the radius
    glm::vec3 maxOrtho = cameraFrustum.frustumCenter + glm::vec3(radius);
    glm::vec3 minOrtho = cameraFrustum.frustumCenter - glm::vec3(radius);
    
    //Get the AABB in light view space
    maxOrtho = glm::vec3(lightViewMatrix*glm::vec4(maxOrtho, 1.0f));
    minOrtho = glm::vec3(lightViewMatrix*glm::vec4(minOrtho, 1.0f));
    
    //Just checking when debugging to make sure the AABB is the same size
    GLfloat lengthofTemp = glm::length(maxOrtho - minOrtho);
    
    //Store the far and near planes
    far = maxOrtho.z;
    near = minOrtho.z;
    
    lightOrthoMatrix = glm::ortho(minOrtho.x, maxOrtho.x, minOrtho.y, maxOrtho.y, near, far);
    
    //For more accurate near and far planes, clip the scenes AABB with the orthographic frustum
    //calculateNearAndFar();
    
    // Create the rounding matrix, by projecting the world-space origin and determining
    // the fractional offset in texel space
    glm::mat4 shadowMatrix = lightOrthoMatrix * lightViewMatrix;
    glm::vec4 shadowOrigin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    shadowOrigin = shadowMatrix * shadowOrigin;
    GLfloat storedW = shadowOrigin.w;
    shadowOrigin = shadowOrigin * 4096.0f / 2.0f;
    
    glm::vec4 roundedOrigin = glm::round(shadowOrigin);
    glm::vec4 roundOffset = roundedOrigin - shadowOrigin;
    roundOffset = roundOffset *  2.0f / 4096.0f;
    roundOffset.z = 0.0f;
    roundOffset.w = 0.0f;
    
    glm::mat4 shadowProj = lightOrthoMatrix;
    shadowProj[3] += roundOffset;
    lightOrthoMatrix = shadowProj;
    

    Which I found over at http://www.gamedev.net/topic/650743-improving-cascade-shadow/ I basically switched to using a bounding sphere instead and then constructing the rounding matrix as in that example. Works like a charm