Three.js Frustum Camera Visibility Guide

In web-based 3D graphics, rendering performance depends heavily on only drawing what is visible to the user. This article explains the function of the Frustum class in Three.js, detailing how it represents the camera’s viewing volume and how it is used to determine whether 3D objects are visible on screen. You will learn about automatic frustum culling and how to manually perform visibility checks using the Three.js API.

What is a Frustum?

In 3D computer graphics, a frustum (specifically a viewing frustum) is the region of space in the modeled world that may appear on the screen. It is shaped like a pyramid with its top cut off, representing the field of view of a camera.

The frustum is defined by six flat planes: * Near plane: The closest boundary to the camera. * Far plane: The furthest boundary from the camera. * Left, Right, Top, and Bottom planes: The lateral boundaries of the camera’s field of view.

Any 3D object located entirely outside of these six planes is invisible to the camera and does not need to be rendered.

Automatic Frustum Culling in Three.js

By default, Three.js handles visibility optimization automatically through a process called frustum culling.

Every standard 3D object inheriting from THREE.Object3D has a property called frustumCulled, which is set to true by default. Before rendering a frame, the Three.js WebGLRenderer automatically checks each object’s bounding sphere or bounding box against the camera’s frustum. If the object lies completely outside the frustum, Three.js skips rendering it, saving valuable GPU resources.

The Role of the THREE.Frustum Class

The THREE.Frustum class allows developers to manually recreate, update, and query this viewing volume. This is highly useful for CPU-side optimization, such as disabling heavy calculations, pausing animations, or managing game logic for entities that are currently off-screen.

To use the Frustum class, you must first instantiate it and then update its planes to match the camera’s current position and projection.

Updating the Frustum

Because the camera can move, rotate, or change its field of view, the frustum must be updated dynamically. This is done by extracting the frustum planes from the camera’s projection matrix:

const frustum = new THREE.Frustum();
const cameraViewProjectionMatrix = new THREE.Matrix4();

// Force update the camera matrices to ensure accuracy
camera.updateMatrixWorld();
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);

// Update the frustum with the camera's projection matrix
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);

Checking for Object Visibility

Once the frustum is updated, you can use several built-in methods to test the visibility of points, spheres, boxes, or entire objects:

Example: Manual Visibility Check

Here is how you can check if a mesh is visible to the camera during the animation loop:

function animate() {
    requestAnimationFrame(animate);

    // Update the frustum helper matrix
    camera.updateMatrixWorld();
    cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
    frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);

    // Check if a specific mesh is visible
    if (frustum.intersectsObject(myMesh)) {
        // Code to execute when the object is visible (e.g., play animation)
        myMesh.material.color.setHex(0x00ff00); 
    } else {
        // Code to execute when the object is hidden (e.g., pause updates)
        myMesh.material.color.setHex(0xff0000);
    }

    renderer.render(scene, camera);
}

By leveraging the THREE.Frustum class, developers can write highly performant applications by ensuring that both rendering pipelines and CPU-bound logical updates are only executed for objects within the user’s active field of view.