Alternatives to Using Z-Bias to Fix Z-Fighting Issues

Introduction

by Matt McClellan and Kipp Owens

There are many instances in 3D applications where two polygons may lie on the same plane, as in the cases of effects like bullet holes or posters on walls. Because these polygons lie on the same plane, they share the same z-buffer values, and this can result in "z-fighting" issues, where results vary based on rendering order. In the past, DirectX* allowed developers to resolve z-fighting issues by applying a “z-bias” to co-planar polygons. While applying a z-bias is an effective solution, it does not generate the same results on all graphics hardware.

Unfortunately, in versions of DirectX prior to DirectX 9, different graphics vendors interpreted the DirectX specification details on the z-bias feature differently. As a result, different graphics vendors applied slightly different algorithms to address the application of z-bias. Worse, a legacy of applications taking these different driver behaviors into account meant that the different hardware vendors could not subsequently change their driver behaviors to be more consistent.

For the developer community, this ambiguity resulted in a lot of custom ‘tweaking’ of the z-bias values and subsequent testing across a wide array of hardware; in short, each piece of hardware might behave differently. Microsoft has resolved this issue in DirectX 9 by replacing z-bias with "depth-bias," in hopes of providing a more predictable and uniform technique for removing z-fighting issues.

The D3DRS_ZBIAS render state was used on DirectX 8 and earlier applications, while the D3DRS_DEPTHBIAS render state is used in DirectX 9. This article outlines three alternatives to using the legacy method of D3DRS_ZBIAS.

Figure 1. Z-fighting from co-planar polygons.

Figure 1 above shows the affects of z-fighting on co-planar polygons. ZFightingDemo is a modified version of Billboard, available in the DirectX SDK, that demonstrates z-fighting.


Alternative Method 1: Projection Matrix


The first method considered here is the use of a new projection matrix. This new projection matrix is loaded with near and far clipping planes pushed out (away from the viewer). The new, 'closer' projection matrix is loaded after the 'far' object and before the object or objects that the developer would like to appear in front. The desired 'front' objects are effectively placed closer to the viewer in the z-buffer, but their location in the view space is not noticeably changed. The sample code below accomplishes this technique. In this case it is applying a z-bias to the posters.

The following code snippet shows the Projection Matrix alternative to using a DirectX z-bias call:

 
// ZFighting Solution #1 - Projection Matrix
D3DXMATRIX mProjectionMat; // Holds default projection matrix
D3DXMATRIX mZBiasedProjectionMat; // Holds ZBiased projection matrix
// Globals used for Projection matrix
float g_fBaseNearClip = 1.0f;
float g_fBaseFarClip = 100.0f;
// Code indicates no change. ie states 'near and far clipping planes pushed out' but only far appears pushed
float g_fNearClipBias = 0.0f;
float g_fFarClipBias = 0.5f;

// Projection Matrix work around
// Best if calculation are done outside of render function.

// The "zbiased" projection has it near and far clipping
// planes pushed out...
D3DXMatrixPerspectiveFovLH( &mZBiasedProjectionMat, D3DX_PI/4,(mProjectionMat._22/mProjectionMat._11),
g_fBaseNearClip + g_fNearClipBias,
g_fBaseFarClip + g_fFarClipBias );
. . .

// Original projection is loaded
m_pd3dDevice ->SetTransform( D3DTS_PROJECTION, & mProjectionMat);

// Billboards are rendered...

// The "zbiased" projection is loaded ...
m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mZBiasedProjectionMat);

// Posters are rendered...

// Original projection is reloaded...
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, & mProjectionMat);

. . .


 

While some adjustments to the projection matrix may still be necessary to get the desired results, this technique is more consistent across a variety of graphics hardware. The result of the alternate solution is pictured below:
 

Figure 2. Z-fighting resolved with projection modification solution.

Alternative Method 2: Viewport


The viewport method is similar to the projection matrix method, in that it effectively pushes the selected object nearer to the user in the z-buffer. The viewport method achieves this resolution by loading a new viewport object with new minimum and maximum z-values. The sample of code below accomplishes this by applying a z-bias to the posters, so that they are correctly displayed on the billboards.

The following code snippet shows the viewport alternative to using a DirectX z-bias call:

 
// ZFighting Solution #2 - Viewport
D3DVIEWPORT9 mViewport; // Holds viewport data
D3DVIEWPORT9 mNewViewport; // Holds new viewport data

// Global used for Viewport
// Hard coded for ZBIAS of 1 using this formula
// MinZ - 256/(2^24-1) and
// MaxZ - 256/(2^24-1)
// 2^24 comes from
the amount of Zbits and the 256 works
// for Intel ® Integrated Graphics, but can be any
// multiple of 16.
float g_fViewportBias = 0.0000152588f;

