Three.js renderOrder for Transparent Overlapping Objects
In WebGL and Three.js, rendering overlapping transparent objects
often leads to visual artifacts, such as background objects incorrectly
clipping or disappearing behind foreground ones. This article explains
the functional significance of the renderOrder property,
detailing how it overrides default WebGL depth-sorting behavior to
ensure transparent 3D models render in the correct visual sequence.
The Challenge of Transparency in Three.js
To understand why renderOrder is necessary, you must
first understand how WebGL handles rendering. For opaque objects,
Three.js uses the GPU’s depth buffer (Z-buffer) to determine which
pixels are in front. If a pixel is behind another already-rendered
pixel, the GPU discards it.
However, transparent objects require alpha blending, meaning the colors of background and foreground objects must be mixed together. To blend colors correctly, transparent objects must be drawn from back to front (the Painter’s Algorithm).
Three.js attempts to handle this automatically by sorting transparent objects based on their center point’s distance to the camera. While this works for simple scenes, it fails under several common conditions: * Intersecting or nested geometry: When one transparent object is inside another. * Large or elongated objects: Where the center point of an object does not accurately represent which parts are closer to the camera. * Complex overlapping structures: Where the relative depth order changes depending on the camera angle.
When the automatic sorting fails, Three.js renders the foreground object first. Because it writes to the depth buffer, any transparent object behind it is discarded, resulting in ugly visual glitches where background objects disappear.
What is the renderOrder Property?
The renderOrder property is a user-defined integer
available on all Three.js objects inheriting from Object3D
(such as Mesh, Group, and
Sprite). By default, all objects have a
renderOrder of 0.
object.renderOrder = 0; // DefaultWhen rendering a scene, Three.js groups and sorts objects based on
the following hierarchy: 1. Opaque vs. Transparent:
Opaque objects are always rendered before transparent objects. 2.
renderOrder: Objects are sorted ascendingly by their
renderOrder value. 3. Z-depth: Within the
same renderOrder group, transparent objects are sorted by
their distance from the camera (back-to-front).
Functional Significance of renderOrder
By manually defining the renderOrder, you take absolute
control over the rendering sequence, bypassing the limitations of
distance-based sorting.
1. Guaranteeing Correct Layering
By assigning a lower renderOrder to background elements
and a higher renderOrder to foreground elements, you
guarantee that the background is drawn first.
// Background transparent glass wall
glassWall.renderOrder = 1;
// Foreground transparent holographic UI
holoUI.renderOrder = 2;Even if the camera moves closer to the glass wall than the UI, the glass wall will always render first, ensuring the UI blends seamlessly on top of it without disappearing.
2. Solving Complex Geometry Clipping
In complex models, like a transparent outer shell surrounding an
inner transparent engine, automatic sorting will cause the inner engine
to clip out as the camera rotates. Setting the inner geometry to
renderOrder = 1 and the outer shell to
renderOrder = 2 permanently resolves the clipping,
regardless of camera rotation.
3. Creating Visual Effects
renderOrder can be used to force specific visual styles.
For example, you can force a transparent X-ray overlay to render on top
of everything else in the scene, regardless of its physical depth, by
assigning it a very high renderOrder (e.g.,
999).
Best Practices When Using renderOrder
To get the best results when using renderOrder for
transparent objects, keep these practices in mind:
- Use with depthWrite = false: For transparent
materials, setting
material.depthWrite = falseprevents the object from writing to the depth buffer. This allows objects rendered afterward (with a higherrenderOrder) to blend correctly instead of being blocked by invisible pixels. - Keep Opaque Objects Default: Do not change the
renderOrderof opaque objects unless absolutely necessary. Opaque objects should render first so WebGL can optimize performance via depth-testing. - Be Mindful of Dynamic Scenes: If transparent
objects constantly swap their physical depth relationship (e.g., two
objects orbiting each other), static
renderOrdervalues will cause visual errors when their positions invert. In such cases, you must dynamically update theirrenderOrdervalues via code based on their positions relative to the camera.