Advertisement Jump to content
Sign in to follow this  
L. Spiro

Variance Shadow Mapping

This topic is 1875 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

My variance shadow maps don’t have soft edges.  sad.png
I am basing my implementation off the one presented here:
But I am also looking at:
They are basically the same thing (note that I am not yet trying to do filtering or summed-area).
In the first link he builds off a few existing functions he made and the results of standard VSM are supposed to be fairly close to those of what he shows there as interpolated shadow mapping (
So let me start by showing what does work and illustrate that my foundation should be correct.
My versions of his texture2DCompare() and texture2DShadowLerp():

float shadow2dDepth( sampler2D _sSampler, float2 _vCoord ){ return tex2D( _sSampler, _vCoord ).x; }
float tex2DShadowCompare( sampler2D _sSampler, float2 _vCoord, float _fComp ){
	float fDepth = shadow2dDepth( _sSampler, _vCoord );
	if ( fDepth == 1.0f ){ fDepth = _fComp; }
	return step( _fComp, fDepth ); }
float tex2DShadow( sampler2D _sSampler, float4 _vInvTexSize, float2 _vCoord, float _fComp ){
	float fX = _vInvTexSize.x * 0.5f;
	float fY = _vInvTexSize.y * 0.5f;
	float fLb = tex2DShadowCompare(_sSampler,_vCoord+float2(-fX,-fY),_fComp);
	float fLt = tex2DShadowCompare(_sSampler,_vCoord+float2(-fX,fY),_fComp);
	float fRb = tex2DShadowCompare(_sSampler,_vCoord+float2(fX,-fY),_fComp);
	float fRt = tex2DShadowCompare(_sSampler,_vCoord+float2(fX,fY),_fComp);
	float2 fFrac = frac( _vCoord * + float2( 0.5f, 0.5f ) );
	float fA = mix( fLb, fLt, fFrac.y );
	float fB = mix( fRb, fRt, fFrac.y );
	return mix( fA, fB, fFrac.x );

And my result (shadow resolution heavily reduced to make results obvious):

This proves my shadow texture’s .x is the depth I need, my use of step() is fine, etc.

Before showing code for my VSM approach I will show the results (it is usually easier that way around).
Here are my wrong VSM results due to the hard edges that should be soft:


Starting from the beginning of the pipeline, my vertex shader (which is the same as for all of my other shadow routines) is this:

void Main(
	in vec3 _vInPos : POSITION0,
	out vec4 _vOutPos : POSITION0,
	out vec4 _vOutDepth : TEXCOORD0 ) {
	_vOutPos = mul( g_mModelViewProjMatrix, vec4( _vInPos, 1.0f ) );

Pixel shader:

void Main(
	in vec4 _vInPos : POSITION0,
	in vec4 _vInDepth : TEXCOORD0,
	out vec4 _vOutColor : COLOR ) {
	// Remember, X = Z, Y = W, from vertex shader swizzle.
	float fComp = _vInDepth.x / _vInDepth.y;
	fComp *= 0.5;
	fComp += 0.5;

// **** ADDED FOR VSM. **** ->
	float fX = ddx( fComp );
	float fY = ddy( fComp );
// <- **** ADDED FOR VSM. ****
	_vOutColor = vec4( fComp, fComp * fComp + 0.25 * (fX * fX + fY * fY), fComp, 1.0f );

My _vOutColor.xy is an exact match with the one in GPU Gems 3, though the inputs could vary.

Pixel shader part that applies shadowing:

// Exact match for both articles, except I use saturate() for performance.
float linstep( float fLow, float fHigh, float fV ) {
	return saturate( (fV - fLow) / (fHigh - fLow) );

 * Gets the amount of shadowing on the given texel.
 * \param _vPos The position to check for being inside the shadow.
 * \param _zDivide The Z-divide.
 * \return Returns the amount of shadowing to apply to the current pixel.
float ShadowLookUpVsm( in vec2 _vPos, in float _zDivide ) {
	// Get the hard shadow part.
	vec2 vMoments = tex2d( g_sShadowTex, _vPos.xy ).xy;
	float fX = (vMoments.x == 1.0f) ? _zDivide : vMoments.x;
	float fP = smoothstep( _zDivide - 0.000625 * 0.5, _zDivide, fX );
	// Everything above here works.  fX was added to prevent far shadowing
	//	and I used my own magic number for biasing.

	// Below appears to match both articles exactly.
	float fVariance = max( vMoments.y - fX * fX, -0.001 );
	float fD = _zDivide - fX; // Used in a square operation so order of subtraction does not matter.
	float fMaxP = linstep( 0.2, 1.0, fVariance / (fVariance + fD * fD) );
	return saturate( max( fP, fMaxP ) );

So I am basically not seeing where I am going wrong.
The GPU Gems 3 article mentions depth problems and suggests to use a range of depths that are not in projected/normlized space and my result was the same.

By the way, this is for a directional light. Not sure if that matters.
And depths go from 0 = near to 1 = far.

I have checked it in PIX.
Depth (R channel):
Moments (G channel):

Anyone see anything wrong here? Other than the result…

L. Spiro

Edited by L. Spiro

Share this post

Link to post
Share on other sites

The GPU Gems 3 article mentions that filtering is possible with this setup, so I checked the source code in the first link and found he is using linear filtering for that example.

Helpful as his page is, it would really make it complete to mention such details, since we normally think of using point filters for shadow maps.


Problem solved when I switched to linear filtering.



L. Spiro

Share this post

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

  • Advertisement

Important Information

By using, you agree to our community Guidelines, Terms of Use, and Privacy Policy. is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!