Convert 3D Coordinates to 2D Screen Space in Three.js

Converting a 3D object’s position into 2D screen-space coordinates is essential for positioning HTML overlays, UI tooltips, or labels over WebGL elements. This guide explains how to use the Three.js Vector3.project() method to project 3D coordinates into Normalized Device Coordinates (NDC) and map them directly to 2D pixel coordinates on your canvas.

Step 1: Get the 3D World Position

Before projecting, you must retrieve the absolute world position of your 3D object. While object.position gives local coordinates relative to its parent, object.getWorldPosition() ensures you obtain the correct global coordinates in the 3D scene.

import * as THREE from 'three';

const tempV = new THREE.Vector3();
mesh.getWorldPosition(tempV);

Step 2: Project the Vector Using the Camera

Pass your camera to the vector’s project method. This projects the 3D world coordinates onto a 2D plane using the camera’s projection matrix.

tempV.project(camera);

After calling .project(camera), the vector’s x and y values are converted to Normalized Device Coordinates (NDC). In this coordinate space: * X ranges from -1 (left edge of the screen) to +1 (right edge of the screen). * Y ranges from -1 (bottom edge of the screen) to +1 (top edge of the screen). * Z represents the depth value (useful for checking if the object is behind the camera).

Step 3: Convert NDC to Screen Pixels

To position an HTML element, you must map the -1 to 1 NDC range to the width and height of your rendering canvas (in pixels).

Additionally, you must invert the Y-axis. In WebGL, the Y-axis points upwards, whereas in standard HTML/CSS screen coordinates, the Y-axis points downwards starting from the top-left corner (0,0).

const canvas = renderer.domElement;

// Convert NDC (-1 to +1) to pixel coordinates (0 to width/height)
const x = (tempV.x *  .5 + .5) * canvas.clientWidth;
const y = (tempV.y * -.5 + .5) * canvas.clientHeight;

Step 4: Apply to HTML Elements

You can now use these calculated x and y coordinates to update the CSS style of an absolutely positioned HTML element.

const tooltip = document.getElementById('tooltip');
tooltip.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px)`;

Handling Off-Screen Objects

If the object moves behind the camera, the projection calculations can still yield coordinates that appear on screen. To prevent tooltips from rendering when the object is behind the camera, check the projected vector’s z depth value:

const isBehindCamera = tempV.z > 1;

if (isBehindCamera) {
    tooltip.style.display = 'none';
} else {
    tooltip.style.display = 'block';
}