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.
- It doesn’t work if the distant objects are very close together, you still get z-fighting
- 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