Skip to main content

Hybrid Multi-Frustum Logarithmic Depth Buffer

Cesium’s rendering engine is built for high-precision rendering. When rendering planetary scale even at just meter resolution, stock game engines experience precision problems, including z-fighting and jitter. Z-fighting is when two triangles are close together and cover the same pixel but there is not enough precision in the depth buffer to determine which triangle is closer to the viewer. This results in the pixel flickering between the two triangles as the view changes. Z-fighting occurs because depth buffer precision is proportional to the inverse depth, which means there is more precision closer to the near plane and less precision as triangles move farther away from it.

Here is a view that shows z-fighting on terrain where the viewer is about 8,000 meters above the WGS84 ellipsoid.

Two possible solutions to z-fighting are to use a multi-frustum or a logarithmic depth buffer. With the 1.45 release, Cesium replaced multi-frustum rendering with a hybrid multi-frustum / logarithmic depth buffer approach.

The new approach is a rare win-win; it results in

  • better performance because there are fewer draw calls, and
  • better visual quality since the near plane can be moved closer generally without impacting performance.

Multi-frustum rendering

Cesium’s original multi-frustum implementation used three frustums with near and far values ranging from 1 to 1,000 meters, 1,000 to 1,000,000, and 1,000,000 to 1,000,000,000. These frustums were rendered in back-to-front order, and the depth buffer was cleared between each frustum.

At the start of rendering, draw commands were placed in one or more frusta based on the command’s bounding volume. Any command whose bounding volume intersected a frustum boundary was inserted to both frustums, causing a draw call to be issued multiple times. To optimize, tight near and far planes were computed from the closest and farthest bounding volumes from the viewer to minimize the number of frusta. Our SIGGRAPH course talk, Using Multiple Frustums for Massive Worlds, covers multi-frustum rendering in more detail.

Here is a view with only terrain near Mount Everest.

View with only terrain

Here’s the same view with the terrain colored by which frustum contains it. The red terrain is in the first frustum, the green terrain is in the second frustum, and the yellow terrain is in both the first and second frustums. The third frustum is not visible here.

View with only terrain colored by frustum

Here’s the same terrain scene with the multi-frustum shown. The first frustum near the viewer is too small to see at this scale.

View of the frustums

In this scene, there are 137 draw calls. There are 28 calls in the first frustum, 102 calls in the second frustum, and 7 calls in the third frustum. The number of duplicate draw calls is 26: 12 are in the first and second frustum, and 7 are in all three frustums.

Logarithmic depth buffer

Another solution to z-fighting is to use a logarithmic depth buffer. Outputting a logarithmic value to the depth buffer better distributes the values. Cesium patches the vertex and fragment shaders to output a logarithmic depth.

For more background, see Brano Kemen’s articles: Maximizing Depth Buffer Range and PrecisionLogarithmic Depth Buffer, and Logarithmic Depth Buffer Optimizations and Fixes.

Using a logarithmic depth buffer for the above scene with a near plane of 0.1 and far plane of 1.0e8, there is only one frustum and a total of 111 draw calls issued. Not only are there fewer draw calls, but the near plane can now be closer to the viewer by default, allowing close-up views. This was possible with a multi-frustum but could cause more than three frustums and impact performance. Because there is only one frustum, using a logarithmic depth buffer also reduces CPU overhead for putting the draw commands into buckets for drawing in each frustum. There are fewer clears and full screen passes, like OIT, inverted classification, and render target copies, that happen per-frustum. It also removes rendering artifacts seen at frustum boundaries, which helps when developing new features such as post-processing and lines on terrain.

Near plane at 1.0 meter distance

A close up of a tractor wheel where the near plane is 1 meter away from the viewer.

Near plane at 0.1 meter distance

A close up of a tractor wheel where the near plane is 0.1 meters away from the viewer.

Roughly, for views with just terrain, the number of duplicate draw calls removed is between 10 and 30. For views with terrain and a 3D Tiles BIM model, 20–40 duplicate draw calls are often removed.

Triangles that cover a large part of the screen need to write the fragment depth in the fragment shader since the linear interpolation of logarithmic values from the vertex shader is incorrect. A typical optimization for logarithmic depth is to only change the vertex shader and only modify the fragment shader for triangles close to the viewer. This is an optimization since writing the fragment depth in the fragment shader disables the early depth test optimization in GPUs. This isn’t possible in Cesium because there can be large triangles that cover a large part of the screen that aren’t close to the viewer, for example, a polygon that covers an entire continent. Therefore, browsers must support the EXT_frag_depth WebGL extension for writing fragment depth in the fragment shader to take advantage of this feature. Otherwise, Cesium falls back to using a multi-frustum.

While most views in Cesium only require a single frustum that uses logarithmic depth, there are cases where a single frustum can fail. An extreme example is when viewing very large triangles at very far distances.

Here is a view of a red plane behind a blue plane; they are the same size and 300 meters apart. The viewer is about 64 million meters away from the planes.

For the above view, it is expected that geometry still visible at that distance will have triangles far apart from one another, for example, planets. Any geometry that has triangles closer together wouldn’t be visible, for example, satellites. To fix this problem, Cesium uses a hybrid technique where each frustum in the multi-frustum uses a logarithmic depth buffer. Lowering the value for Scene.logarithmicDepthFarToNearRatio to increase the number of frustums will remove the z-fighting.