Skip to content

Raycaster

Video Lecture

Raycaster Raycaster

 (Pay Per View)

You can use PayPal to purchase a one time viewing of this video for $1.49 USD.

Pay Per View Terms

  • One viewing session of this video will cost the equivalent of $1.49 USD in your currency.
  • After successful purchase, the video will automatically start playing.
  • You can pause, replay and go fullscreen as many times as needed in one single session for up to an hour.
  • Do not refresh the browser since it will invalidate the session.
  • If you want longer-term access to all videos, consider purchasing full access through Udemy or YouTube Memberships instead.
  • This Pay Per View option does not permit downloading this video for later viewing or sharing.
  • All videos are Copyright © 2019-2025 Sean Bradley, all rights reserved.

Description

Raycasting allows you to create a vector from a 3D point in the scene, and detect which object(s) the vector intersects.

The raycasting class is mostly used for mouse picking objects in the 3D scene.

We can set up the Raycaster position and direction using the set or setFromCamera methods and then call its intersectObject or intersectObjects methods to tell us many things about the scene objects that were intersected by the ray, including,

  • the distance of the intersection from the Raycaster position,
  • the position of the intersection in the 3D scene,
  • the face of the object that was intersected,
  • the direction of the faces normal,
  • the UV coordinate of the intersection on the face
  • and a reference to the intersected object itself.
<>

Lesson Script

The 3D model used in this lesson was created in the GLTFLoader Part 2 lesson.

./src/main.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
import Stats from 'three/addons/libs/stats.module.js'

const scene = new THREE.Scene()

new RGBELoader().load('img/venice_sunset_1k.hdr', (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping
  scene.environment = texture
  scene.background = texture
  scene.backgroundBlurriness = 0.5
})

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(0, 0, 3)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 0.8
renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
})

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

// const raycaster = new THREE.Raycaster()
// const pickables: THREE.Mesh[] = []
// const mouse = new THREE.Vector2()

// const arrowHelper = new THREE.ArrowHelper()
// arrowHelper.setLength(0.5)
// scene.add(arrowHelper)

// renderer.domElement.addEventListener('mousemove', (e) => {
//   mouse.set((e.clientX / renderer.domElement.clientWidth) * 2 - 1, -(e.clientY / renderer.domElement.clientHeight) * 2 + 1)

//   raycaster.setFromCamera(mouse, camera)

//   const intersects = raycaster.intersectObjects(pickables, false)

//   if (intersects.length) {
//     //console.log(intersects.length)
//     //console.log(intersects[0].point)
//     //console.log(intersects[0].object.name + ' ' + intersects[0].distance)
//     //console.log((intersects[0].face as THREE.Face).normal)

//     const n = new THREE.Vector3()
//     n.copy((intersects[0].face as THREE.Face).normal)
//     //n.transformDirection(intersects[0].object.matrixWorld)

//     arrowHelper.setDirection(n)
//     arrowHelper.position.copy(intersects[0].point)
//   }
// })

// renderer.domElement.addEventListener('dblclick', (e) => {
//   mouse.set((e.clientX / renderer.domElement.clientWidth) * 2 - 1, -(e.clientY / renderer.domElement.clientHeight) * 2 + 1)

//   raycaster.setFromCamera(mouse, camera)

//   const intersects = raycaster.intersectObjects(pickables, false)

//   if (intersects.length) {
//     const n = new THREE.Vector3()
//     n.copy((intersects[0].face as THREE.Face).normal)
//     //n.transformDirection(intersects[0].object.matrixWorld)

//     const cube = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), new THREE.MeshStandardMaterial())
//     cube.lookAt(n)
//     cube.position.copy(intersects[0].point)
//     cube.position.addScaledVector(n, 0.1)
//     cube.castShadow = true

//     scene.add(cube)
//     pickables.push(cube)
//   }
// })

new GLTFLoader().load('models/suzanne_scene.glb', (gltf) => {
  const suzanne = gltf.scene.getObjectByName('Suzanne') as THREE.Mesh
  suzanne.castShadow = true
  // @ts-ignore
  suzanne.material.map.colorSpace = THREE.LinearSRGBColorSpace
  //pickables.push(suzanne)

  const plane = gltf.scene.getObjectByName('Plane') as THREE.Mesh
  plane.receiveShadow = true
  //pickables.push(plane)

  const spotLight = gltf.scene.getObjectByName('Spot') as THREE.SpotLight
  spotLight.intensity /= 500
  spotLight.castShadow = true

  scene.add(gltf.scene)
})

const stats = new Stats()
document.body.appendChild(stats.dom)

function animate() {
  requestAnimationFrame(animate)

  controls.update()

  renderer.render(scene, camera)

  stats.update()
}

animate()

Raycaster (threejs.org)