3
votes

I'm trying to implement Variance Shadow Map techniqe with OpenGL API. I have been using tutorial (Fabien Sanglard's Soft shadows with VSM) and followed every step, but my shadowmap looks kind of weird. The main thing i've noticed is that when i change near clipping plane of light (perspective) projection matrix, it starts to look strange.

For example here's how it looks on 1.0f near clipping http://postimg.org/image/rupf6wqcx/ (This result is considered as good)

And here's on 0.1f value http://postimg.org/image/fox04z14z/

Notice that position of the light remains the same.

I've been trying to find what's wrong for 3 days straight with no result. Can you help me with that?

Here's the code for shadow fragment shader.

in vec4 v_position;
out vec4 color;

void main()
{
    float depth = v_position.z / v_position.w;
    depth = depth * 0.5 + 0.5;

    float dx = dFdx(depth);
    float dy = dFdy(depth);

    float moment1 = depth;
    float moment2 = depth * depth - 0.25 * (dx * dx + dy * dy);

    color = vec4(moment1, moment2, 0.0, 1.0);
}

And the Shadow Mapping part from actual render pass fragment shader

in vec4 ShadowPosition;
out vec4 outColor;
uniform sampler2D shadowMap;
vec4 sc;

float chebyshevUpperBound(float distance)
{
    float p = 0.0;

    // We retrive the two moments previously stored (depth and depth*depth)
    vec2 moments = texture2D(shadowMap, sc.xy).rg;

    // Surface is fully lit. as the current fragment is before the light occluder
    if (distance <= moments.x)
        p = 1.0;

    // The fragment is either in shadow or penumbra. We now use chebyshev's upperBound to check
    // How likely this pixel is to be lit (p_max)
    float variance = moments.y - (moments.x * moments.x);
    variance = max(variance, 0.00001);

    float d = distance - moments.x;
    float p_max = variance / (variance + d*d);

    return max(p, p_max);
}

void main()
{
    /* Shadow Mapping */
    vec3 pixColor = vec3(1.0, 1.0, 1.0);
    sc = ShadowPosition / ShadowPosition.w;
    sc = sc * 0.5 + 0.5;
    float visibility = chebyshevUpperBound(sc.z);
    outColor = vec4(visibility * pixColor, 1.0);
}

Vertex shaders are pretty simple, calculating vertexes from light or camera point of view using MVP matrices so I dont think I need to post them.

This code is for initializing and rendering:

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

glGenFramebuffers(1, &FramebufferName);

glGenTextures(1, &light_s.shadowBO);
glBindTexture(GL_TEXTURE_2D, light_s.shadowBO);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1024, 1024, 0, GL_RGBA, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

glGenTextures(1, &light_s.shadowBOZ);
glBindTexture(GL_TEXTURE_2D, light_s.shadowBOZ);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, light_s.shadowBO, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, light_s.shadowBOZ, 0);

while (running)
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        /* Shadow pass */
        glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
        glViewport(0, 0, 1024, 1024);
        glDrawBuffer(GL_BACK);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

        glUseProgram(theShadowProgram);
        glEnableVertexAttribArray(svxposition);

        glUniformMatrix4fv(spmatrix, 1, GL_FALSE, light_s.mProjection);
        glUniformMatrix4fv(svmatrix, 1, GL_FALSE, light_s.mView);
        glUniformMatrix4fv(smmatrix, 1, GL_FALSE, bunny_s.mModel);

        glBindBuffer(GL_ARRAY_BUFFER, bunny_s.vertexBufferObject);
        glVertexAttribPointer(svxposition, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bunny_s.elementBufferObject);
        glDrawElements(GL_TRIANGLES, bunny_s.elementBufferSize, GL_UNSIGNED_INT, NULL);

        glBindBuffer(GL_ARRAY_BUFFER, vbplaneVert);
        glVertexAttribPointer(svxposition, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbplaneElem);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);

        glDisableVertexAttribArray(svxposition);

        /* Rendering pass */
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glViewport(0, 0, 1024, 768);
        glDrawBuffer(GL_BACK);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

        glUseProgram(theProgram);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, light_s.shadowBO);

        glEnableVertexAttribArray(vxposition);
        glEnableVertexAttribArray(normals);

        glUniformMatrix4fv(dpmatrix, 1, GL_FALSE, light_s.mProjection);
        glUniformMatrix4fv(dvmatrix, 1, GL_FALSE, light_s.mView);
        glUniformMatrix4fv(dbmatrix, 1, GL_FALSE, mDepthBias);

        glUniformMatrix4fv(pmatrix, 1, GL_FALSE, camera_s.mProjection);
        glUniformMatrix4fv(vmatrix, 1, GL_FALSE, camera_s.mView);
        glUniformMatrix4fv(mmatrix, 1, GL_FALSE, bunny_s.mModel);

        glUniform3f(campos, camera_s.x, camera_s.y, camera_s.z);
        glUniform3f(lightpos, light_s.x, light_s.y, light_s.z);

        glUniform1i(frsampler, 0);

        glBindBuffer(GL_ARRAY_BUFFER, bunny_s.vertexBufferObject);
        glVertexAttribPointer(vxposition, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ARRAY_BUFFER, bunny_s.normalBufferObject);
        glVertexAttribPointer(normals, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bunny_s.elementBufferObject);
        glDrawElements(GL_TRIANGLES, bunny_s.elementBufferSize, GL_UNSIGNED_INT, NULL);

        glBindBuffer(GL_ARRAY_BUFFER, vbplaneVert);
        glVertexAttribPointer(vxposition, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ARRAY_BUFFER, vbplaneNorm);
        glVertexAttribPointer(normals, 3, GL_FLOAT, GL_FALSE, 0, NULL);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbplaneElem);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);

        glDisableVertexAttribArray(vxposition);
        glDisableVertexAttribArray(normals);
    }
}

It is not optimized, but will do for now.

1
It's going to be difficult to diagnose without code. Can you post some?Chris Mantle
@ChrisMantle I have edited my question with some code samples, hope it helps.Vladislav Mikhalin

1 Answers

3
votes

You can cut your shadowmap storage and memory bandwidth requirements in half if you use GL_RG32F instead of GL_RGBA32F since color.zw (BA) is constant. Also, you are aware that depth * depth changes very slowly vs. depth the closer you get to the near plane with a perspective projection? This leads to precision issues as you can see in your screenshots. Both of these things are briefly discussed in the Integration section of this paper.

Basically, you need to manage your near and far planes more intelligently (fit it more closely to the min/max distance)... this is good practice for any shadow mapping algorithm, but especially important for VSM.

I would consider throwing out the perspective depth altogether and eliminate the issue of non-linear distribution of precision (you do not need projected depth for the algorithm to work, it will work fine if you compute the regular distance from the light). This should help tremendously with the primary issue you are experiencing.