Emulate Hardware Shadow Mapping (2)

Continue with my previous post. The key to generate correct result is to set the texture parameter GL_TEXTURE_MAG_FILTER to GL_NEAREST when trying to emulate hardware PCF by one’s own. The code used in my shader is showing here:

vec4 newTexPos = texPos / texPos.w;

vec2 texmapscale = vec2(1/512.0, 1/512.0);

vec2 shadowMapCoord = vec2(512, 512) * newTexPos.xy;
vec2 lerps = fract(shadowMapCoord);

float result[4];

float bias = -0.0;

result[0] = texture(depthTex, newTexPos.xy).r + bias > newTexPos.z + bias? 1.0 : 0.0;
result[1] = texture(depthTex, newTexPos.xy + vec2(1.0/512, 0)).r + bias > newTexPos.z ? 1.0 : 0.0;
result[2] = texture(depthTex, newTexPos.xy + vec2(0, 1.0/512)).r + bias > newTexPos.z ? 1.0 : 0.0;
result[3] = texture(depthTex, newTexPos.xy + vec2(1.0/512, 1.0/512)).r + bias > newTexPos.z ? 1.0 : 0.0;

float shadowCoeffs = mix(mix(result[0], result[1], lerps.x),
mix(result[2], result[3], lerps.x),

The above code does a bi-linear interpolation between the four samples, therefore, there is no need to turn on GL_LINEAR for GL_TEXTURE_MAG_FILTER. Also, the result here:

Figure 7

You may refer to the two threads, thread on OpenGL forum and thread on MSDN forum for further details.


Emulate Hardware Shadow Mapping

I started with the fundamental shadow mapping technique. The shadow mapping effect depends on how you defines the texture object in the application and how you carry out shadow comparison in the fragment shader.

The easiest shadow mapping technique requires to define the texture object with the following code:

glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);



Pay special attention to the last two texture parameters, GL_TEXTURE_COMPARE_MODE and GL_TEXTURE_COMPARE_FUNC, which only work for depth texture.

In the fragment shader, the texture uniform is defined as

uniform sampler2DShadow depthTex;

then use the following function to directly return a value that represents how much the current fragment is in shadow:

float shadeFactor = textureProj(depthTex, texPos);

Refer to Figure 1 for the result.

Figure 1

However, notice that the value of the third parameter for the texture object, GL_TEXTURE_MAG_FILTER, has been assigned to GL_LINEAR, so the hardware automatically did a 4-sample percentage-closer filtering(PCF) for us, which smoothed out the transition from unshadowed area to shadowed area. And Figure 2 shows the result after I changed the value for the parameter to GL_NEAREST.

Figure 2

Now set back GL_TEXTURE_MAG_FILTER to GL_LINEAR before go on to try more samples.

The change from last program only lies in the fragment shader. The following function can be used to do a projected texture map read with an offset given in texel units. The variable texmapscale is a vec2 containing 1/width and 1/height of the shadow map.

float offset_lookup(sampler2DShadow map, vec4 loc, vec2 offset)
vec2 texmapscale = vec2(1/512.0, 1/512.0);
return textureProj(map, vec4(loc.xy + offset * texmapscale * loc.w, loc.z, loc.w));

We can implement the 16-sample version in a fragment program as follows:

float sum = 0;
float x, y;

for (y = -1.5; y <= 1.5; y += 1.0)
for (x = -1.5; x <= 1.5; x += 1.0)
sum += offset_lookup(depthTex, texPos, vec2(x, y));

float shadeFactor = sum / 16.0;

The result is shown in Figure 3. For detail explanation of the code above, refer to http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html.

Figure 3

Because I want to apply PCF to the opacity maps that cannot be treated as depth textures, thus I have to emulate the hardware PCF.

The first thing I need to do, is to change the texture object definition. Change the value for the parameter GL_TEXTURE_COMPARE_MODE to GL_NONE, so the textureProj function will not do a comparison, and the expected return value from textureProj should be the depth value in the sampled texture. In addition, I manually did a comparison between the current depth value and the depth value in the texture as follows:

if(texCoord.z < depth)
return 1.0;
return 0;

But this time, the result was not as expected. The area within the view of the light was all black, as if all the comparisons returned 0 (Figure 4).

Figure 4

Therefore I tried another way. Changed the definition of the texture uniform to

uniform sampler2D depthTex;

thus the function textureProj no longer takes in a sampler2DShadow, and returns a vec4, which should be the depth value in the texture. I took the value from the R channel and compared with the depth of the fragment, but the result was also out of expected (Figure 5).

Figure 5

I will stop here until I find better solutions. I also found some strange phenomenon that might be helpful in solving my current problem.

Back to the 16-sample PCF, keep everything unchanged except the GL_TEXTURE_MAG_FILTER parameter, here I assign GL_NEAREST, and the result becomes a little like Figure 5 (Figure 6).

Figure 6