Depth Sorting

In which you configure your renderer to show only the surfaces nearest to the viewer.

Triangles will inevitably be projected on the image plane such that they overlap. If the object is opaque, you want the triangle nearest to the eye to be visible. One way to achieve this would to be sort the triangles based on their depth. Then you could process them from farthest to nearest, letting nearer triangles overwrite farther ones. The problem with this approach is that every time the scene changes or the viewer moves, you'll have to sort the triangles again. Sorting is too expensive to perform so frequently.

In 1974, graduate student Ed Catmull proposed a less expensive algorithm. The algorithm adds a new channel to the framebuffer for storing depth values. At the beginning of each frame, this depth buffer is cleared to the maximum depth of 1. The triangles are then drawn in any order. Each triangle is rasterized into fragments. Before a fragment is written, its depth is compared to the value currently in the depth channel. If the fragment's depth is smaller, both the color and depth are written to the framebuffer. If the fragment's depth is bigger, it is discarded. After all the triangles are drawn, only the fragments that have the least depths are present in the framebuffer. Catmull went on to found Pixar.

In WebGL these days, you turn on Catmull's depth sorting or z-buffer algorithm with this statement:


To clear the depth buffer, you add a flag to gl.clear, which you've already been using to clear the previous frame's colors:


A fragment's depth is \(z_\mathrm{norm}\), which is computed by your projection matrix and the perspective divide. WebGL will compare \(z_\mathrm{norm}\) to the current value stored in the depth buffer only if you have enabled the depth test. Whether this comparison happens before or after the fragment shader depends on your GPU and configuration.

Compare how these two intersecting triangles are drawn with and without depth sorting:

Rotate the scene with your mouse or other pointing device. Which triangle is drawn first? Can you tell which is drawn first when the depth test is enabled?

A fragment's depth is available in the fragment shader as the z-component of the builtin variable gl_FragCoord. It will be 0 at the near face of the viewing volume and 1 at the far face. Consider this renderer that colors each fragment of a box according to its depth:

Rotate the box to see how depth changes across the surface. The color is assigned with this statement:

fragmentColor = vec4(vec3(gl_FragCoord.z), 1.0);

This depth is considered to be in pixel space. If you need the depth in normalized space, eye space, or others, you'll need to undo the transformations that were performed to arrive in pixel space.