Z-Fighting and Depth Precision

A 24-bit depth buffer has 16.7 million steps, but perspective spends them unevenly: stored depth follows 1/z (Perspective Projection), so the smallest separable gap grows with the square of the distance and shrinks with near. With near 0.1 and far 20, surfaces 3 units away must be 0.000005 units apart, at 15 units 0.00013; with near 0.01, ten times more. Coplanar surfaces, such as a sticker on a cover, have no gap: rounding decides pixel by pixel which wins, and they shimmer through each other (z-fighting). For decals, polygonOffset(f, u) adds f x the depth slope + u x the smallest depth step while POLYGON_OFFSET_FILL is enabled:

Pulling a coplanar sticker toward the viewer while it is drawnJavaScript
shelf.draw(uniforms);                           // the books, depth test on
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.polygonOffset(-1, -1);                       // nearer by about one depth step
shelf.draw(uniforms, [sticker]);                // a flat box on Salt and Saffron's cover
gl.disable(gl.POLYGON_OFFSET_FILL);

Without the offset (tried for this section), a white sticker on a cover frayed into diagonal stripes of cover color; with it, it was solid. For surfaces that are merely close, raise near first; far matters far less. Reversed-Z (float depth, EXT_clip_control's 0-to-1 depth, GREATER) spreads precision evenly.

A coplanar sticker on a cover z-fighting (left) and fixed with polygonOffset (right)HTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  canvas { display: block; width: 100%; max-width: 600px; }
  .names { display: flex; max-width: 600px; text-align: center; font-family: monospace; }
  .names div { flex: 1; padding: 4px 2px; }
</style>
<canvas id="c" width="600" height="260"></canvas>
<div class="names"><div>coplanar sticker, no offset</div><div>POLYGON_OFFSET_FILL, polygonOffset(-1, -1)</div></div>
<script>
const m4 = {
  perspective(fovy, aspect, near, far) {
    const f = 1 / Math.tan(fovy * Math.PI / 360), d = near - far;
    return [f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) / d, -1, 0, 0, 2 * far * near / d, 0];
  },
  lookAt(eye, target, up = [0, 1, 0]) {
    const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
    const unit = (v) => v.map((c) => c / Math.hypot(...v));
    const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    const z = unit(eye.map((c, i) => c - target[i])), x = unit(cross(up, z)), y = cross(z, x);
    return [x[0], y[0], z[0], 0, x[1], y[1], z[1], 0, x[2], y[2], z[2], 0, -dot(x, eye), -dot(y, eye), -dot(z, eye), 1];
  },
  multiply(a, b) {
    const out = new Array(16).fill(0);
    for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) for (let k = 0; k < 4; k++) out[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k];
    return out;
  },
  box: (x, y, z, sx, sy, sz) => [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, x, y, z, 1],
};
const gl = document.getElementById('c').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec3 aPosition;
uniform mat4 uViewProjection, uModel;
void main() { gl_Position = uViewProjection * uModel * vec4(aPosition, 1.0); }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; uniform vec3 uColor; out vec4 c; void main() { c = vec4(uColor, 1.0); }`]]) {
  const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
  gl.attachShader(program, s);
}
gl.linkProgram(program); gl.useProgram(program);
const u = (n) => gl.getUniformLocation(program, n);

// Two flat rectangles in the z = 0 plane: a cover split into two triangles one way,
// and a sticker split the other way. Same plane, different triangles: rounding decides.
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0,  -0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0,     // cover
  -0.5, -0.5, 0, 0.5, -0.5, 0, -0.5, 0.5, 0,  0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]),   // sticker
  gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.SCISSOR_TEST);

function frame(ms) {
  // A far, glancing view makes depth steps coarse; the slow sway keeps the fight moving.
  const eye = [2.2 + Math.sin(ms * 0.0007) * 0.6, 0.8, 5];
  gl.uniformMatrix4fv(u('uViewProjection'), false,
    m4.multiply(m4.perspective(20, 300 / 260, 0.1, 100), m4.lookAt(eye, [0, 0, 0])));
  [false, true].forEach((offset, pane) => {
    gl.viewport(pane * 300, 0, 300, 260); gl.scissor(pane * 300 + 2, 0, 296, 260);
    gl.clearColor(0.93, 0.91, 0.87, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.uniformMatrix4fv(u('uModel'), false, m4.box(0, 0, 0, 1.0, 1.5, 1));
    gl.uniform3f(u('uColor'), 0.71, 0.27, 0.18);            // Salt and Saffron's cover
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    if (offset) {
      gl.enable(gl.POLYGON_OFFSET_FILL);
      gl.polygonOffset(-1, -1);                             // nearer by about one depth step
    }
    gl.uniformMatrix4fv(u('uModel'), false, m4.box(0.05, 0.25, 0, 0.7, 0.5, 1));
    gl.uniform3f(u('uColor'), 0.97, 0.95, 0.9);             // a white price sticker
    gl.drawArrays(gl.TRIANGLES, 6, 6);
    gl.disable(gl.POLYGON_OFFSET_FILL);
  });
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>