Navigate
Search topics across all sections
Raycasting
Pointer events feel like magic -- you click a 3D cube and it responds. But how does the computer know which object you clicked? It cannot just check pixel colors like a 2D app. It needs to fire an invisible ray from your mouse into the 3D world and see what it hits. That process is called raycasting, and understanding it unlocks everything from click detection to shooting mechanics to AI line-of-sight.
You attach onClick to a detailed 3D character model with 100,000 triangles. Every time the user clicks anywhere on the canvas, R3F raycasts against all 100,000 triangles to check if the click hit the model. Your click response takes 50ms and the whole scene stutters.
Performance: intersectObject() took 47ms for mesh "Character" - Geometry faces: 100,000 - Triangle intersection tests: 100,000 - Consider using a simplified collider or meshBounds.
Real-world
Imagine you are in a dark room full of people, and you have a laser pointer. You aim it straight ahead and the beam travels in a straight line until it hits the first person. You now know exactly who is standing in that direction, how far away they are, and which part of them the beam touched.
That is raycasting. An invisible ray shoots from your camera through the mouse position into the 3D world. It tests every object along its path and reports what it hit first, where it hit, and how far away. R3F does this automatically for pointer events, but you can also cast rays manually for custom mechanics.
The Raycasting Pipeline
Camera + Mouse
Pointer position mapped to normalized coordinates
Cast a Ray
From camera origin through mouse point
Test Intersections
Ray vs. triangles of each mesh
Sort by Distance
Nearest hit comes first
Dispatch Event
onClick fires on the closest object
See It In Action
The demo below visualizes raycasting in real time. Watch the ray extend from the camera and see which objects it intersects as you move your mouse.
Building It Step by Step
Step 1 -- Understand automatic raycasting
When you add onClick or onPointerOver to a mesh, R3F automatically raycasts against that object. You do not need to set up a raycaster yourself. Only meshes with event handlers are tested -- everything else is invisible to the ray.
// R3F raycasts against this mesh automatically
<mesh onClick={(e) => {
console.log('Hit at:', e.point)
console.log('Distance:', e.distance)
}}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial />
</mesh>The event object gives you the exact 3D hit position, the distance from the camera, the face that was hit, and more. No manual setup required.
Step 2 -- Use meshBounds for fast approximate hits
For complex geometry where pixel-perfect accuracy is not critical, drei's meshBounds replaces per-triangle raycasting with a single bounding-box test. Thousands of times faster.
import { meshBounds } from '@react-three/drei'
<mesh
raycast={meshBounds}
onClick={() => console.log('Bounding box hit!')}
>
<torusKnotGeometry args={[1, 0.3, 256, 64]} />
<meshStandardMaterial color="purple" />
</mesh>For a 16,000-triangle torus knot, this turns 16,000 intersection tests into just 1 box test. Clicks near the edges may register outside the actual geometry, but for buttons and UI that is fine.
Step 3 -- Cast rays manually for custom mechanics
For shooting, line-of-sight, or ground detection, create a Raycaster yourself and cast rays in any direction, not just from the mouse.
const raycaster = useRef(new THREE.Raycaster())
const down = useRef(new THREE.Vector3(0, -1, 0))
useFrame(() => {
raycaster.current.set(player.position, down.current)
const hits = raycaster.current.intersectObjects(ground)
if (hits.length > 0) {
player.position.y = hits[0].point.y + 1
}
})This casts a ray straight down from the player to find the ground, then snaps the player to the surface. The same idea powers shooters (ray from gun barrel), AI vision (ray toward target), and physics (collision probes).
What you just learned
Raycasting shoots an invisible ray and reports what it hits in the 3D scene
R3F raycasts automatically for pointer events -- only objects with handlers are tested
meshBounds from drei replaces per-triangle testing with a fast bounding-box check
You can cast rays manually for shooting, line-of-sight, and ground detection
Always store Raycaster and Vector3 objects in refs to avoid garbage collection stutter
Question
How does raycasting work with InstancedMesh? By default, R3F does not raycast individual instances -- it tests the base bounding box. For per-instance click detection, you need to implement a custom raycast function or use drei's <Instances> component which provides per-instance event support out of the box.
Think about it...
You have a 3D car model with 80,000 triangles. You want users to click it to open a detail panel. What is the fastest approach?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Toggle showRay off -- do events still work?
Try This!
Beginner
Set targetSize to 2 -- easier to hit!
Try This!
Beginner
Switch rayColor -- is it cosmetic only?
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// 50k polygon model -- 50k triangle tests per click
<mesh onClick={() => console.log('Clicked!')}>
<primitive object={gltf.scene} />
</mesh>// Tests EVERYTHING in the scene
const hits = raycaster.current.intersectObjects(
scene.children, true
)useFrame(() => {
// NEW objects every frame = garbage collection stutter
const origin = new THREE.Vector3(0, 5, 0)
const direction = new THREE.Vector3(0, -1, 0)
const raycaster = new THREE.Raycaster(origin, direction)
})Best Practices
Use simplified colliders
For complex models, overlay an invisible simple mesh for raycasting. Disable raycasting on the visual mesh with raycast={() => null}.
Minimize event handlers
Every mesh with a handler is a raycasting target. Fewer handlers means fewer intersection tests per pointer event.
Reuse objects in useFrame
Store Raycaster and Vector3 objects in refs. Never create new instances inside the frame loop.
meshBounds for UI elements
For buttons and clickable items, meshBounds from drei provides near-instant raycasting with minimal code.