2
votes

I wrote a simple 3D application implementing hard and PCF shadow mapping algorithms using the famous front face culling technique. Unfortunatly, the problem with this technique is only sealed meshes can produce cast shadows. For example a plane can't produce a such effect because a plane is a front face by itself.

So the solution is to use the function 'glPolygonOffset' which the goal is to slightly modify the depth value during the depth rendring path of each vertex visible from the 'light view'. In other words this function is needed to avoid 'shadow acne' artifacts keeping this time all meshes front faces.

Here's a display of a such rendering using hard shadow mapping algorithm:

enter image description here

As you can see the shadow rendering is perfect without any artifacts!

Here's the C++ client code defining the depth texture rendering path:

/*glEnable(GL_CULL_FACE);   //OLD TECHNIQUE
    glCullFace(GL_FRONT);*/

for (uint32_t idy = 0; idy < lightSceneNodeList.size(); idy++)
{   
    if (lightSceneNodeList[idy]->IsShadowEnabled())
    {
        type::ShadowCasterPtr pShadowCaster = ShadowManager::GetSingleton()
            .FindShadowCasterByName(lightSceneNodeList[idy]->GetName());
        {
            pShadowCaster->Bind(TARGET_FBO);
            {
                glEnable(GL_POLYGON_OFFSET_FILL);   //NEW TECHNIQUE
                glPolygonOffset(1.1f, 4.0f);

                glClear(GL_DEPTH_BUFFER_BIT);
                glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
                {
                    pShadowCaster->UpdateFrustrumPosition(
                        lightSceneNodeList[idy]->GetParentModelMatrix());
                    pShadowCaster->SetViewport();
                    {
                        for (uint32_t idx = 0; idx < pBatchList.size(); idx++)
                            pBatchList[idx]->Render(pShadowCaster);
                    }
                }
                glDisable(GL_POLYGON_OFFSET_FILL);
            }
            pShadowCaster->Unbind(TARGET_FBO);
        }
    }
}
//glDisable(GL_CULL_FACE);

And now the code used in the fragment shader code to compute the shadow factor during the second rendering path:

/*
** \brief Recover the depth value from shadow map by projection
*/
float Tex2D_Proj(sampler2DShadow shadowSampler, vec4 LightToVertexDir_LS)
{
    float ShadowFactor = 1.0f;
    {
        vec3 LightToVertexDir_CS = LightToVertexDir_LS.xyz/LightToVertexDir_LS.w;

        ShadowFactor = texture(shadowSampler, LightToVertexDir_CS);
    }
    return (ShadowFactor);
}
/*
** \brief Returns biased hard shadow factor.
*/
float Get_2D_Hard_ShadowFactor(sampler2DShadow shadowSampler, int index)
{
    float shadowFactor = 1.0f;
    {
        if (ShadowCoords[index].z <= MaxShadowDist[index])
        {
            if (ShadowCoords[index].w > 0.0f);
            {
                shadowFactor = Tex2D_Proj(shadowSampler, ShadowCoords[index]);
            }
        }
    }
    return (shadowFactor);
}

The 'ShadowCoords' uniform variable is a vertex position in light space and the 'index' is the light index.

But now I have a problem using PCF shadow mapping algorithm (an example with 4 samples) using also the function 'glPolygonOffset' during the first path:

enter image description here

As you can see we can see clearly 'shadow acne' artifacts!

Here's the code from my fragment shader:

float Get_2D_PCF_ShadowFactor(sampler2DShadow shadowSampler, int index)
{
    float shadowFactor = 0.0f;
    {
        int kernel_base = int(PCFKernelType[index])/2;
        float kernel_count = pow(int(PCFKernelType[index]), 2.0f);

        if (ShadowCoords[index].z <= MaxShadowDist[index])
        {
            if (ShadowCoords[index].w > 0.0f)
            {
                shadowFactor += textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(-1, 1));
                shadowFactor += textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(1, 1));
                shadowFactor += textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(1, -1));
                shadowFactor += textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(-1, -1));

                shadowFactor *= 0.25f;
            }
        }
    }
    return (shadowFactor);
}

The 'textureProjOffset' code is equal to the following one:

float Tex2D_Proj_Offset(sampler2DShadow shadowSampler, vec4 LightToVertexDir_LS, vec2 offsetCoords, vec2 shadowMapSize)
{
    float offset_x = 1.0f/shadowMapSize.x;
    float offset_y = 1.0f/shadowMapSize.y;

    float ShadowFactor = 1.0f;
    {
        vec3 LightToVertexDir_CS = LightToVertexDir_LS.xyz/LightToVertexDir_LS.w;
        vec2 ShadowTexCoords = vec2(LightToVertexDir_CS.x, LightToVertexDir_CS.y);

        vec2 DerivedShadowTexCoords = vec2(
            ShadowTexCoords.x + offsetCoords.x * offset_x,
                ShadowTexCoords.y + offsetCoords.y * offset_y);

        ShadowFactor = texture(shadowSampler, vec3(
            DerivedShadowTexCoords, LightToVertexDir_CS.z));
    }
    return (ShadowFactor);
}