// Projection Matrix work around
// Viewport work around
m_pd3dDevice->GetViewport(&mViewport);

// Copy old Viewport to new
mNewViewport = mViewport;

// Change by the bias
mNewViewport.MinZ -= g_fViewportBias;
mNewViewport.MaxZ -= g_fViewportBias;
. . .

// Original viewport is reloaded …
m_pd3dDevice->SetViewport(&mViewport);

// Billboards are rendered …

// The new viewport is loaded …
m_pd3dDevice->SetViewport(&mNewViewport);

// Posters are rendered …

// Original viewport is reloaded …
m_pd3dDevice->SetViewport(&mViewport);

. . .


 

Again, some adjustments to the new viewport values may still be necessary to get the desired results, but this technique is more consistent across a variety of graphics hardware than using z-bias. The hard-coded example above is the equivalent of D3DRS_ZBIAS = 1. The result of the alternate solution is pictured below:
 

Figure 3. Z-fighting resolved with viewport modification solution.

Alternative Method 3: Depth Bias


The last method addressed in this article uses the DirectX 9 Depth Bias method to solve z-fighting. A check to verify that the graphics card is capable of performing depth bias is needed. Intel Integrated Graphics will support depth bias in the next graphics core code named Grantsdale. After checking the cap bits to verify that depth bias is supported, this technique merely requires setting D3DRS_SLOPESCALEDEPTHBIAS and D3DRS_DEPTHBIAS to the proper values to get the desired effect.

The following code snippet shows the depth-bias alternative to using a DirectX z-bias call:

 
BOOL m_bDepthBiasCap; // TRUE, if device has DepthBias Caps

// Globals used for Depth Bias
float g_fSlopeScaleDepthBias = 1.0f;
float g_fDepthBias = -0.0005f;
float g_fDefaultDepthBias = 0.0f;

// Check for devices which support the new depth bias caps
if ((pCaps->RasterCaps & D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS) &&
(pCaps->RasterCaps & D3DPRASTERCAPS_DEPTHBIAS))
{
m_bDepthBiasCap = true; // TRUE, if DepthBias Caps
}

// Billboards are rendered...

// DepthBias work around
if ( m_bDepthBiasCap ) // TRUE, if DepthBias supported
{
// Used to determine how much bias can be applied
// to co-planar primitives to reduce z fighting
// bias = (max * D3DRS_SLOPESCALEDEPTHBIAS) + D3DRS_DEPTHBIAS,
//
where max is the maximum depth slope of the triangle being rendered.
m_pd3dDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, F2DW(g_fSlopeScaleDepthBias));
m_pd3dDevice->SetRenderState(D3DRS_DEPTHBIAS, F2DW(g_fDepthBias));
}

// Posters are rendered...

if ( m_bDepthBiasCap ) // TRUE, if DepthBias supported
{
// DepthBias work around
// set it back to zero (default)
m_pd3dDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, F2DW(g_fDefaultDepthBias));
m_pd3dDevice->SetRenderState(D3DRS_DEPTHBIAS, F2DW(g_fDefaultDepthBias));
}

. . .


 

Like the other methods (and like the original z-bias), some tweaking may be necessary, but using D3DRS_SLOPESCALEDEPTHBIAS and D3DRS_DEPTHBIAS is a relatively consistent technique for resolving z-fighting issues across a wide selection of graphics devices. The figure below shows the result of this alternate solution:
 

Figure 4. Z-fighting solved with depth bias solution.
As Figure 4 shows, care should be taken for adjusting the D3DRS_SLOPESCALEDEPTHBIAS and D3DRS_DEPTHBIAS. They can be very sensitive and lead to other issues like the problem below for distant objects:
 
Figure 5. Depth-bias solution possible issue: unwanted overlapping polygons.

Conclusion


Z-fighting is an inevitable issue when dealing with co-planar polygons. The three methods shown in this paper – loading a new projection matrix, loading a new viewport, and using the new DirectX 9 Depth Bias – can all be used as alternatives to z-bias with broad success. These techniques cannot eliminate the need for solid testing, but they can limit the amount of tweaking that is required as new problems arise stemming from the inconsistent behavior of z-bias.

The included sample code provides the developer with simple examples of z-bias alternatives that can be used to eliminate z-fighting.


Additional Resources

The following materials will be of particular interest to the audience of this article:


About the Authors

Matt McClellen is a applications engineer with Intel Corporation in Folsom, CA. He has held various positions since joining Intel in 1996.  His current focus is predominantly in the area of software optimization. He has a Bachelors of Science in Computer Engineering from California State University, Sacramento.

Kipp Owens (photo not available) is an applications engineer with Intel Corporation in Folsom, CA.

 

For more complete information about compiler optimizations, see our Optimization Notice.