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:
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:
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:
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.