Only the call of 'textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(0, 0))' works correctly (of course it refers to the first hard shadow mapping technique).

If I only use the following call (so a simple hard shadow mapping but using an offset):

shadowFactor = textureProjOffset(shadowSampler, ShadowCoords[index], ivec2(-1, 0));

I have the following rendering:

enter image description here

As you can see, there are 'shadow acne' artifacts only on the right face of the cube!

To resolve my problem I tried several combinations of code adding some bias values to deal with vertex depth value in light space without any success.

1
it's called shadow acneratchet freak
glPolygonOffset works in combination with the front/back face switch -- the back faces are rendered in position; the front faces are rendered with an offset proportional to their slopes. Neither front-face-only rendering, nor constant offsets in light space, are going to be effective in the same way. If you need a plane to cast a shadow, just make it a two sided plane.Justin

1 Answers

0
votes

Don't know if this helps. Maybe I'm over-complicating your question. This code is from an Objective C application, but the relevant code is mostly just C. It runs and works. You'll see in the scene fragment shader a statement: if (depthInShadow > 0.006). This depth in shadow number is something I had to "tweak" to get rid of the acne.

This is an edit. After re-reading your post I see a statement: if (ShadowCoords[index].w > 0.0f). This looks very similar to the depthInShadow statement in my scene fragment shader which I had to "tweak" to something just a little greater than 0.0 to get rid of the acne. Give that a try.

I'll leave the code posted below in case it's of interest to anyone else coming by who's new to shadow mapping.

Many of the variables are declared in the .h file, but you'll get the idea. This is pretty standard shadow mapping code, so if you've seen this all before, you can stop here and save yourself a long read.

I set up the shadow buffer and shaders:

// ************************************* Save the Current Frame Buffer

glGetIntegerv(GL_FRAMEBUFFER_BINDING, renderBuffer);

// ************************************ Create the Shadow Map Texture

glGenTextures(1, shadowTexture);
glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);

glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST );
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST );

glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, SHADOWMAPRATIO * VIEWWIDTH, SHADOWMAPRATIO * VIEWHEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

glActiveTexture(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);

// ************************************ Create the Shadow Map Frame Buffer

glGenFramebuffersEXT(1, shadowBuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, shadowBuffer[0]);

// **************************************** No Color Attachment

glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

// ************************************* Attach the Shadow Texture to It

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, shadowTexture[0], 0);

// ******************************* Check to see if Frame Buffer is Complete

GLenum frameBufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
if(frameBufferStatus != GL_FRAMEBUFFER_COMPLETE)
{
    NSLog(@"There is a problem with the shadow frame buffer, %d", frameBufferStatus);
    if(frameBufferStatus == GL_INVALID_ENUM) NSLog(@"Invalid Enum.");
    if(frameBufferStatus == GL_INVALID_VALUE) NSLog(@"Invalid Value.");
    if(frameBufferStatus == GL_INVALID_OPERATION) NSLog(@"Invalid Operation");
    if(frameBufferStatus == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) NSLog(@"Incomplete Attachment");
    if(frameBufferStatus == GL_FRAMEBUFFER_UNSUPPORTED) NSLog(@"Unsupported");
}

// *********************************** Reset the original Frame Buffer

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, renderBuffer[0]);

// ************************************** Compile and Link the Shadow Shaders

ShaderInfo shadowShaderInfo[] = {
        { GL_VERTEX_SHADER, "path/shadow120.vsh" },
        { GL_FRAGMENT_SHADER, "path/shadow120.fsh" },
        { GL_NONE, NULL }
    };

shadowShaders = LoadShaders(shadowShaderInfo);

glUseProgram(shadowShaders);

shadowPositionLoc = glGetAttribLocation(shadowShaders, "ShadowPosition");

shadowViewMatrixLoc= glGetUniformLocation(shadowShaders, "ShadowViewMatrix");
if(shadowViewMatrixLoc == -1) NSLog(@"View Matrix not found in shadow shader");

shadowModelMatrixLoc= glGetUniformLocation(shadowShaders, "ShadowModelMatrix");
if(shadowModelMatrixLoc == -1) NSLog(@"Model Matrix not found in shadow shader");

shadowProjectionMatrixLoc= glGetUniformLocation(shadowShaders, "ShadowProjectionMatrix");
if(shadowProjectionMatrixLoc == -1) NSLog(@"Projection Matrix not found in shadow shader");

shadowColorLoc= glGetUniformLocation(shadowShaders, "FrontColor");
if(shadowColorLoc == -1) NSLog(@"Front Color not found in shadow shader");

The uniform shadow matrices are, of course, from the camera position.

The shadow shaders used to render into the shadow buffer are trivial.

Shadow Vertex shader:

#version 120

attribute vec4 ShadowPosition;

