Z-fighting for shadow volumes on ATI cards, and a question about NV_DEPTH_CLAMP

I’ve been writing a game on my powerbook – which has an nVIDIA card – and for which all my shadow code is working.

recently I tested it on a machine with an ATI card, and my god was it awful. Z-fighting everywhere. So in the interest of robustness, I’ve isolated the shadow code and made a test app which I’m using to try to solve the problem robustly.

See the screenshots to see the z-fighting I’m talking about:

Works on nVIDIA

Looks like hell on ATI

Now, this looks to me like the occluder geometry is z-fighting with the front-caps of my shadow volumes. ( for reference, I’m using z-fail ). On my nVIDIA, where I wrote this code initially, all it took was a call to glPolygonOffset to push the front caps away from the viewer.

Here’s the relevant code, displaying a pre-calculated volume.

  
void Geometry::drawShadowVolume( void )
{
	/*
		Volume needs to be pushed *backwards* in depth buffer so
		as to eliminate z-fighting with occluder geometry
	*/

	glEnableClientState( GL_VERTEX_ARRAY );
	glVertexPointer( 3, GL_FLOAT, 0, _shadowVolumePoints );
	glDrawArrays( GL_QUADS, 0, _shadowVolumeTop );

	glEnable( GL_POLYGON_OFFSET_FILL );
	glPolygonOffset( 1.0, 1.0 );

	glVertexPointer( 3, GL_FLOAT, 0, _shadowCapPoints );
	glDrawArrays( GL_TRIANGLES, 0, _shadowCapTop );

	glDisable( GL_POLYGON_OFFSET_FILL );
	glDisableClientState( GL_VERTEX_ARRAY );
}

This works on nVIDIA, and not on ATI, as you can see from the screenshots. I’ve attempted several ranges of values for polygon offset but none seem to do the trick on the ATI. Am I missing something?

My second question also regards shadow volumes on ATI cards. I’m using GL_NV_DEPTH_CLAMP to allow me to not have to use an infinite projection matrix for my volume extrusion. Mainly this is because I’m lazy…

So, I googled around a bit and found a fragment program to simulate GL_NV_DEPTH_CLAMP on hardware which doesn’t have that extension.

  
!!ARBfp1.0

MOV result.color, 1.0;
MOV_SAT result.depth, fragment.position.z;

END

I had to add the MOV result.color, 1.0; line as without it nothing at all happened.

This shader, when run on my nVIDIA ( 5200 Fx Go ) works. It does exactly what GL_NV_DEPTH_CLAMP does, and I get nice robust shadows without far-plane capping artifacts.

But, on an ATI card which does support ARB_fragment_program, I get far-plane capping artifacts, as if nothing’s happening. And, to make things worse, I don’t get any errors.

Finally, this is on Mac OSX 10.3, 10.4, in case that makes any difference.

Thanks in advance, I hope somebody can help me.

Your stencil pass eye z values are incompatible with the fragment z you get from post projection matrix screen z on your depthwrite pass.

For shadows cast on terrain Carmack’s reverse is a bit redundant. You could just extrude down a bit and do zpass stencil ops and forget about caps unless the sun is low. Heck even than you don’t cap, just extrude far, capping in the distance trades one anomaly for another on z pass so just extrude far enough.

Actually, I’d rather stick with a consistent zfail, though zpass is faster, since the character often drives under shadow casters, and when driving up a hill the camera can easily go into the vehicle’s shadow.

Anyway, I decided to give an infinite projection matrix a shot and it works quite well. And given that, I was able to have shadow geometry ( for directional lights ) converge on one point at infinity so I reduce vertex throughput nicely. Plus, I don’t rely on GL_NV_DEPTH_CLAMP or shaders, so this works on a GF4MX and on old ATI cards, which is good since Apple sticks laptop users with piss-poor cards. The only issue was having to un-infinitize my projection matrix for frustum culling, but that was no big deal.

Finally, the z-fighting was just an issue of mismanagement. I was already using polygon offset to aid in solid wireframing, I just had to take that into account, and now it works fine.

Thanks for the info, though!