Convert 2D NDC to 3D Coordinates in Three.js

Converting 2D normalized device coordinates (NDC) back into a 3D space is a crucial technique in 3D web development, commonly used for mouse interaction, object picking, and positioning elements in a 3D scene based on screen inputs. This article provides a direct, step-by-step guide on how to use the .unproject() method in Three.js to accurately translate 2D screen-space coordinates back into 3D world-space coordinates.

Understanding Normalized Device Coordinates (NDC)

In Three.js, Normalized Device Coordinates range from -1 to 1 on both the X and Y axes. * For the X-axis, -1 represents the far-left edge of the screen, and 1 represents the far-right edge. * For the Y-axis, -1 represents the bottom edge, and 1 represents the top edge.

Step 1: Convert Screen Coordinates (Pixels) to NDC

Before using the unproject method, you must map your screen pixel coordinates (like mouse event coordinates) to the -1 to 1 range.

// Assuming 'event' is a mouse or touch event
const ndcX = (event.clientX / window.innerWidth) * 2 - 1;
const ndcY = -(event.clientY / window.innerHeight) * 2 + 1;

Step 2: Create a 3D Vector

Create a THREE.Vector3 using your normalized X and Y coordinates. The Z component of this vector represents depth within the camera’s view frustum, ranging from 0 (the near clipping plane) to 1 (the far clipping plane). Typically, a value of 0.5 is used to project a point midway.

import * as THREE from 'three';

const targetDepth = 0.5; // Depth value between 0 (near plane) and 1 (far plane)
const ndcVector = new THREE.Vector3(ndcX, ndcY, targetDepth);

Step 3: Use the Unproject Method

Pass your active camera as an argument to the .unproject() method of your Vector3 instance. This projects the vector from 2D camera space back into 3D world space.

// 'camera' is your Three.js PerspectiveCamera or OrthographicCamera
ndcVector.unproject(camera);

// ndcVector now contains the 3D world coordinates
console.log(`World Coordinates: X: ${ndcVector.x}, Y: ${ndcVector.y}, Z: ${ndcVector.z}`);

Practical Example: Finding a Point on a 3D Plane

If you need to find where a 2D click intersects a specific 3D plane (like the ground plane at Y = 0), you can use the unprojected vector to define a raycast direction from the camera.

// 1. Get NDC coordinates
const mouse = new THREE.Vector2(ndcX, ndcY);

// 2. Initialize a Raycaster
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);

// 3. Define a plane (e.g., flat ground plane at y = 0)
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);

// 4. Calculate intersection point
const targetIntersection = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, targetIntersection);

// targetIntersection now holds the precise 3D coordinate on the plane
console.log(targetIntersection);