uniform mat4 ShadowModelMatrix;
uniform mat4 ShadowViewMatrix;
uniform mat4 ShadowProjectionMatrix;

void main()
{
    gl_Position = ShadowProjectionMatrix * ShadowViewMatrix * ShadowModelMatrix * ShadowPosition;
}

The Shadow fragment shader:

#version 120

uniform vec4 FrontColor;

void main()
{

    gl_FragColor = FrontColor;
}

I first render the scene to the shadow buffer with:

glUseProgram(shadowShaders);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, shadowBuffer[0]);

glColorMask ( GL_FALSE , GL_FALSE , GL_FALSE , GL_FALSE );

glActiveTexture(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_2D, shadowTexture[0]);

glClearDepth(1.0);
glClear(GL_DEPTH_BUFFER_BIT);

glUniformMatrix4fv(shadowModelMatrixLoc, 1, GL_FALSE, lightGLKModelMatrix.m);
glUniformMatrix4fv(shadowViewMatrixLoc, 1, GL_FALSE, lightGLKViewMatrix.m);
glUniformMatrix4fv(shadowProjectionMatrixLoc, 1, GL_FALSE, lightGLKProjectionMatrix.m);
glUniform4fv(shadowColorLoc, 1, worldAmbient);

.... rendering code ....

glDisable(GL_POLYGON_OFFSET_FILL);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, renderBuffer[0]);
glColorMask ( GL_TRUE , GL_TRUE , GL_TRUE , GL_TRUE );

I then render the scene normally using these shaders:

Scene Vertex shader:

#version 120

attribute vec4 RingPosition;
attribute vec3 RingNormal;

uniform vec4 LightPosition;

uniform mat4 RingModelMatrix;
uniform mat4 RingViewMatrix;
uniform mat4 RingProjectionMatrix;
uniform mat3 RingNormalMatrix;

uniform mat4 ShadowBiasMatrix;
uniform mat4 ShadowModelMatrix;
uniform mat4 ShadowViewMatrix;
uniform mat4 ShadowProjectionMatrix;

varying float DiffuseIntensity;
varying float SpecularIntensity;
varying vec4 ShadowCoordinate;

const float specularContribution = 1.0;
const float diffuseContribution = 1.0;

void main()
{
    mat4 ShadowMatrix =  ShadowBiasMatrix * ShadowProjectionMatrix * ShadowViewMatrix * ShadowModelMatrix;

    ShadowCoordinate = ShadowMatrix * RingPosition;

    vec3 lightPosition= vec3(LightPosition);
    float shininess = gl_FrontMaterial.shininess;

    vec3 ecPosition = vec3(RingViewMatrix * RingModelMatrix  * RingPosition);
    vec3 tnorm = normalize(RingNormalMatrix * RingNormal);
    vec3 lightVec = normalize(lightPosition - ecPosition);
    vec3 reflectVec = reflect(-lightVec, tnorm);
    vec3 viewVec = normalize(-ecPosition);

    float spec = clamp(dot(reflectVec, viewVec), 0.0, 1.0);
    SpecularIntensity = specularContribution * pow(spec, shininess / 5.0);

    DiffuseIntensity = diffuseContribution * max(dot(lightVec, tnorm), 0.0);

    gl_Position = RingProjectionMatrix * RingViewMatrix * RingModelMatrix * RingPosition;
}

Scene Fragment shader:

#version 120

uniform sampler2D ShadowMap;

varying float DiffuseIntensity;
varying float SpecularIntensity;
varying vec4 ShadowCoordinate;

void main()
{

    vec3 emission = vec3(gl_FrontMaterial.emission);
    vec3 ambient = vec3(gl_FrontMaterial.ambient);
    vec3 diffuse = vec3(gl_FrontMaterial.diffuse);
    vec3 specular = vec3(gl_FrontMaterial.specular);

    // Normalize the Shadow Map coordinates
    // shadowCoordinateWdivide.z = current fragment depth from light

    vec3 shadowCoordinateWdivide = ShadowCoordinate.xyz / ShadowCoordinate.w ;

    float distanceFromLight = texture2D(ShadowMap, shadowCoordinateWdivide.xy).r;

    float depthInShadow = shadowCoordinateWdivide.z - distanceFromLight;

    float specularIntensity = SpecularIntensity;
    float diffuseIntensity = DiffuseIntensity;

    if (depthInShadow > 0.006)
    {
        specularIntensity = SpecularIntensity * 0.0;
        diffuseIntensity = DiffuseIntensity * 0.0;
    }

    vec3 lightColor = emission;
    lightColor = lightColor + ambient;
    lightColor = lightColor + (specularIntensity * specular);
    lightColor = lightColor + (diffuseIntensity * diffuse);

    lightColor = clamp(lightColor, 0.0, 1.0);

    gl_FragColor = vec4(lightColor, 1.0);

}

The shadow bias matrix is:

GLfloat shadowBiasMatrix[16] = {
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0};