Three.js - 鼠标拾取(射线追踪法)(十九)

射线追踪法

  • 射线追踪法是最常见的方法,因为three.js提供了Raycaster对象来实现它。
  • 原理:从鼠标处发射一条射线,穿透场景的视椎体,通过计算,找出与射线相交的对象。

Raycaster

  • 属性:
  1. origin 光线投射的原点。
  2. direction 射线的方向。
  3. near 投射近点,不能大于far,不能为负值,其默认值为0。
  4. far 投射远点,不能小于near,其默认值为无穷大。
  • 常用方法:
  1. .setFromCamera(coords,camera) 更新原点坐标和相机视椎。coords原点坐标,camera相机。
  2. .intersectObject(scenes,recursive,optionalTarget) 检查射线与场景对象和其子集是否有交集。scenes 要检查的场景对象数组格式recursivetrue检查所有后代。optionalTarget返回交集结果,可选。

使用

  • 基础场景,绘制两个立方体。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学习</title>
  </head>
  <body>
    <canvas id="c2d" class="c2d" width="1000" height="500"></canvas>
    <script type="module">
      import * as THREE from './file/three.js-dev/build/three.module.js'
      import { OrbitControls } from './file/three.js-dev/examples/jsm/controls/OrbitControls.js'

      const canvas = document.querySelector('#c2d')
      // 渲染器
      const renderer = new THREE.WebGLRenderer({ canvas })

      const fov = 40 // 视野范围
      const aspect = 2 // 相机默认值 画布的宽高比
      const near = 0.1 // 近平面
      const far = 10000 // 远平面
      // 透视投影相机
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
      camera.position.set(0, 6, 5)
      camera.lookAt(0, 0, 0)
      // 控制相机
      const controls = new OrbitControls(camera, canvas)
      controls.update()

      // 场景
      const scene = new THREE.Scene()
      {
        // 灯光
        const color = 0xffffff
        const intensity = 1
        const light = new THREE.DirectionalLight(color, intensity)
        light.position.set(-1, 10, 4)
        scene.add(light)
      }

      {
        // 立方体
        const boxWidth = 1
        const boxHeight = 1
        const boxDepth = 1
        const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)

        const material = new THREE.MeshPhongMaterial({
          color: 0x6688aa
        })
        const cube = new THREE.Mesh(geometry, material)
        cube.position.x = -1
        scene.add(cube)

        const material2 = new THREE.MeshPhongMaterial({
          color: 0x6688aa
        })
        const cube2 = new THREE.Mesh(geometry, material2)
        cube2.position.x = 1
        scene.add(cube2)
      }

      // 渲染
      function render() {
        renderer.render(scene, camera)
        requestAnimationFrame(render)
      }
      requestAnimationFrame(render)
    </script>
  </body>
</html>

image.png

使用.Raycaster()

  • 第一步获取鼠标的屏幕坐标,将鼠标坐标转化成设备坐标(归一化),后续的操作都交由.Raycaster()完成。
  • 这里非全屏使用画布,鼠标坐标以画布开始计算。
      // 计算 以画布 开始为(0,0)点 的鼠标坐标
      function getCanvasRelativePosition(event) {
        const rect = canvas.getBoundingClientRect()
        return {
          x: ((event.clientX - rect.left) * canvas.width) / rect.width,
          y: ((event.clientY - rect.top) * canvas.height) / rect.height
        }
      }
  • 归一化鼠标坐标。
      /**
       * 获取鼠标在three.js 中归一化坐标
       * */
      function setPickPosition(event) {
        let pickPosition = { x: 0, y: 0 }

        // 计算后 以画布 开始为 (0,0)点
        const pos = getCanvasRelativePosition(event)

        // 数据归一化
        pickPosition.x = (pos.x / canvas.width) * 2 - 1
        pickPosition.y = (pos.y / canvas.height) * -2 + 1

        return pickPosition
      }
  • 监听鼠标事件,获取归一化坐标。使用.intersectObjects()发出射线计算相交对象。
  • 需要一个全局对象,用于鼠标离开后恢复对象的原样。
      // 监听鼠标
      window.addEventListener('mousemove', onRay)
      // 全局对象
      let lastPick = null
      function onRay(event) {
        let pickPosition = setPickPosition(event)

        const raycaster = new THREE.Raycaster()
        raycaster.setFromCamera(pickPosition, camera)
        // 计算物体和射线的交点
        const intersects = raycaster.intersectObjects(scene.children, true)

        // 数组大于0 表示有相交对象
        if (intersects.length > 0) {
          if (lastPick) {
            lastPick.object.material.color.set('yellow')
          }
          lastPick = intersects[0]
        } else {
          if (lastPick) {
            // 复原
            lastPick.object.material.color.set(0x6688aa)
            lastPick = null
          }
        }
      }

1.gif

  • 就这样一个简单鼠标拾取就完成了。
  • 除了射线追踪法还可以使用GPU拾取,它是利用颜色的6位16进制表示,以颜色作为ID,在后台渲染出纹理后。然后,检查鼠标位置关联的像素的颜色,通过颜色来确认相交的对象是哪个。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值