Target Ray Space vs Grip Space in Three.js WebXR

When developing virtual reality (VR) and augmented reality (AR) applications with Three.js and WebXR, tracking user input accurately is crucial. WebXR provides two distinct spatial representations for controllers: the target ray space and the grip space. This article explains the fundamental differences between these two spaces, their specific use cases, and how to implement them in your Three.js projects to ensure intuitive user interactions.

The Target Ray Space

The target ray space represents a pointer or a “laser pointer” emitting from the controller. It is used primarily for selection, targeting, and UI navigation. The origin of this space is the point from which the user is expected to point, and the orientation points directly along the pointing direction (usually forward, along the negative Z-axis).

In Three.js, you access the target ray space using:

const controller = renderer.xr.getController(index);
scene.add(controller);

Common Use Cases: * Pointing at and selecting 3D UI elements or menus. * Teleportation mechanics (casting a ray to the ground). * Shooting mechanics in games where a laser-like precision is required.

The Grip Space

The grip space represents the physical position and orientation of the user’s hand holding the controller. Unlike the target ray, which is optimized for pointing, the grip space is anchored to the user’s palm. This space is essential when you need to render a 3D model of the controller, virtual hands, or any object the user is physically holding (like a sword, flashlight, or tool).

In Three.js, you access the grip space using:

const controllerGrip = renderer.xr.getControllerGrip(index);
scene.add(controllerGrip);

Common Use Cases: * Rendering realistic 3D virtual hands or controller models. * Grabbing and holding virtual objects naturally. * Using hand-held tools where the rotation matches the wrist movement rather than a pointer.

Key Differences Summary

Feature Target Ray Space (getController) Grip Space (getControllerGrip)
Primary Purpose Pointing, selecting, and UI interaction. Rendering hand models and held objects.
Origin Point The “pointer” output on the controller. The center of the user’s palm/grip.
Default Direction Points forward (along the negative Z-axis). Aligns with the physical grip of the hand.
Visual Element Best paired with a line or laser pointer mesh. Best paired with controller or hand glTF models.

Implementing Both in Three.js

To create a complete XR experience, developers often use both spaces simultaneously. You can use the grip space to render the physical controller model and the target ray space to cast a ray for UI interaction.

// 1. Set up the Target Ray (for pointing)
const controller1 = renderer.xr.getController(0);
scene.add(controller1);

// Add a visible laser line to the target ray
const geometry = new THREE.BufferGeometry().setFromPoints([
    new THREE.Vector3(0, 0, 0), 
    new THREE.Vector3(0, 0, -1)
]);
const line = new THREE.Line(geometry);
line.name = 'line';
line.scale.z = 5; // length of the ray
controller1.add(line);

// 2. Set up the Grip Space (for the hand model)
const controllerGrip1 = renderer.xr.getControllerGrip(0);
scene.add(controllerGrip1);

// Add a controller model to the grip space
const controllerModelFactory = new XRControllerModelFactory();
const model = controllerModelFactory.createControllerModel(controllerGrip1);
controllerGrip1.add(model);