ShadowMapping Artifacts

Started by
5 comments, last by sepul 17 years, 10 months ago
I'm trying to implement a simple shadow map , but as you can see in the screenshot, there are some strange z-artifacts, here is the shader :

float4x4		_lightViewProjMat;
shared float4x4	_worldViewProjMat;
float4x4		_textureMat;			// transform projection into shadow map texture coords

texture _shadowMapTexture;
texture _diffuseTexture;

sampler _shadowMapSampler = sampler_state
{
	texture = (_shadowMapTexture);
	
	MipFilter = Linear;
	MinFilter = Linear;
	MagFilter = Linear;
	
	AddressU = Clamp;
	AddressV = Clamp;
};

sampler _diffuseSampler = sampler_state
{
	texture = (_diffuseTexture);
	
	MipFilter = Linear;
	MinFilter = Linear;
	MagFilter = Linear;
	
	AddressU = Wrap;
	AddressV = Wrap;
};

struct VS_OUTPUT_SMAP
{
	float4 p : POSITION;
	float depth : TEXCOORD0;
};

struct VS_OUTPUT_UNLIT
{
	float4 p : POSITION;
	float4 t : TEXCOORD0;
	float2 coord : TEXCOORD1;
};

/////////////////////////////////////////////////////////
VS_OUTPUT_SMAP hmrVS_ShadowMap( float4 pos : POSITION )
{
	VS_OUTPUT_SMAP output = (VS_OUTPUT_SMAP)0;
	output.p = mul( pos, _lightViewProjMat );
	output.depth = output.p.z;
	return output;
}

float4 hmrPS_ShadowMap( VS_OUTPUT_SMAP input ) : COLOR
{
	return float4( input.depth, input.depth, input.depth, 1.0f );
}

///////////////////////////////////////////////////////
VS_OUTPUT_UNLIT hmrVS_Unlit( float4 pos : POSITION, float2 coord : TEXCOORD0 )
{
	VS_OUTPUT_UNLIT output = (VS_OUTPUT_UNLIT)0;
	output.p = mul( pos, _worldViewProjMat );
	output.t = mul( pos, _textureMat );
	output.coord = coord;
	return output;
}

float4 hmrPS_Unlit( VS_OUTPUT_UNLIT input ) : COLOR
{
	// 3x3 PCF filtering	
	float4 texCoords[9];
	float texelSize = 1.0f / 100.0f;
	
	// 4  3  5
	// 1  0  2
	// 7  6  8
	texCoords[0] = input.t;
	texCoords[1] = input.t + float4( -texelSize, 0.0f, 0.0f, 0.0f );
	texCoords[2] = input.t + float4( texelSize, 0.0f, 0.0f, 0.0f );
	texCoords[3] = input.t + float4( 0.0f, -texelSize, 0.0f, 0.0f );
	texCoords[6] = input.t + float4( 0.0f, texelSize, 0.0f, 0.0f );
	texCoords[4] = input.t + float4( -texelSize, -texelSize, 0.0f, 0.0f );
	texCoords[5] = input.t + float4( texelSize, -texelSize, 0.0f, 0.0f );
	texCoords[7] = input.t + float4( -texelSize, texelSize, 0.0f, 0.0f );
	texCoords[8] = input.t + float4( texelSize, texelSize, 0.0f, 0.0f );
	
	float shadowTerm = 0.0f;
	for( int i = 0; i < 9; i++ )	{
		float A = tex2Dproj( _shadowMapSampler, texCoords ).r;
		float B = (input.t.z - 0.1f);
		
		// compare A (shadow map) to the new one (B) to see if it is shadowed
		shadowTerm += A < B ? 0.2f : 1.0f;
	}
	
	shadowTerm /= 9.0f;
	
	float4 diffuse = tex2D( _diffuseSampler, input.coord );
	return shadowTerm * diffuse;
}



