Skip to main content
GameDev.net gamedev.net
🔒 Locked

VSM tweaking

Started by irreversible Feb 6, 2011 at 2:01 PM 4 replies 2.5k views
Original Post
irreversible
irreversible
The attached image shows non-blurred VSM shadows adapted from here. The shadows seem to be working in a general sense (as in, they're there and they're actually correct) - however, there are some some strange artifacts that I can't seem to be able to get rid of: namely the banding-like effect in the non-shadowed part of the scene. Since I've reverted back to the shaders that are available when you click the link, I won't post the shader code, but here's my adaption of the application-side code (yes, it's a bit of a mess, but I'm still working on it ):

PS - ignore the light's frustum drawn as a cone - it's still rendered regularly as a capped pyramid.


//draw into an FBO with the light's depth texture (colorTextureID) as the target
EASSERT(smfbo->Bind());
//run the store depth shader
EASSERT(smsdshader->Enable());
drv->Clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);
drv->LoadIdentity();

//set up the light's POV (this render target is shown in the small window)
drv->LookAt(
light->vPosition,
light->vOrientation,
TVector3D(1, 0, 0));

//the ground is not a closed shape, so disable culling for now
drv->EnableCulling(false, false);
RenderScene(drv, NULL);
//copied from the example
setTextureMatrix();
EASSERT(smsdshader->Disable());
EASSERT(smfbo->Release());

//generate mipmaps
drv->BindTexture(0, colorTextureId);
glGenerateMipmapEXT(GL_TEXTURE_2D);
drv->BindTexture(0, 0);

//draw the geometry from the camera's POV and apply the VSM texture
EASSERT(shadervsm->Enable());
//use the same TU (7) as the example
drv->BindTexture(7, colorTextureId);

//disable back face rendering
drv->EnableCulling(false, true);
drv->Clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);
drv->camera->Look();
RenderScene(drv, NULL);
EASSERT(shadervsm->Disable());


vsmshading.png

Can anyone see anything wrong with this or has seen this kind of an effect before? If so, I'd appreciate some suggestions as to what I'm doing wrong!

Bonus question about fighting shadow acne - I was quite successful in getting rid of it when implementing regular cascaded shadows by using appropriate culling, polygon offset and blending any potential acne in the lighting term itself. However, VSM seems to be a bit more elusive - offseting has no effect and culling, while effective, still leaves visible seams near polygon edges. Increasing texture precision seems to have no effect and increasing resolution lessens the acne, but doesn't get rid of it (or even make it minimal). Is there anything else I can try or should I just try and avoid acute angles?

PS - for some reason GD tells me "This upload failed" when I try to attach my screenshot. Blah.
smasherprog
smasherprog
I believe you are experiencing light bleeding. Try adding this code to your pixel shader to fix the problem

You will have to tweak the lightbleed values to get it correct.
const float lightbleed = .3f;
const float invlightbleed = 1.0f / (1.0f - lightbleed );


// add this inside of your

float chebyshevUpperBound( float distance){
// We retrive the two moments previously stored (depth and depth*depth)
vec2 moments = texture2D(ShadowMap,ShadowCoordPostW.xy).rg;
// Surface is fully lit. as the current fragment is before the light occluder
if (distance <= moments.x)
return 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.00002);

float d = distance - moments.x;
float p_max = variance / (variance + d*d);
p_max= clamp((p_max - lightbleed) * invlightbleed, 0, 1);// light bleeding calculation
return p_max;
}


Also, I think there are some problems in your vsm code ... Here is the code that I used


float VSM_FILTER( float2 tex, float fragDepth ){// fragdepth is the world space distance from the light source to the pixel
float lit = (float)0.0f;
float2 moments = ShadowMap.Sample( FILT_LINEAR, float3( tex, 0.0f ) );

float E_x2 = moments.y;
float Ex_2 = moments.x * moments.x;
float variance = E_x2 - Ex_2;
float mD = (moments.x - fragDepth );
float mD_2 = mD * mD;
float p = variance / (variance + mD_2 );
lit = max( p, fragDepth <= moments.x );
lit = clamp((lit - lightbleed) * invlightbleed, 0, 1);// light bleeding calculation
return lit;
}
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
irreversible
irreversible
Hey, thanks for the reply, smasherprog! I toyed around with the ligh bleed variable a bit and unfortunately I still can't get the artifacts out:

1) I modified your shader code slightly to make the shadows monochromatic
2) I dynamically adjusted the light bleed factor to find the lowest value that retained actual shadow information. The one I came up with is ~0.7

I circled the artifacts in red - I'm ignoring the poly edge bleeding for now as I assume blurring will take care of that. The two red circles show artifacts that only seem to appear at particular rough angles and are related to light bleeding (eg they depend on the light bleed factor); however, I'm not able to find a working set of parameters that would get rid of them.

vsmlightbleed.png


I'm not sure which language your shader is written in, but here's my shader code after mild adjustments. PS - I'm assuming the line:
lit = max( p, fragDepth <= moments.x );

in your shader translates to:

lit = max( p, min(_distance, moments.x));

in GLSL.


float lit = 0.0;
vec2 moments = texture2D(stex, ShadowCoordPostW.xy).rg;//ShadowMap.Sample( FILT_LINEAR, float3( tex, 0.0f ) );

float E_x2 = moments.y;
float Ex_2 = moments.x * moments.x;
float variance = E_x2 - Ex_2;
float mD = (moments.x - _distance);
float mD_2 = mD * mD;
float p = variance / (variance + mD_2);

//use a uniform to control the light bleed factor
float lightbleed = fLightBleed;
float invlightbleed = 1.0 / (1.0 - lightbleed );

float f = min(_distance, moments.x);
lit = max( p, f);
lit = clamp((lit - lightbleed) * invlightbleed, 0.0, 1.0);// light bleeding calculation
//reverse shadows and make them monochromatic
lit = 1.0 - lit;
return lit < 1.0 ? 0.0 : 1.0;

smasherprog
smasherprog
in the code . . . .
lit = max( p, fragDepth <= moments.x );

fragDepth <= moments.x will evaluate to true or false. So, if fragdepth is less than or equal to the moments, it will evaluate to 1, othwerwise it will be 0.

It is a handy way to skip a branch and get the results you want. It is the same as your code where you post this to check and see if the point is totally in the light. In your code, you have a branch, but what I posted does more code without an early out, so which ever way you want to do it, not sure which is faster.
if (distance <= moments.x)
return 1.0 ;


Also, forgot to mention, if you want an easy way to sample your shadow map without running a check to see if the point is within the light frustum, you can sample your shadow map using CLAMP with a border value of 1. This means if you fetch a texture outside of the shadow map, it will return 1. You can use 0 too if you want the rest of your scene to be in shadow, which might make more sense.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
irreversible
irreversible
Thanks for the clarification! I think I have the shadows working - now the main problem remaining is the acne part. Here's a gratuitous screenshot of a worst case scenario:

vsmacne.png

I've tried toying with the minimum variance, but it comes nowhere close to reducing the acne to negligible levels. Reading the GPU Gems article I'm left with the impression that the already included partial derivatives and variance clamping should generally take care of the problem. I can't really increase the shadow map resolution any further, so I'm wondering if there are any other tricks I might employ in extreme cases. The only one I can think of myself is blending the shadow term faster as the angle of the light decreases so that when the acne pops up, it's essentially transparent.

Any ideas?
smasherprog
smasherprog
Are you blurring correctly? TO do the blur right you have to do two passes: an up/down pass and a left/right pass.

I believe that I only saw a single diagonal blur in your code.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.