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:
- 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.
- Selection Outlines: Creating a highlight or wireframe around an object that remains visible even when the object goes behind a wall.
- 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.