How Frustum Culling Works in Three.js
In Three.js, automatic frustum culling is a crucial performance optimization technique that prevents the GPU from wasting resources on objects outside the camera’s field of view. By calculating whether a 3D object’s bounding volume intersects with the camera’s viewing frustum, Three.js automatically skips the rendering process for off-screen objects. This article explains the mechanics of this process, how bounding volumes are used, and how developers can configure or troubleshoot frustum culling in their applications.
What is the Viewing Frustum?
The viewing frustum is the 3D region of space modeled by the camera that is visible on the screen. It is shaped like a truncated pyramid, bounded by six planes: near, far, top, bottom, left, and right. Anything lying entirely outside this pyramid is invisible to the user.
The Mechanism of Automatic Frustum Culling
By default, Three.js enables automatic frustum culling for all renderable objects, such as meshes and points. Before sending data to the GPU for rendering during each frame, Three.js performs a CPU-side check:
- Bounding Volume Generation: For every mesh,
Three.js computes a simplified bounding volume, typically a bounding
sphere or a bounding box (
Box3). This volume represents the outermost boundaries of the complex 3D geometry. - Frustum Intersection Test: The engine extracts the six mathematical planes defining the camera’s current viewing frustum. It then tests whether the object’s bounding volume intersects with these planes.
- Conditional Drawing: If the bounding volume is entirely outside the frustum, Three.js flags the object to be skipped during the current render pass. If any part of the bounding volume intersects or lies completely inside the frustum, the object is sent to the GPU to be drawn.
How It Improves Rendering Performance
Rendering 3D scenes can be highly resource-intensive. Frustum culling improves performance in several key areas:
- Reduced Draw Calls: The CPU avoids sending unnecessary draw commands to the GPU, minimizing CPU-to-GPU communication overhead.
- Decreased GPU Workload: The GPU does not have to process vertices, perform rasterization, or calculate pixel shaders for geometry that the camera cannot see anyway.
- Scalability: This allows developers to build large, complex virtual worlds. As long as the player only looks at a fraction of the world at any given time, the rendering engine only processes the visible portion.
Managing Frustum Culling in Your Code
While automatic frustum culling works seamlessly for most static
objects, developers occasionally need to manage it manually. This is
controlled via the frustumCulled property on
Object3D:
mesh.frustumCulled = true; // Enabled by defaultIf you set this property to false, Three.js will bypass
the intersection test and always send the object to the GPU, regardless
of camera position.
Troubleshooting Common Issues
Sometimes, an object might suddenly disappear or “pop” out of view when its center moves off-screen, even if parts of it should still be visible. This usually happens when the bounding volume does not accurately reflect the geometry’s actual shape, a common issue when using custom vertex shaders or skinned mesh animations that displace vertices dynamically.
To resolve this, you can manually recompute the bounding volumes after modifying the geometry:
mesh.geometry.computeBoundingSphere();
mesh.geometry.computeBoundingBox();Alternatively, if the geometry is highly dynamic and constantly
changes shape, disabling frustum culling
(mesh.frustumCulled = false) for that specific object
ensures it remains visible, though at a slight performance cost.