Reverse Depth Buffer (z-buffer)

For distant objects with standard depth where meshes are close together or intersect z fighting will occur as the error rate increases with distance:

One way to deal with distant objects which Babylon offers is to use a logarithmic depth buffer; however that has some drawbacks.

  1. It doesn’t work if the distant objects are very close together, you still get z-fighting
  2. Calcuating the z value is done in the fragment shader which means the gpu can’t do early rejection after the vertex shader and needs to always run the fragment shader (this then violates rule 2. (slowing down the rendering process)

An alternative way of dealing with this is reversing the z-buffer so far objects are at 0 and near objects are at 1. This deals with the z fighting quite nicely as it gives more precision to far objects in exchange for less for near objects which isn’t required unless you are dealing with tiny, tiny objects and a massive macro zoom.

(obligatory Nvidia article Depth Precision Visualized | NVIDIA Developer)

Currently you can get most of the way there in Babylon by inverting the depth function:

engine.setDepthFunctionToGreaterOrEqual();

Turning off auto clear (as it clears depth it to 1.0 and we now want to clear to 0.0)

scene.autoClear = false;
scene.autoClearDepthAndStencil = false;
scene.setRenderingAutoClearDepthStencil(0, false, false, false);
scene.setRenderingAutoClearDepthStencil(1, false, false, false);

and intercepting the before draw to do a clear of the depth to 0.0.

scene.onBeforeDrawPhaseObservable.add((scene, state) => {
    const gl = scene.getEngine()._gl;
    gl.clearDepth(0.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
});

You also need to change the camera’s projection matrix to output reversed z values (flipping near and far), with an infinite far distance

Something like:

let t = 1.0 / (Math.tan(this.fov * 0.5));
let a = isVerticalFovFixed ? (t / aspect) : t;
let b = isVerticalFovFixed ? t : (t * aspect);

Matrix.FromValuesToRef(
    a, 0.0, 0.0, 0.0,
    0.0, b, 0.0, 0.0,
    0.0, 0.0, -near, 1.0,
    0.0, 0.0, 1.0, 0.0,
    matrix
);

All well and good so far; however the issue comes when using other components of Babylon that don’t expect this; for example the GeometryBufferRenderer which clears its own z-buffer to 1.0.

// set default depth value to 1.0 (far away)
this._multiRenderTarget.onClearObservable.add((engine) => {
    engine.clear(new Color4(0.0, 0.0, 0.0, 1.0), true, true, true);
});

And also that its operating outside of Babylon using gl calls e.g. gl.clearDepth(0.0);

So it would be nice if this could be a regular option.

Pictures from (with further commentary) Depth Precision | The Devil In The Details

1 Like

According to your links, reverse-Z seems nice indeed!

However, for openGL, I understood that you need glClipControl which is not available in GL ES…

Somewhere else ((Tutorial) Reversed-Z in OpenGL : opengl), they say that we can use glDepthRange to set a [-1, +1] range for the zbuffer, so maybe it’s still possible in GL ES (as glDepthRange does exist)…

The problem of this change is that a lot of parts of the engine will be impacted, I think, as now the z values are not from [0,1] anymore but between -1 and +1…

Hey @sebavan, what’s your take on this?

That’s not how you’d implement it in webgl though; you reverse the z values; so its [1,0] set the far distance to infinite (so it can’t go beyond 0 into negative as there is no greater distance) then the near distance is 1 and anything greater than that (behind the camera) is clipped normally.

And use you use the perspective matrix to reverse the z-depth rather than changing the clip with something like glDepthRange

If Z range is [1,0] instead of [0,1], you still have a lot of changes to perform because all your testings are now reversed (where you tested z1 < z2, you should now test z1 > z2).

Also, maintaining two code paths for both cases may be quite hard… Imagine all the shaders that are impacted, for eg.

I read that this new scheme is better in every way from the current one and so could be the only one to be supported. However, it’s not possible in Babylon as it must be backward compatible and not break existing code.

1 Like

I think both have their use case and it would be nice to see it in :slight_smile:

Actually AR could have a great use of it as well.

Now on the practical side of the implementation, yes this PR will be a bit fat but it might be worth it on the long run knowing we have larger and larger scenes being drawn all together.

@Deltakosh any thoughts ?

Adding @bghgary with whom we chatted about it last week for inputs.

I also agree on the range issue and impact of those code paths. Good part is it should fail pretty fast if wrong :wink:

reverse depth: it is an option you have to turn on for specific scenario so I’m not worried.

Regarding moving the depth range to [-1, 1]: I would not like doing that :slight_smile: