Shadow Mapping - Self-Shadowing Z-Fighting Artifacts
Is there *any* good way to fix the z-fighting artifacts that happen with shadow mapping? Every article basically says to render back faces into the shadow map (which I do) and to use glPolygonOffset. Using an offset I can solve some of the artifact problems, but it doesn't solve artifacts at shadow edges and introduces new artifacts where there were none before.
An image
Is there some shadow technique that helps fix it? I've tried every combination of offset imaginable. Moving the glPolygonOffset call anywhere and everywhere. Increasing the shadow map resolution to frightening levels. Adjusting the light frustum. Etc...
An image
Is there some shadow technique that helps fix it? I've tried every combination of offset imaginable. Moving the glPolygonOffset call anywhere and everywhere. Increasing the shadow map resolution to frightening levels. Adjusting the light frustum. Etc...
Read Forsyth's papers on shadows, which describe using an 8 bit object ID to eliminate shadow acne.
arekkusu Wrote:Read Forsyth's papers on shadows, which describe using an 8 bit object ID to eliminate shadow acne.
Very interesting. However, it says that the 8-bit object-ID buffers can only be used for curing acne on shadows cast from one object onto another. They can't be used for self-shadowing, which is where most of my problems lie. I assume a 16-bit per-triangle buffer can be used for self-shadowing, but the slides also say that it requires hardware support (which is limited at this time).
EDIT: Weird that I was just on that page for the Trilights and Spherical Harmonics papers, and completely missed the shadow stuff.
Try changing the values you pass in to glPolygonOffset. The optimal values will depend on things such as your units of measure, your viewport setup, etc. Once you have a decent value for glPolygonOffset, doing some filtering on the pixel shader will also help get rid of some artifacts as well.
I did something really hacky but it did fix the self-shadow acne problem for me. What I did was to simply say that shadow is 100% for any fragment who's triangle normal is facing away from the light. This is to say, the planar normal, not the interpolated vertex normal.
It's a dirty trick, but it eliminated 99% of shadow acne for me.
Now, this does cause all triangles facing away from the light to be 100% in shadow. From a geometric standpoint this is correct. But for smoothly interpolated normals it can look wonky since you get a harsh triangle edge. But this is still better looking than the acne, so I'm OK with it.
It's a dirty trick, but it eliminated 99% of shadow acne for me.
Code:
float shadow = shadow2DProj( ShadowMap, ShadowCoord ).r;
shadow += shadow2DProj( ... ).r;
// blah blah blah
vec3 ec_normal = normalize(cross(dFdx(ecPos), dFdy(ecPos)));
float lambertian = dot(ec_normal,normalize( gl_LightSource[0].position.xyz - ecPos ));
return step( 0.004, lambertian ) * ( shadow / 8.0 );Now, this does cause all triangles facing away from the light to be 100% in shadow. From a geometric standpoint this is correct. But for smoothly interpolated normals it can look wonky since you get a harsh triangle edge. But this is still better looking than the acne, so I'm OK with it.
Umm, since the back faces will always be completely dark, the z-fighting should not matter at all, as the faces don't receive any light from that source in the first place.
Also, DON'T DON'T DON'T use shadow2D*(), it just plain sucks, on some (all?) hardware you can't have linear filtering, but get some pseudo-PCF which looks like crap. You also don't really have control of bias.
Leave GL_TEXTURE_COMPARE_MODE_ARB at GL_NONE, set GL_DEPTH_TEXTURE_MODE_ARB to GL_INTENSITY, and write your own depth compare sampler instead, if you wish with true PCF.
I've found that doing the above, and turning on linear filtering, 99% of the artifacts disappeared, even though due to crap geometry I have to render both front and back faces into the shadow map.
Also, DON'T DON'T DON'T use shadow2D*(), it just plain sucks, on some (all?) hardware you can't have linear filtering, but get some pseudo-PCF which looks like crap. You also don't really have control of bias.
Leave GL_TEXTURE_COMPARE_MODE_ARB at GL_NONE, set GL_DEPTH_TEXTURE_MODE_ARB to GL_INTENSITY, and write your own depth compare sampler instead, if you wish with true PCF.
I've found that doing the above, and turning on linear filtering, 99% of the artifacts disappeared, even though due to crap geometry I have to render both front and back faces into the shadow map.
DoG Wrote:on some (all?) hardware you can't have linear filtering
It's only older ATI hardware which doesn't support PCF. Everything currently shipping supports it.
DoG Wrote:Umm, since the back faces will always be completely dark, the z-fighting should not matter at all, as the faces don't receive any light from that source in the first place.
Not when the interpolation of vertex normals across a triangle causes a transition from lit to unlit...
Quote:Also, DON'T DON'T DON'T use shadow2D*(), it just plain sucks, on some (all?) hardware you can't have linear filtering, but get some pseudo-PCF which looks like crap. You also don't really have control of bias.
Leave GL_TEXTURE_COMPARE_MODE_ARB at GL_NONE, set GL_DEPTH_TEXTURE_MODE_ARB to GL_INTENSITY, and write your own depth compare sampler instead, if you wish with true PCF.
I've found that doing the above, and turning on linear filtering, 99% of the artifacts disappeared, even though due to crap geometry I have to render both front and back faces into the shadow map.
I'd love to work with you or somebody who understands this better to help me make my shadow mapping code non-sucky. For what it's worth, what I've got looks great... ( cascading shadow maps FTW )
True that, about the back faces, I have forgotten about normal interpolation.
I don't think I fully understand shadow mapping, and unfortunately have very little experience with stuff like PSM, et al. I was working on an automotive headlamp simulation, and that is a very degenerate case for shadow mapping, as the light shines largely *nearly* parallel to the terrain, so from a top-down view, things tend to look like crap, eg. lots of Z-fighting. But with above settings, and shadow sampler as follows, I am getting very viewable results:
One major problem with the builtin shadow2DProj() is that it returns only 0.0 or 1.0, so you don't have the kind of control over the bias as you do manually, even though you could theoretically achieve the same effects via a polygon offset, doing that properly on diverse hardware seems more difficult. shadow2D() seems to be plagued of similar problems, I wasn't able to get that to work as advertised, either.
I don't think I fully understand shadow mapping, and unfortunately have very little experience with stuff like PSM, et al. I was working on an automotive headlamp simulation, and that is a very degenerate case for shadow mapping, as the light shines largely *nearly* parallel to the terrain, so from a top-down view, things tend to look like crap, eg. lots of Z-fighting. But with above settings, and shadow sampler as follows, I am getting very viewable results:
Code:
uniform float shadowBias; // 1.002 seems to be a good starting point
float sampleShadowXY(sampler2D shadowMap, vec2 shadowCoord, float fragDepth)
{
// shadow2DProj doesn't actually return the depth with hardware PCM enabled, eg GL_LINEAR filtering on the depth texture
float shadowDepth = texture2D(shadowMap, shadowCoord).r;
float depth = (fragDepth)/(shadowDepth);
return depth > shadowBias ? 0.0 : 1.0;
}
float sampleShadow(sampler2D shadowMap, vec4 shadowCoord)
{
vec2 nCoord = shadowCoord.xy/shadowCoord.w;
float fragDepth = shadowCoord.z/shadowCoord.w;
return sampleShadowXY(shadowMap, nCoord, fragDepth);
}One major problem with the builtin shadow2DProj() is that it returns only 0.0 or 1.0, so you don't have the kind of control over the bias as you do manually, even though you could theoretically achieve the same effects via a polygon offset, doing that properly on diverse hardware seems more difficult. shadow2D() seems to be plagued of similar problems, I wasn't able to get that to work as advertised, either.
arekkusu Wrote:It's only older ATI hardware which doesn't support PCF. Everything currently shipping supports it.
On the Nvidia 8600M I am usually on, I can't tell, as the occlusion reported by shadow2DProj() doesn't seem to be correct, eg with PCF I'd have expected a value in the range 0..1, but I get either 0.0 or 1.0 exactly. Could of course be a peculiar bug with the particular shader I have, similarly how 'discard' doesn't seem to work.
DoG Wrote:On the Nvidia 8600M I am usually on, I can't tell
So, take a minute to prove it to yourself:
1) launch OpenGL Shader Builder.
2) New fragment shader.
3) modify the default shader to use a shadow sampler, like so:
Code:
uniform sampler2DShadow tex;
uniform float r;
void main()
{
gl_FragColor = shadow2D(tex, vec3(gl_TexCoord[0].xy, r));
}5) in the Symbols tab, animate the "r" uniform.
6) view the Render tab.
You should see bilinear PCF on the resulting shadow comparison. That is, it will look like any other zoomed in blurry texture. On hardware that doesn't support PCF, like the Radeon X1600, you'll see hard diagonal edges instead.
That is somewhat reassuring arekkusu; I have the x1600, and am using shadow2D simply because it made sense logically to do so. But since I've found hardly any material in fairly thorough googling on how to achieve high quality pcf, and since apparently my GPU won't do it anyway, I've just done the old average-8-jittered-samples thing.
Forcing the software renderer in your example I see the PCF...
Also, @DoG:
Your code uses a ternary to return 1 or 0 after the texture2D lookup. So how do you get anything but hard edges? What am I missing?
Forcing the software renderer in your example I see the PCF...
Also, @DoG:
Your code uses a ternary to return 1 or 0 after the texture2D lookup. So how do you get anything but hard edges? What am I missing?
I say you can keep using shadow2D, as long as you're smart about it. If you're reasonably sure that hardware PCF is possible, then you can use it to your advantage. If not, then you need to set it to use nearest neighbor sampling and sample each point for your filter manually. Assuming you can find out what video card the host is running at runtime, it shouldn't be too difficult to have the 2 different code paths.
Or, draw a four pixel gradient with PCF and inspect the results at init time to figure out what it did.
TomorrowPlusX Wrote:Also, @DoG:
Your code uses a ternary to return 1 or 0 after the texture2D lookup. So how do you get anything but hard edges? What am I missing?
That code does indeed just get you a hard shadow, but using that as the basis for a PCF works reliably. If I understand things right, my method gives you a simply linearly filtered shadow depth texture, which gets rid of the block artifacts, and you can additionally add PCF in the shader via your method to get rid of artifacts that occur on faces nearly parallel to the light.
Possibly Related Threads...
| Thread: | Author | Replies: | Views: | Last Post | |
| 2d shadow blending problems | tesil | 1 | 4,675 |
Mar 17, 2011 10:12 AM Last Post: Skorche |
|
| iPhone 3GS Shadowing Bug | Bersaelor | 6 | 9,519 |
Dec 28, 2010 04:08 PM Last Post: jarodl |
|
| OpenGL ES 2.0, 2D Alpha Transparency Artifacts | Macmenace | 3 | 6,075 |
Mar 28, 2010 11:18 PM Last Post: AnotherJake |
|
| Shadowing bug | TomorrowPlusX | 6 | 4,123 |
Feb 1, 2010 12:00 PM Last Post: DoG |
|
| Replacing edges with degenerate quads (for shadow volumes) | Coyote | 9 | 6,396 |
Jan 15, 2010 07:08 PM Last Post: Coyote |
|

