How depthTest Affects Overlapping Three.js Materials

In Three.js, managing how overlapping 3D objects render on top of one another is crucial for visual accuracy. This article explains the depthTest property in Three.js materials, how it controls whether an object’s pixels are tested against the depth buffer, and the visual consequences of enabling or disabling this property when rendering overlapping 3D objects.

Understanding the Depth Buffer

To understand depthTest, you must first understand the depth buffer (or Z-buffer). The depth buffer is a 2D grid that stores the depth value (distance from the camera) of every pixel rendered on the screen.

When Three.js renders a 3D scene, it uses the depth buffer to determine which objects are in front of others. This ensures that an object positioned closer to the camera naturally blocks the view of an object positioned behind it.

What is depthTest?

The depthTest property is a boolean on Three.js materials (such as MeshBasicMaterial, MeshStandardMaterial, etc.) that dictates whether the renderer should compare a fragment’s depth against the current depth buffer before drawing it.

By default, depthTest is set to true.

When depthTest is True (Default)

When depthTest is enabled: * Before rendering a pixel of an object, the GPU compares its depth value with the depth value already stored in the depth buffer for that screen coordinate. * If the new pixel is closer to the camera than the existing pixel, the new pixel is drawn, and the depth buffer is updated. * If the new pixel is further away, it is discarded (occluded). * Result: Standard, realistic occlusion. Objects physically behind other objects are correctly hidden from view.

When depthTest is False

When depthTest is disabled: * The GPU bypasses the depth comparison check entirely. * The object is rendered regardless of whether there are other objects closer to the camera at those same screen coordinates. * Result: The object can appear to “render through” other solid objects. Whether it appears on top depends heavily on the rendering order of the scene. If it is rendered last, it will always appear on top of everything else, even if it is physically located far behind them.

Common Use Cases for Disabling depthTest

While disabling depthTest breaks realistic physics and spatial occlusion, it is highly useful for specific visual effects:

  1. Heads-Up Displays (HUDs) and UI Elements: Rendering 3D UI elements, text labels, or navigation gizmos that must always remain visible on top of the 3D scene.
  2. Selection Outlines: Creating a highlight or wireframe around an object that remains visible even when the object goes behind a wall.
  3. Halos and Glows: Adding atmospheric glow effects or indicators that need to overlay the main geometry without clipping.

Interaction with depthWrite

depthTest is often confused with depthWrite, but they perform two different functions: * depthTest: “Should I look at the depth buffer to see if I am hidden?” * depthWrite: “Should I write my own depth into the buffer so other objects can hide behind me?”

If you set depthTest to false but keep depthWrite as true, the object will ignore depth checks (drawing over closer objects) but will still write its own depth to the buffer, which can cause strange rendering artifacts for other objects drawn after it. For overlay effects, it is common to set both depthTest: false and depthWrite: false.