the Shadow buffer is R32F the "_lightViewProjMat" is set to -> World * LightView * LightProj . the "_worldViewProjMat" is set to -> World * CamView * CamProj the "_textureMat" is set to ->

	float offs = 0.5f + (0.5f / 512.0f /*size of the shadow buffer*/);
	D3DXMATRIX texMat(	0.5f,	0.0f,	0.0f,	0.0f,
				0.0f,   -0.5f,	0.0f,	0.0f,
				0.0f,	0.0f,	1.0f,	0.0f,
				offs, offs,  0.0f, 1.0f );
        _textureMat = World * LightView * LightProj * texMat;
I think the problem is the "float B = input.t.z - 0.1f" line which causes floating point precision errors, but don't know how to solve it , any ideas ?

dark-hammer engine - http://www.hmrengine.com

Advertisement
This looks like a case of common shadow acne. It's because of floating point errors (even when using a floating point texture to store the shadowmap depth values). There are numerous methods for reducing this, including adding a certain bias to the shadow depth value when calculating it. The method I am using is to reverse the culling when rendering the geometry into shadow maps. This means that the depth of the back face will be stored in the shadow map, and will always be farther then the front face. There are limitation to your geometry with this, as things have to be solid (a single face that usually faces the camera will be culled out when rendering shadows, so it will never cast a shadow, unless it has another face behind it facing the other way).
"shadow acne" is well put. Your offset (0.1) is about on the same scale as you shadow pixels. You can see the pixels pretty clearly on the projection of teacup in the background. The problem gets worse as you lose resolution in the background.

I second xycsoscyx suggestion of using closed shapes and rendering backfaces for your shadowmaps instead of front faces. Its a much easier solution than trying to work out bias and floating point resolution.

There is actually an alternative, though, if you don't want to reverse the culling. I don't have links offhand, but it's a method for determining the bias inside the vertex shader. Basically, the greater the angle of the surface (from the cameras point of view), the more bias. Surfaces that are perpendicular to the viewer will get less bias, and things that are angled closer to parallel get a greater bias.

My biggest problem with biasing the shadows is that it creates gaps at corners, and there really isn't a way around that. If you are biasing the shadow depth, then you are pushing the shadows farther away. This means t-junctions will always have small gaps to the shadow. The only time you'll notice shadow acne when reversing is possibly at these junctions, and that's only if you have a very imprecise shadow map.

Also, as a note, you can usually get away with standard 32bit textures with the reversed approach, which saves time and memory since you no longer need a floating point buffer to store the shadow depth (and if you really want, you can always pack the depth into the full RGBA value).
unfortunately, I can't reverse the culling, cuz my main geometry is not closed.
and about the biasing, could you send me a link or something that explains the problem more clearly ?

I've also heard that VSM (variance shadow maps) do not suffer from this biasing problem , is it true ?

dark-hammer engine - http://www.hmrengine.com

Anything that renders the front faces (the faces that the light can see) of the geometry, will suffer from this. I don't know much about variance shadow maps, but I assume it samples the depth using some other value? Your geometry doesn't actually have to be closed, consider this (I hope the ascii shows up right):


| |
| |
-----| |-----


-----| |-----
| |
| |

This is not a closed mesh (in case the ascii doesn't show up, it's just a t junction in a level), but if a light is in the center, then it will still shadow correctly when culling is reversed. For testing, isn't it a simple matter to reverse the culling when you're rendering into your shadow map? Try it and see what it looks like, if it just refuses to work, then you can look into depth biasing, but for many cases, it does work.

I don't know any links offhand, but it's just a matter of adding a depth bias, and scaling that depending on the angle between the face and the view vector (again, perpendicular to the view vector get the least bias). This bias is added to your value when you are rendering the depth into the shadow map (using whatever method, pixel shader, depth bias render state, etc).
I have implemented variance shadow maps, and got really good results from it, the artifacts are completely gone regardless of the geometry and view, and I got better quality shadows than 3x3 PCF filtering with faster speed.
the implementation is easy, fast and rebust, so I will recommend this method to anyone.

check out the new shots :



what is really missing here, is something like a gaussian blur post effect, which will make it much more cooler
here is the original paper :
Variance Shadow Maps

anyway, thanks for the help guys

dark-hammer engine - http://www.hmrengine.com

This topic is closed to new replies.

Advertisement