如何在THREE.JS中利用射线与物体进行交互-biu biu biu!!!

138 阅读2分钟

光线投射Raycaster-全窗口渲染的情况

three.js的场景中我们要点击某个模型获取它的信息、或者做一些其他操作,这时候要用到 Raycaster(光线投射 )

☝ 原理就是在你鼠标点击的位置发射一根射线,被这根射线射中的物体都被记录下来

在光线投射之前,我们要做以下几步

首先我们准备多一点物体,可以让我们点来点去

(这里不要在外面设置材质共用材质对象,之前我是那样写的,因为所有物体引用了一个材质对象,你触发改变材质会全部改变🤣,需要每个物体都是自己的材质对象!)

for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
        for (let k = 0; k < 5; k++) {
            const geometry = new THREE.BoxBufferGeometry(5, 5, 5)
            const material = new THREE.MeshBasicMaterial({
                wireframe: true,
                color: 0xff0fff,
            })
            let mesh = new THREE.Mesh(geometry, material)
            mesh.position.x = i * 12
            mesh.position.y = j * 12
            mesh.position.z = k * 12
            cubeArr.add(mesh)
        }
    }
}

获取了我们鼠标点击的坐标,也可以知道画布的宽高(假设我们此时按照全窗口渲染,window.innerWidht&window.innerHeight

const renderDom = document.body
const cavWidth = window.innerWidth
const cavHeight = window.innerHeight

那么我们可以算出一个点击位置在画布中的比例关系,然后通过一定算法转化为投射光线需要的 -1 - 1的二维向量!(因为dom中左上角 为0 0 坐标系,向右,向上分别递增!,但是THREE.JS的坐标轴在中心(0,0)上正y,右正x,前-z),所以下面取 y的比例要取反!

const pointer = new THREE.Vector2();

renderDom.addEventListener("click", event => {
		
		// 转比例  event.clientX / cavWidth 范围 (0~1) 需要 (-1~1) 先转成 (0~2),所以*2 然后逐个 -1就得到了 (-1~1)
    pointer.x = (event.clientX / cavWidth) * 2 - 1;
    pointer.y = -(event.clientY / cavHeight) * 2 + 1;

    // 每次点击,通过摄像机和鼠标位置实时更新射线
    raycaster.setFromCamera(pointer, camera);

    // 计算物体和射线的焦点
    const intersects = raycaster.intersectObjects(cubeArr.children);

    console.log(intersects);
    for (let i = 0; i < intersects.length; i++) {
				 // 将光线选中的物体,材质颜色变为白色
        intersects[i].object.material.color.set(0xffffff);
    }
})

效果: 射线效果1.gif 完整代码:

import * as THREE from "three"
import {
    OrbitControls
} from "three/examples/jsm/controls/OrbitControls"


// 场景
const scene = new THREE.Scene()
const renderDom = document.body
const cavWidth = window.innerWidth
const cavHeight = window.innerHeight
// 相机
const camera = new THREE.PerspectiveCamera(45, cavWidth / cavHeight, 0.1, 1000)
camera.position.set(150, 150, 150)

// 坐标辅助
const axes = new THREE.AxesHelper(100)
scene.add(axes)

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const cubeArr = new THREE.Group()

for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
        for (let k = 0; k < 5; k++) {
            const geometry = new THREE.BoxBufferGeometry(5, 5, 5)
            const material = new THREE.MeshBasicMaterial({
                wireframe: true,
                color: 0xff0fff,
            })
            let mesh = new THREE.Mesh(geometry, material)
            mesh.position.x = i * 12
            mesh.position.y = j * 12
            mesh.position.z = k * 12
            cubeArr.add(mesh)
        }
    }
}
scene.add(cubeArr)
renderDom.addEventListener("click", event => {

    pointer.x = (event.clientX / cavWidth) * 2 - 1;
    pointer.y = -(event.clientY / cavHeight) * 2 + 1;

    // 每次点击通过摄像机和鼠标位置实时更新射线
    raycaster.setFromCamera(pointer, camera);
    // 计算物体和射线的焦点
    const intersects = raycaster.intersectObjects(cubeArr.children);
    for (let i = 0; i < intersects.length; i++) {
        intersects[i].object.material.color.set(0xffffff);
    }
})
const renderer = new THREE.WebGLRenderer()
renderer.setSize(cavWidth, cavHeight)

renderDom.appendChild(renderer.domElement)

// 控制器
const controller = new OrbitControls(camera, renderer.domElement)

// 渲染函数
const handleRender = () => {
    controller.update
    // 通过摄像机和鼠标位置更新射线
    renderer.render(scene, camera)
    requestAnimationFrame(handleRender)
}
requestAnimationFrame(handleRender)

光线投射Raycaster-某个盒子中去渲染!

其实这里也差不多的思路,只不过那个比例计算的是,盒子角度,你点击的坐标,去比上盒子的宽高,拿这个比例去转换为投射光线所需要的二维向量!

<div class="raycaster" id="raycaster"></div>
.raycaster {
	margin: 100px auto;
	width: 50%;
	height: 500px;
}

这里准备了一个盒子,将来呢,我们就在这个盒子里面,按照这个盒子的大小去渲染我们的画布!

const renderDom = document.getElementById("raycaster") // 获取画布元素

// 画布宽高,相机比例,渲染器大小都按照这个宽高计算!
const cavWidth = renderDom.offsetWidth
const cavHeight = renderDom.offsetHeight

下面就是完成我们盒子坐标和盒子宽高比例了!话不多说全在注释里了!!🤣

renderDom.addEventListener("click", event => {
    /**
     * 首先要计算出, 在盒子的角度, 点击的是盒子的哪个位置, 求出一个盒子角度点击的坐标
     * 那么x轴就是点击的 x - 盒子的offsetLeft
     *    y轴就是点击的y - 盒子距离顶部的高度offsetTop
     */
    const boxEventX = event.x - renderDom.offsetLeft
    const boxEventY = event.y - renderDom.offsetTop

    // 然后我们转化的比例 应该是 上面盒子角度点击的坐标与盒子宽高的比例!
    pointer.x = (boxEventX / cavWidth) * 2 - 1
    pointer.y = -(boxEventY / cavHeight * 2) + 1

    // 每次点击通过摄像机和鼠标位置实时更新射线
    raycaster.setFromCamera(pointer, camera);

    // 计算物体和射线的焦点
    const intersects = raycaster.intersectObjects(cubeArr.children);
    for (let i = 0; i < intersects.length; i++) {
        intersects[i].object.material.color.set(0xffffff);
    }
})

盒子内部射线效果.gif