Shadow Mapping (front face) edge artifact

Started by
14 comments, last by kalle_h 5 years, 5 months ago
41 minutes ago, Lewa said:

I simply render a normal back face culled shadowmap and reconstruct the normals from there.

Maybe that's the key. As i have used the normal from triangle directly this often misaligned with neighboring triangles when shadowing.

Ah, i read you plan to do this as well. Let us know how it works out. Also try to render sphere carved out of something, so curvature has the other sign too...

Personally i did this with software rendering for a global illumination many views approach (tiny framebuffers, e.g. 8x8 or 16x16), so that's quite a different scenario.

 

Advertisement
7 hours ago, JoeJ said:

Maybe that's the key. As i have used the normal from triangle directly this often misaligned with neighboring triangles when shadowing.

Ah, i read you plan to do this as well. Let us know how it works out. Also try to render sphere carved out of something, so curvature has the other sign too...

Personally i did this with software rendering for a global illumination many views approach (tiny framebuffers, e.g. 8x8 or 16x16), so that's quite a different scenario.

 

I'm done with the implementation now. :D  (at least rougly.)

Made a ton of screenshots to show the advantages (and some still existing issues) of this technique.

Now, after testing that technique i understand why you got artifacts/visible wireframes in your implementation. If your sampling resolution is too low, detailed geometry which occupies a single fragment can't be properly reconstructed. (Thus the reconstruction yields wrong results and you get unwanted shadowing.)

I have a similar issue here at the low 512x512 sampling resolution:

ShadowMap (4 cascades, each with 512x512 resolution):

Spoiler

sampling.thumb.png.524b37b03b0dc9ca96212f0bbd391007.pngsamling_2.thumb.png.8edd9bf06b27e8a37444347f4adfb80e.png

However, if we up the resolution to 2k, the results are a whole lot better (the issue still persists, but is much less noticeable):

ShadowMap (4 cascades, each with 2048x2048 resolution):

Spoiler

noShadowAcne.thumb.png.4c469074b11cd2a724a1f225526d74c8.pngshadowBias.thumb.png.cfdf1f1c430ae2b43e9ba5525619986b.pngshadowBias3.thumb.png.8ad899edcfdb7e3eef0c47f6be2abf57.png

There is basically no shadow acne in the distance (well, almost) and the peter-paning effect is kept to a minimum (as the bias value can kept very small due to the plane reconstruction)

 

Now, the issue is that shadow acne still exists but only on faces which are almost perpendicular to the sun/light vector:

Spoiler

acne1.thumb.png.9ee591e2c423a929f6be311b0917514a.pngacne2.thumb.png.affbbcd7609b46bd34d9bcb7944c0e86.png

You can see the view-space normals of the cascade in the lower left corner. The face which has shadow acne is almost perpendicular (the face is painted red in the normal buffer preview in the first screenshot). in the Second screenshot the wall is 100% perpendicular to the sun (the wall is not even visible in the normal buffer).

I'm not 100% sure what causes the shadow acne in this case. (Have to investigate further.)

So the remaining issues are:

  1. corners of the geometry can experience self-shadowing (most noticeable at lower shadowmap resolutions)
  2. Shadow acne on very steep (almost perpendicular) faces

Number 1 can be hopefully fixed by doing additional filtering on the edges of the geometry (the normals of each texel can be used to detect edges rather consistently.)

Number 2 ... no idea at the moment.

 

/Edit: Issue number 2 is probably bias related. Now that i reconstruct the depth/face normal i'll have to take that into account before applying the bias. (otherwise if you have an almost perpendicular wall from the suns perspective and then apply bias in the direction of the sun, the overall distance from the bias-corrected depth and the reconstructed depth is too small and you get shadow acne.)

Interesting! (i did not really see wireframes in my 8x8 images - just too tiny, so that was just a guess :) )

22 minutes ago, Lewa said:

Number 2 ... no idea at the moment.

Maybe, for walls that are almost perpendicular, you could max the shadow calculated from the shadow map with a falloff that tends to put in shadow when the light source is close to the plane? (so the solution would be independent from shadow map and its limitations)

I would assume at least it's easier to fix than point 1. So far all the guys that tried to fix shadow maps once and for all jumped out of the window at some point, haha :)

 

Regarding issue number 2: turns out that this might be a floating point precision issue.

Here is an almost perpendicular wall placed at the center of the world (coordiantes are 0/0/0)

Spoiler

precision_2.thumb.png.c64fae0d277e48608a20cc1de3444666.png

Here is the same wall at a steep angle at the position (500/500,0): (z is the up vector)

Spoiler

precision.thumb.png.eee19034c347021e45d146b04598b1db.png

I suppose it has to do with the view space matrix which looses precision the further you move away from the center. :/ (thus the reconstructed position which should move along the plane also looses accuracy which leads to shadow acne.)

I'll have to see if i can optimize my shader. (Currently i'm doing a few things not really optimally, though i'm not sure if this issue can be completely removed even if i keep my matrix operations to a minimum.)

Currently, i do those operations:


//works (verified)
vec3 depthToWorld(vec2 texcoord,float depth,mat4 invViewProj){

	vec4 clipSpaceLocation;
	clipSpaceLocation.xy = texcoord * 2.0f - 1.0f;
	clipSpaceLocation.z = depth * 2.0f - 1.0f;
	clipSpaceLocation.w = 1.0f;

	vec4 homogenousLocation = invViewProj * clipSpaceLocation;
	return homogenousLocation.xyz / homogenousLocation.w;
}	


	//calculate world-space position of fragment
	//-------------
	float cameraDepth = texture2D(sCameraDepth, vTexcoord).r;
	vec3 pixelWorldPos = depthToWorld(vTexcoord,cameraDepth,uInvViewProjection);
	//----------
	//determine cascade
	int cascadeIndex = 0;
	
	for(int i = 0;i<NUM_CASCADES;i++){
		cascadeIndex = i;
		if(cameraDepth <= uCascadeEndClipSpace[i+1]){
			break;
		}
	}
	
	
	//-------
	vec3 shadowCoord = vec3(uSunViewProjection[cascadeIndex]*vec4(pixelWorldPos,1.0));//from world space to projectionspace
	shadowCoord = (shadowCoord+1.0)/2.0;//move coordinates from -1/1 range to 0/1 range (used later for texture lookup)
	//shadowCoord is now the position of the given fragment seen from the players perspective, projected onto the shadowmap
	//------------

the variable "shadowCoord" at the end of those operations probably looses way to much precision which leads to those acne artifacts.(Due to the transformation of the depth from the players View > to World Position > to viewspace from the shadowmaps view.)

/Edit:

Fixed the precision issues! :D

What i did is to move the eye coordinates of the players view matrix to 0/0/0 for the shadowmap depth comparisons. Had to shift the sun view matrix relative to that too in order to not break the shadowmap calculations. But it worked! The precision critical part (depth to world position) is now done from the center of the world. (And this seems to fix the shadowacne)

Now i have to fix issue number 1, but once that is done the results should be close to perfect.

For performance and precision you should concatenate all transfroms to single matrix.(or one per cascade). This is how UE4 does it.https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Shaders/Private/ShadowProjectionPixelShader.usf#L57 If you have precision problems you can calculate matrix operations using doubles on CPU. You can even add scale and bias to get uv coordinates directly.(from -1:1 to 0:1)

 

This topic is closed to new replies.

Advertisement