Jump to content
  • Advertisement
Sign in to follow this  

OpenGL SAVSM Numeric Instability Problems

This topic is 2686 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

[color="#333333"]Hi guys. Im having issue with loss of precision in my SAVSM setup.

when you see the light moving around the effect is very striking; there is a lot of noise with fragments going black and white all the time. This can be somewhat lessened by using the minvariance (thus ignoring anything below a certain threshold) but then we get even worse effects with incorrect falloff (see my other post).

Im using GLSL 1.2 because I'm on a mac so I dont have access to the modf function in order to split the precision across two channels as described in GPU Gems 3 Chapter 8.

Im using GL_RGBA32F_ARB textures with a Framebuffer object and ping ponging two textures to generate a summed area table which i use with the VSM algorithm.

Shadow Shader.

The Summed tables do seem to be working. I know this because I have a function that converts back from the summed table to the original depth map and the two images do look pretty much the same. Im also using the -0.5 + 0.5 trick in order to get some more precision but it doesnt seem to be helping frown.gif

uniform sampler2D ShadowMap;

uniform float minVariance;

uniform float ambientLevel;

uniform float lightAttenuation;

uniform int shadowMapSize;

uniform float filterSize;

varying vec4 ShadowCoord;

uniform vec4 lightPosition;

varying vec4 lightDir, eyeVec;

varying vec3 vertexNormal;

varying vec3 vertexNormalWorld;

vec3 ShadowCoordPostW;

float step = 1.0 / float(shadowMapSize);

float g_DistributeFactor = 1024.0;

// This is somewhat specific to the sub objects within our QC File so might need to be changed

// TODO - Minus light dir do we think?

float lightLevel() {

float d = ShadowCoordPostW.z - lightPosition.z;

float attenuation = 1.0 / (d * d * lightAttenuation);

return attenuation * max(dot(vertexNormalWorld, -lightDir.xyz), 0.0);


float linstep(float min, float max, float v)


return clamp((v - min) / (max - min), 0.0, 1.0);


float ReduceLightBleeding(float p_max, float Amount)


// Remove the [0, Amount] tail and linearly rescale (Amount, 1].

return linstep(Amount, 1.0, p_max);


// Box Sample Blur - WITH SUMMED TABLES!

vec4 btex2D(vec2 uv) {

float ss = step * filterSize / 2.0;

float xmax = uv.x - ss;

float xmin = uv.x + ss;

float ymax = uv.y - ss;

float ymin = uv.y + ss;

vec4 total = texture2D(ShadowMap, vec2(xmax,ymax)) - texture2D(ShadowMap, vec2(xmax,ymin))

- texture2D(ShadowMap, vec2(xmin,ymax)) + texture2D(ShadowMap, vec2(xmin,ymin));

return total / (filterSize * filterSize);


// Upper Bound VSM Shadow code

float chebyshevUpperBound()


// We retrive the two moments previously stored (depth and depth*depth)

// these are split over r,g and b,a

vec4 moments = btex2D(ShadowCoordPostW.xy);

// float FactorInv = 1.0 / g_DistributeFactor;

// vec4 moments = vec4(splits.y * FactorInv + splits.x, splits.w * FactorInv + splits.z, 0.0,0.0);

//vec4 moments = vec4(splits.x * FactorInv + splits.y, splits.z * FactorInv + splits.w,0.0,0.0);

//moments.x += 0.5; // Since using SUMMED Tables we need to adjust

//moments.y += 0.5;

//vec2 moments = texture2D(ShadowMap,ShadowCoordPostW.xy).rg;

// Surface is fully lit. as the current fragment is before the light occluder

// Hardly ever occurs because the distances will always be greater or very close to equal

if (ShadowCoordPostW.z <= 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, minVariance);

float d = ShadowCoordPostW.z - moments.x ;

float p_max = variance / (variance + d * d);

return p_max;

//return max (1.0 - p_max, 0.0);


void main()


ShadowCoordPostW = 0.5 * (ShadowCoord.xyz / ShadowCoord.w + 1.0);

float shadow = ReduceLightBleeding(chebyshevUpperBound(),0.15);

float litFactor = (1.0 - ambientLevel) * (shadow) * lightLevel();

//gl_FragColor = gl_Color * smoothstep(ambientLevel,1.0,max(lightLevel(),shadow));

gl_FragColor = gl_Color * (litFactor + ambientLevel);


varying vec4 v_position;

varying float tDepth;

float g_DistributeFactor = 1024.0;

void main()


// Is this linear depth? I would say yes but one can't be utterly sure.

// Could try a divide by the far plane?

float depth = v_position.z / v_position.w ;

depth = depth * 0.5 + 0.5; //Don't forget to move away from unit cube ([-1,1]) to [0,1] coordinate system

vec2 moments = vec2(depth, depth * depth);

// Adjusting moments (this is sort of bias per pixel) using derivative

float dx = dFdx(depth);

float dy = dFdy(depth);

moments.y += 0.25 * (dx*dx+dy*dy);

// Subtract 0.5 off now so we can get this into our summed area table calc

//moments -= 0.5;

// Split the moments into rg and ba for EVEN MORE PRECISION

// float FactorInv = 1.0 / g_DistributeFactor;

// gl_FragColor = vec4(floor(moments.x) * FactorInv, fract(moments.x ) * g_DistributeFactor,

// floor(moments.y) * FactorInv, fract(moments.y) * g_DistributeFactor);

gl_FragColor = vec4(moments,0.0,0.0);


[color="#333333"]So close and yet so far! Argh! ><

Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!