PCF

In the last previous tutorial we explored the depth shadow maps in all it’s aliasing glory.
I showed you ho to fix the steep angle artifacts by using the diffuse term, and the shadow poping by adding a bias to the shadows.
In this lesson we are going to implement something called Percentage Closer Filtering or PCF as it is more commonly called, the name doesn’t exactly reveal what it does.
It is in fact a way to multi sample the shadows and this is pretty simple, so simple that i am not going to supply you with anything other than the new shader file, the rest is exactly the same as tutorial 03a.

1. transform the texture coordinates first two values by a really small amount.
2.make a shadow test against those coordinates.
3. repeat 1 and 2 as many times as needed..
4. take the collective result from the shadow tests and divide it with the number if samples you just did.

Lets demonstrate this with some code.
First we set up some variables + load the other textures besides the shadows.
Since we are also going to do 25 samples we want to eliminate as much as possible from the loop, that is why i added the inverted bias to gl_TexCoord[2].z instead of the shadow value.


float blur_spread[5];
blur_spread[0] = -0.003;
blur_spread[1] = -0.001;
blur_spread[2] = 0.000;
blur_spread[3] = 0.001;
blur_spread[4] = 0.003;

float samples=1.0/25.0;

gl_TexCoord[2] = gl_TexCoord[2]/gl_TexCoord[2].w;
gl_TexCoord[2]=(gl_TexCoord[2]+ 1.0) * 0.5;

gl_TexCoord[2].z -=0.005;
vec4 base = texture2D(texunit0, gl_TexCoord[0].xy);
vec3 norm = texture2D(texunit1, gl_TexCoord[0].xy).xyz*2.0-1.0;

float shade=0.0;
float shadow=0.0;

int x=0;
int y=0;

next do the sampling, note that because a division is more problematic than a add, we pre divided the samples so instead of adding one for each sample we are adding 0.04, this saves us the trouble of having to divide at runtime.


for(x=0;x<5;x++)
{
for(y=0;y<5;y++)
{
shadow = texture2D(texunit2,gl_TexCoord[2].xy+vec2(blur_spread[x],blur_spread[y]));
if(shadow > gl_TexCoord[2].z)
shade+=samples;
}
}

Great now all we have to do is include the rest and we are done


norm = normalize(gl_NormalMatrix * norm);
float fresnel =max((norm.z-0.6)*-1.0,0.0);
float diffuse = max(dot(lightVec, norm),0.0);
float specular = max(dot(reflect(lightVec,norm), viewVec), 0.0)*1.7;
specular=pow(specular,8.0);
shade*=diffuse;
gl_FragColor = (base* shade)+(vec4(0.0,0.1,0.3,0.0)*fresnel)+
(vec4(0.5,0.5,0.4,0.0)*specular*shade);

You can now begin to play around with the numbers, increasing the values in blur_spread will make it a bit more blurry, but then other artifacts show up, these can be fixed by fiddling with the bias parameters, but ultimately PCF is just a hack and won’t work every time.
I originally planned to include a version that changed the blurriness according to the distance from the shadow casting surface, it looked pretty good, unfortunately not good enough in all situations, it was done by getting the distance between the fragment and the shadow map depth value and with this manipulating the values in blur_spread.
It would have worked if it whereat for those meddling kids(read:artifacts).
Test and see if you can do it.

Download the new shader file for this tutorial, to get the rest download the previous tutorial


2 Comments

  • By Barotto, August 28, 2011 @ 23:00

    thanks for the tutorial.

    there’s a little bug in the shader code: blur_spread is a 5 elements array, so it should be x<5 (or x<=4) and not x<4. the same goes for the inner y.

  • By Peter Wallström, August 28, 2011 @ 23:15

    yea, your right, tanks for catching that.
    Though in reality just using a regular array like this is a bit pointless, to get a better result you should really use a small array of random coordinates that changes for each screen coordinate.
    I guess i have to add it to the list of new tutorials to write, im up to like 9 so far and some of them are multiparters.

Other Links to this Post

RSS feed for comments on this post. TrackBack URI

Leave a comment


WordPress Themes