Ever clicked a 3D object in Three.js and the raycaster hits somewhere completely wrong?
You're not alone. The fix is just a few lines of code — but the why is what confuses most people.
Here’s the simple explanation.
The raycaster doesn't use pixels
It works in the camera’s coordinate system using Normalized Device Coordinates (NDC) — values from -1 to +1.
- X:
-1= left,+1= right - Y:
+1= top,-1= bottom
So instead of pixels, it uses a normalized coordinate system based on the screen (you can think of it like percentages).
The conversion (this is the important part)
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
What’s happening here:
-
event.clientX / width→ gives a value from0 → 1across the screen - We remap that to
-1 → +1 - Same for Y, but flipped because browser coordinates start at the top
In other words, you're converting from pixel space → camera space.
A quick visual
(-1, +1) (+1, +1)
┌─────────────┐
│ │
│ (0,0) │
│ │
└─────────────┘
(-1, -1) (+1, -1)
Why this exists
This system makes everything consistent:
- Works on any screen size or resolution
- Doesn’t depend on pixel values
- Lets
setFromCamera()generate a correct 3D ray every time
Minimal click example
window.addEventListener('click', (event) => {
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
console.log("Clicked on:", intersects[0].object.name);
}
});
That’s it. No complex math, no magic — just a coordinate conversion.
If this cleared things up for you as much as it did for me.
Top comments (0)