Skip to content

十四、射线拾取模型

更新: 12/19/2025字数: 0 字阅读: 0 分钟

射线Ray

类比数学几何中提到的射线,在三维空间中,一条线把一个点作为起点,然后沿着某个方向无限延伸。

1、创建射线Ray

javascript
// 创建射线对象Ray
const ray = new THREE.Ray()

2、设置射线起点.origin

可以用一个三维向量Vector3的x、y、z分量表示,也可以用.set()方法设置

javascript
// 方式一
ray.origin = new THREE.Vector3(1,0,3);

// 方式二
ray.origin.set(1, 0, 3);

3、设置射线方向.direction

用一个三维向量Vector3表示,向量长度保证为1,也就是单位向量。

注意:.direction的值需要是单位向量,不是的话可以执行.normalize()归一化或者说标准化。

javascript
// 表示射线沿着x轴正方向
ray.direction = new THREE.Vector3(1,0,0);
// 表示射线沿着x轴负方向
ray.direction = new THREE.Vector3(-1,0,0);


// 向量长度不是1,需要归一化
ray.direction = new THREE.Vector3(5,0,0).normalize();
// 表示射线沿着xy坐标轴的中间线
ray.direction = new THREE.Vector3(1,1,0).normalize();

.intersectTriangle()方法计算一个射线和一个三角形在3D空间中是否交叉

执行.intersectTriangle()方法,如果相交返回交点坐标,不相交返回空值null

.intersectTriangle( a :Vector3, b :Vector3, c :Vector3, backfaceCulling :Boolean, target :Vector3) :Vector3

  • a,b,c- 组成三角形的三个Vector3
  • backfaceCulling- 是否使用背面剔除。
  • target— 结果将会被复制到这一Vector3中。
javascript
// 三角形三个点坐标
const p1 = new THREE.Vector3(100, 25, 0);
const p2 = new THREE.Vector3(100, -25, 25);
const p3 = new THREE.Vector3(100, -25, -25);
const point = new THREE.Vector3();//用来记录射线和三角形的交叉点
// `.intersectTriangle()`计算射线和三角形是否相交叉,相交返回交点,不相交返回null
const result = ray.intersectTriangle(p1,p2,p3,false,point);
console.log('交叉点坐标', point);
console.log('查看是否相交', result);

Raycaster射线投射器

射线投射器Raycaster具有一个射线属性.ray,该属性的值就是射线对象Ray

javascript
// 方式一
const raycaster = new THREE.Raycaster(new THREE.Vector3(-100, 0, 0),new THREE.Vector3(1, 0,0));

// 方式二
// 设置射线起点
raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
// 设置射线方向射线方向沿着x轴
raycaster.ray.direction = new THREE.Vector3(1, 0,0);

射线交叉计算(.intersectObjects()方法)

射线投射器Raycaster通过.intersectObjects()方法可以计算出来与自身射线.ray相交的网格模型。

.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算,未选中对象返回空数组[],选中一个对象,数组1个元素,选中多个对象,数组多个元素,如果选中多个对象,对象在数组中按照先后排序。

javascript
//.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
// 未选中对象返回空数组[],选中一个对象,数组1个元素,选中多个对象,数组多个元素
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
console.log("射线器返回的对象", intersects);
// intersects.length大于0说明,说明选中了模型
// 如果选中多个模型,就会按照先后顺序排序
if (intersects.length > 0) {
    // console.log("交叉点", intersects[0].point);
    // console.log("交叉对象",intersects[0].object);
    // console.log("射线原点和交叉点距离",intersects[0].distance);
    // 选中模型的第一个模型,设置为红色
    intersects[0].object.material.color.set(0xff0000);
}

注意:射线拾取的时候,mesh1, mesh2, mesh3位置要确保更新的情况下,执行射线计算

javascript
//注意更新下模型的世界矩阵,你设置的position生效,再进行射线拾取计算
model.updateMatrixWorld(true);

屏幕坐标转标准设备坐标

获取鼠标事件坐标

.offsetX.offsetY表示鼠标单击位置的坐标,单位是像素px,以点击的HTML元素左上角为坐标原点,水平向右方向为x轴,竖直向下方向为y轴。

.clientX.clientY.offsetX.offsetY含义区别在于坐标原点不同,是以浏览器窗口页面的左上角作为坐标原点,其他一样。

标准设备坐标系

Three.js Canvas画布具有一个标准设备坐标系,该坐标系的坐标原点在canvas画布的中间位置,x轴水平向右,y轴竖直向上。

标准设备坐标系的坐标值不是绝对值,是相对值,范围是[-1,1]区间,也是说canvas画布上任何一个位置的坐标,如果用标准设备坐标系去衡量,那么坐标的所有值都在-1到1之间。

屏幕坐标转标准设备坐标

.offsetX.offsetY坐标转化为标准设备坐标:

canvas画布的宽度是width,.offsetX的范围是0~width,.offsetX除以canvas画布宽度width,就可以从绝对值变成相对值,范围是0~1,相对值乘以2,范围0~2,再减去1,范围是-1~1,刚好和canvas画布标准设备坐标的范围-1~1能够对应起来。

javascript
// 坐标转化公式
addEventListener('click',function(event){
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})

.clientX.clientY坐标转化为标准设备坐标:

注意:把.clientX.clientY转化为以canvas画布左上角为原点的坐标

javascript
// 屏幕坐标转标准设备坐标
addEventListener('click',function(event){
    // left、top表示canvas画布布局,距离顶部和左侧的距离(px)
    const px = event.clientX-left;
    const py = event.clientY-top;
    //屏幕坐标px、py转标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})

通过Raycaster实现鼠标点击选中模型

  1. 坐标转化(鼠标单击的屏幕坐标转标准设备坐标)
  2. 使用.setFromCamera()方法进行射线计算(通过鼠标单击位置+相机参数计算射线值)
  3. 使用.intersectObjects()射线交叉计算
javascript
renderer.domElement.addEventListener('click', function (event) {
    // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转WebGL标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
    
    //创建一个射线投射器`Raycaster`
    const raycaster = new THREE.Raycaster();
    //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
    // 形象点说就是在点击位置创建一条射线,用来选中拾取模型对象
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    
    //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
    // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中多个对象,数组多个元素
    const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
    console.log("射线器返回的对象", intersects);
    // intersects.length大于0说明,说明选中了模型
    if (intersects.length > 0) {
        // console.log("交叉点", intersects[0].point);
        // console.log("交叉对象",intersects[0].object)
        // 选中模型的第一个模型,设置为红色
        intersects[0].object.material.color.set(0xff0000);
    }
})

射线拾取层级模型(模型描边)

后处理描边样式OutlinePass和射线投射器Raycaster鼠标点击选中模型结合

射线拾取Sprite控制场景

射线投射器Raycaster通过.intersectObjects()方法可以拾取网格模型Mesh,一样也可以拾取精灵模型Sprite

1、给精灵模型绑定一个函数.change()

三维场景中提供了两个精灵模型对象,可以分别自定义一个方法.change()

javascript
sprite.change = function(){
  mesh.material.color.set(0xffffff);
}
sprite2.change = function(){
  mesh.material.color.set(0xffff00);
}

2、射线选中Sprite,sprite.change()函数执行

鼠标单击,如果选中某个精灵模型,就调用该精灵模型绑定的函数.change()

javascript
addEventListener('click', function (event) {
    ...
    ...
    // 射线交叉计算拾取精灵模型
    const intersects = raycaster.intersectObjects([sprite,sprite2]);
    if (intersects.length > 0) {
        intersects[0].object.change();//执行选中sprite绑定的change函数
    }
})