threejs使用Raycaster选取模型

1,332 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

一、前言

相信大家在使用threejs的过程中,都需要点击模型,进行类似替换贴图,触发事件等等操作,但是threejs又不能直接绑定元素,这就需要Raycaster射线类。

原理就是从鼠标点击的位置,发射一条射线,然后按距离远近,将穿过的物品一一返回给我们。

直接试试吧。

二、过程

1、引入three并生成场景

由于不是本文的主要叙述内容,就不再重复了,如果有不会的小伙伴,可以移步我的另一篇文章 # vite2+vue3+ts+threejs搭建项目,摸鱼学习vue3和threejs的日子(二)

2、 射线函数

前提,我的场景是宽1000px,高800px,canvas窗口距离浏览器窗口顶部164px,距离浏览器窗口左侧460px,为什么需要知道这个呢,是因为要进行换算,鼠标点击位置,要减去偏移值,才是鼠标在threejs场景里的点击位置。

如图

image.png

生成好场景了,就可以写射线函数onDocumentMouseDown了

需要注意的地方就是mouse的换算公式

mouse.x = ((鼠标点击位置 - 左侧偏移值) / 场景宽度) * 2 - 1
mouse.y = ((鼠标点击位置 - 顶部偏移值) / 场景高度) * - 2 + 1

然后就是要注意一下,由于raycaster只对mesh对象有感应,而导入的模型基本是group的,所以intersectObject的第二个参数必须为true,要检查后代才能拿到该模型。

ps:如果目标模型的同级模型干扰严重,也可以采用在模型外部新建一个透明的mesh进行包裹绑定。

下面是官网的说法

raycaster.intersectObject() ( object : [Object3D](), recursiveBoolean, optionalTarget : Array ) : Array

[object](<> "Object3D") —— 检查与射线相交的物体。\

[recursive](<> "Boolean") —— 若为true,则同时也会检查所有的后代。
否则将只会检查对象本身。默认值为true。\

[optionalTarget](<> "Array") — (可选)设置结果的目标数组。
如果不设置这个值,则一个新的[Array](<> "Array")会被实例化;
如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)。

检测所有在射线与物体之间,包括或不包括后代的相交部分。
返回结果时,相交部分将按距离进行排序,最近的位于第一个。

本文打算实现是效果就是点击mesh,在生成mesh的时候,我将mesh的name赋值为了cubes,方便更换mesh的纹理图片

具体函数如下

function onDocumentMouseDown(event: any) {
  let mouse = new THREE.Vector2() // 生成一个矢量
  let raycaster = new THREE.Raycaster() // 生成我们的本次主角射线类
  //var windowleft = 460 // threejs的canvas距离窗口左边的距离
  //var windowtop = 140 + 24 // threejs的canvas距离窗口顶边的距离
  //var sceneWidth = 1000 // 场景宽度
  //var sceneHeight = 800 // 场景高度
  //mouse.x = ((event.clientX - windowleft) / sceneWidth) * 2 - 1
  //mouse.y = ((event.clientY - windowtop) / sceneHeight) * -2 + 1
  // 后面我发现这样写死有点蠢,改良了一下,替换上面的6行,换汤不换药
  var container = document.getElementById('three3d')
  const getBoundingClientRect = container.getBoundingClientRect()
  mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1
  mouse.y = ((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * -2 + 1
  
  
  raycaster.setFromCamera(mouse, camera) // 调用setFromCamera方法,参数是mouse和相机
  
  // intersectObjects第二个参数必须为true
  const intersects = raycaster.intersectObjects(scene.children, true)
  // 这个intersects就是返回的全部对象了
  intersects.forEach(item => {
    if(item.object.name === 'cubes') {
      const texture = new THREE.TextureLoader().load(
        "/assets/puzzle-factory.png"
      ) //首先,获取到纹理
      // item.object.material.map = texture
      mesh.material.map = texture
      // item.object.needsUpdate = true
    }
  })
}

绑定到window事件

    // 点击模型
window.addEventListener('click', onDocumentMouseDown)

3、效果

点击前

image.png

鼠标点击了mesh,返回了6个对象(排序按远近),可以看到第一个就是我们要的cubes,其余5个是坐标系等等其他物品,可以忽略

image.png

点击后,精确换图,当然也可以触发别的函数操作

image.png

ps: 我是地霊殿__三無,下期我们试试gasp动画库

Snipaste_2022-07-19_15-30-26.jpg