# WebGPU: default depth setup z-fights noticeably more than WebGL2; reversed Z fixes it. Worth making it the default?

**URL:** <https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351>\
**Category:** Bugs\
**Created:** [October 19, 2026, 1:29pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351 "2026-10-19T13:29:49Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![ctabone](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/ctabone/32/52182_2.png) [@ctabone](https://forum.babylonjs.com/u/ctabone)\
**Post date:** [October 19, 2026, 1:29pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/1 "2026-10-19T13:29:49Z")

</div>

On WebGPU, with Babylon’s default depth setup, nearly coplanar surfaces z-fight noticeably more than the same scene on WebGL2. Setting `engine.useReverseDepthBuffer = true` makes it go away completely. We hit this in a game where tree canopies are thousands of overlapping, camera-facing leaf cards: on WebGPU the cards visibly traded places frame to frame as the camera moved.

**Repro** (Playground, WebGPU engine). A grid of green cards, each with a slightly smaller red card a tiny distance behind it. The red card is drawn second, so it wins every depth tie, and red should never show. Rows are distances from the camera (25, 100, 400, 1600). Columns are the gap as a fraction of that distance (1e-3 down to 1e-5).

```js
const createScene = () => {
  // engine.useReverseDepthBuffer = true; // <- uncomment: all the red disappears on WebGPU
  const scene = new BABYLON.Scene(engine);
  scene.clearColor = new BABYLON.Color4(0, 0, 0, 1);
  const camera = new BABYLON.FreeCamera("cam", BABYLON.Vector3.Zero(), scene);
  camera.minZ = 0.1; camera.maxZ = 10000; // a typical open-world range
  camera.setTarget(new BABYLON.Vector3(0, 0, 1));
  const flat = (name, color) => {
    const m = new BABYLON.StandardMaterial(name, scene);
    m.disableLighting = true; m.emissiveColor = color; return m;
  };
  const green = flat("green", new BABYLON.Color3(0, 1, 0));
  const red = flat("red", new BABYLON.Color3(1, 0, 0));
  const distances = [25, 100, 400, 1600];
  const gaps = [1e-3, 3e-4, 1e-4, 3e-5, 1e-5];
  distances.forEach((d, row) => gaps.forEach((g, col) => {
    const x = (col - 2) * 0.16 * d, y = (1.5 - row) * 0.16 * d;
    const card = (mat, z, s) => {
      const p = BABYLON.MeshBuilder.CreatePlane(mat.name, { size: 0.12 * d * s }, scene);
      p.position.set(x, y, z); p.rotation.y = 0.5; p.material = mat; // tilted, so depth varies across it
    };
    card(green, d, 1);
    card(red, d * (1 + g), 0.95); // drawn second: it wins every depth tie
  }));
  return scene;
};

```

**Measured:** the share of card pixels where the red card wrongly shows, averaged over the 20 cells. Babylon **9.31.0** and 9.12.0 gave identical numbers. Chrome, Intel Iris Xe (Dawn), 1200×700, no MSAA.

| Setup | Red showing (mean of 20 cells) |
| --- | --- |
| WebGL2 | 25.9 % |
| WebGPU, default (`depth24plus-stencil8`, standard Z) | **39.1 %** |
| WebGPU, `stencil: false` (`depth32float`, standard Z) | 39.1 % (no change) |
| WebGPU, `useReverseDepthBuffer = true` | **0.0 %** |
| WebGPU, reversed Z + `depth32float` | 0.0 % |

(Screenshot attached: WebGL2, WebGPU default, and WebGPU reversed, left to right.)

On the real scene, a slow camera orbit around a leafy tree, counting pixels that flip between consecutive frames, the result is the same: WebGL2 0.075‰, WebGPU default 0.157‰, WebGPU reversed Z 0.029‰.

A float depth buffer alone changes nothing; reversed Z is what fixes it. Our reading is that with WebGPU’s [0, 1] clip-space depth, the float32 depth the vertex stage writes near 1.0 has about one bit less to work with than WebGL’s [-1, 1] range, which the viewport transform then halves. Reversed Z puts the float’s dense range at the far plane instead. I know it’s been said that WebGPU’s depth precision should be no different from WebGL’s ( [What are the limits of WebGPU?](https://forum.babylonjs.com/t/29261) ), so I wanted to share numbers showing a difference in practice.

**Suggestion:** since reversed Z already works fully on WebGPU (as @Evgeni_Popov noted in [Reverse Depth Buffer question](https://forum.babylonjs.com/t/45318) ), could WebGPU default to `useReverseDepthBuffer = true`, ideally with a float depth buffer? Failing that, a note on the WebGPU breaking-changes page would help: “if you see more z-fighting than on WebGL, set `engine.useReverseDepthBuffer = true`”. It took us a while to pin down.

Thanks for the great engine!

 ![zfight-webgl2-webgpu-reversed](https://us1.discourse-cdn.com/flex024/uploads/babylonjs/original/3X/f/4/f44e309037f37538533cc3bb2d2a8bb5796be47f.png)

---

<div class="post-metadata">

**Author:** ![Pryme8](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/pryme8/32/7212_2.png) [@Pryme8](https://forum.babylonjs.com/u/Pryme8)\
**Post date:** [October 19, 2026, 2:21pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/2 "2026-10-19T14:21:45Z")

</div>

I would assume switching it on by default would break old shaders also I dont think webGL really has it working fully with firefox I think still not really supporting it so a reversed-Z default on the WebGL path is still not really there.

Defaulting it on WebGPU alone would make the same scene act differently, shadow, and reconstruct depth differently depending on which engine came up.

I just asked Grok and this is what it said… kinda on the same page what I was thinking.

“”"  
What the flag actually changes

`useReverseDepthBuffer` is a convention switch, not an extra pass. In Babylon’s engine it flips the depth test from `LEQUAL` to `GEQUAL`, clears depth to `0` instead of `1`, negates polygon offset, and defines `USE_REVERSE_DEPTHBUFFER` so shadows and the depth renderer can match. The depth texture those systems hand you is remapped back to the usual `0..1` range. Anything that reads the real buffer, or rebuilds a world position from NDC, has to treat the near plane as `z = 1`.

That is enough to break custom materials that set their own depth function or write fragment depth, logarithmic depth (the two techniques disagree about which end of the buffer is dense), and a very small `camera.minZ`. When the feature was added, Babylon’s own snapshots diverged until `minZ` was raised from `0.01` to `0.1` ([#10500](https://github.com/BabylonJS/Babylon.js/pull/10500)).  
“”"

---

<div class="post-metadata">

**Author:** ![ctabone](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/ctabone/32/52182_2.png) [@ctabone](https://forum.babylonjs.com/u/ctabone)\
**Post date:** [October 19, 2026, 2:55pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/3 "2026-10-19T14:55:35Z")

</div>

Thanks, fair points about existing custom shaders. Anything that writes fragment depth, uses logarithmic depth, or reconstructs position from the raw buffer would need care, so I understand why flipping the convention for everyone is a big step.

I’d push back gently on the parity point, though, because it cuts the other way. The engines already behave differently today: the same scene, with no custom shaders at all, z-fights about 1.5× more on WebGPU than on WebGL2 (the table above). The WebGPU docs call backward compatibility with the WebGL implementation “one of our pillars”, and this is a case where a plain WebGL scene doesn’t come across the same. To be clear, I only suggested this for WebGPU: on WebGL, reversed Z wouldn’t help anyway because of the [-1, 1] range.

Smaller steps that would close the gap without surprising anyone:

1. A note on the WebGPU breaking-changes page: “If you see more z-fighting on WebGPU than on WebGL, set `engine.useReverseDepthBuffer = true` (keep `camera.minZ` reasonable, and check custom shaders that read or write depth).” This alone would have saved us a lot of time.
2. An opt-in `WebGPUEngine` option, for example `reverseDepth: true`, so it’s set at creation time.
3. Maybe considering it as the WebGPU default in the next major version, where a convention change is expected and can go in the migration notes?

Thanks for the input!

---

<div class="post-metadata">

**Author:** ![Pryme8](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/pryme8/32/7212_2.png) [@Pryme8](https://forum.babylonjs.com/u/Pryme8)\
**Post date:** [October 19, 2026, 3:08pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/4 "2026-10-19T15:08:45Z")

</div>

I don’t think anyone would have objections to an opt in at engine creation!

Bet if you make a PR for that it would get accepted.

---

<div class="post-metadata">

**Author:** ![Evgeni\_Popov](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/evgeni_popov/32/4432_2.png) [@Evgeni\_Popov](https://forum.babylonjs.com/u/Evgeni_Popov)\
**Post date:** [October 19, 2026, 4:30pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/5 "2026-10-19T16:30:09Z")

</div>

Thanks for the detailed repro and measurements!

I tried your example with Babylon.js 9.31.0, at 1200×700 without MSAA, on an RTX 5070 Ti. Interestingly, I get the opposite ranking between WebGL2 and WebGPU:

| Configuration | Mean red pixels across the 20 cells |
| --- | --- |
| WebGL2, default depth buffer | 34.0% |
| WebGL2, explicit 32-bit float depth | 34.0% |
| WebGPU, default depth buffer | 21.7% |
| WebGPU, explicit `depth32float` | 21.7% |
| WebGPU, reversed Z, either format | 0.0% |

This doesn’t invalidate your Intel results, but it suggests that WebGPU having worse precision than WebGL2 is not a general rule.

The important distinction is between **depth-buffer precision and the precision of the calculations producing the depth values**. Even with identical buffer formats, differences in projection calculations, shader compilation, and rasterization can produce different rounding and therefore different z-fighting. I’d be cautious about attributing the difference to a guaranteed one-bit loss from WebGPU’s clip-space convention.

Also, a 32-bit floating-point depth buffer does not provide 32 uniformly distributed precision bits: it has 24 significant binary digits. With standard Z, distant geometry maps close to 1, where float32 spacing is almost identical to 24-bit normalized-integer spacing. That explains why switching to `depth32float` alone may not help.

Reversed Z takes advantage of the much finer floating-point spacing near zero for distant geometry, which is why it can improve things dramatically. NVIDIA’s [Depth Precision Visualized](https://developer.nvidia.com/blog/visualizing-depth-precision/) explains this well. For a large depth range like yours, enabling it is a good recommendation:

```auto
engine.useReverseDepthBuffer = true;

```

Changing the default would nevertheless be a compatibility change for existing applications, particularly custom shaders that read or write depth. Documenting this recommendation and its caveats seems like a sensible first step, without implying that every WebGPU implementation necessarily has worse standard-Z precision than WebGL2.

---

<div class="post-metadata">

**Author:** ![ctabone](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/ctabone/32/52182_2.png) [@ctabone](https://forum.babylonjs.com/u/ctabone)\
**Post date:** [October 19, 2026, 4:46pm UTC](https://forum.babylonjs.com/t/webgpu-default-depth-setup-z-fights-noticeably-more-than-webgl2-reversed-z-fixes-it-worth-making-it-the-default/64351/6 "2026-10-19T16:46:20Z")

</div>

Thanks a lot for running it on your side, @Evgeni_Popov, that’s really useful!

Fair correction: with your RTX 5070 Ti numbers it’s clearly not a general WebGPU-vs-WebGL rule, and I’ll drop the “one bit lost to the clip range” theory. Your explanation of float spacing near 1 vs near 0 matches what we saw (depth32float alone changing nothing, reversed Z fixing it). For what it’s worth, on our Intel machine reversed Z on WebGL2 made things slightly worse (30.5 % vs 25.0 %), which fits with it only paying off where the depth range allows it.

Following Pryme8’s suggestion, I opened two small PRs:

- [Add useReverseDepthBuffer engine creation option by christabone · Pull Request #19010 · BabylonJS/Babylon.js · GitHub](https://github.com/BabylonJS/Babylon.js/pull/19010) adds an opt-in `useReverseDepthBuffer` engine creation option (default unchanged).
- [WebGPU breaking changes: add a note on depth precision and z-fighting by christabone · Pull Request #1648 · BabylonJS/Documentation · GitHub](https://github.com/BabylonJS/Documentation/pull/1648) adds a “Depth precision and z-fighting” note to the WebGPU page.

I’ve reworded both after your reply. They no longer say WebGPU z-fights more than WebGL; they just recommend reversed depth (ideally with a float depth buffer) for large depth ranges, with the custom-shader caveats, and link the NVIDIA article. Happy to adjust anything. Thanks again for taking the time to test and reply.
