Fix Three.js Z-Fighting Using polygonOffset

This article explains how to resolve z-fighting in Three.js using the polygonOffset material property. You will learn what causes this rendering artifact when dealing with coplanar geometry and receive a step-by-step guide on how to configure your materials to ensure overlapping surfaces render correctly without flickering.

Understanding Z-Fighting in Three.js

Z-fighting occurs when two or more polygons share the same, or nearly the same, 3D space (coplanar surfaces). Because the GPU’s depth buffer has limited precision, it cannot accurately determine which polygon is closer to the camera. This results in the surfaces rapidly flickering and overlapping as the camera moves.

The most effective way to solve this in Three.js without physically moving your geometry is by using the polygonOffset property on your materials.

How to Implement polygonOffset

The polygonOffset property tells the WebGL renderer to artificially nudge the depth calculation of a material either forward or backward in the depth buffer.

To implement this, you need to set three specific properties on your THREE.Material:

  1. polygonOffset: A boolean that enables or disables the offset. Set this to true.
  2. polygonOffsetFactor: A factor that scales the depth offset based on the slope of the polygon relative to the camera.
  3. polygonOffsetUnits: A constant value added to the depth calculation to push the polygon forward or backward.

Code Example

Here is how to apply these properties to a material that you want to render on top of another coplanar surface:

import * as THREE from 'three';

// Create the background material (rendered normally)
const backgroundMaterial = new THREE.MeshBasicMaterial({
  color: 0xcccccc
});

// Create the foreground material (offset to prevent z-fighting)
const foregroundMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  polygonOffset: true,
  polygonOffsetFactor: -1,
  polygonOffsetUnits: -4
});

// Apply to meshes
const backgroundMesh = new THREE.Mesh(geometry, backgroundMaterial);
const foregroundMesh = new THREE.Mesh(geometry, foregroundMaterial);

How to Choose the Right Values

The values you assign to polygonOffsetFactor and polygonOffsetUnits determine how much the material is shifted:

For most desktop and mobile displays, starting with a polygonOffsetFactor of -1 and a polygonOffsetUnits of -4 is highly effective. If you still notice flickering at extreme angles, gradually increase the magnitude of the units (e.g., -1, -8).

Avoid using excessively large numbers, as this can cause the offset material to render through other unrelated objects that should rightfully block it from view.