Threejs之光线投射Raycaster

前言

Three.js中的光线投射(Raycaster)是一个功能强大的类,用于在三维场景中执行射线投射和交叉检测。它广泛应用于鼠标拾取、碰撞检测等场景,使用户能够与3D对象进行交互。

一、简要介绍

1.1 定义与原理

  • 定义:光线投射(Raycaster)是Three.js中用于进行射线投射和交叉检测的类。
  • 原理:通过从特定点(如相机位置或屏幕上的某一点)发射一条射线,并检测这条射线是否与场景中的物体相交,从而确定用户与哪些物体进行了交互。

1.2 构造器

构造器:Raycaster(origin : Vector3, direction : Vector3, near : Float, far : Float)
origin:光线投射的原点向量。
direction:向射线提供方向的方向向量,应当被标准化。
near:返回的所有结果比此值远。默认为0。
far:返回的所有结果都比此值近。默认为Infinity(正无穷)。

1.3 常用属性

far:远距离因数,表示哪些对象可以基于该距离而被Raycaster所考虑。
near:近距离因数,表示哪些对象可以基于该距离而被Raycaster所忽略。
camera:对依赖于视图的对象(如Sprites等)进行光线投射时使用的相机。
layers:用于控制Raycaster在执行相交测试时忽略哪些3D对象。

1.4 常用方法

  1. set(origin : Vector3, direction : Vector3)
    origin —— 光线投射的原点向量。
    direction —— 为光线提供方向的标准化方向向量。
    使用新的原点和方向来更新射线。
  2. setFromCamera(coords : Vector2, camera : Camera)
    coords—— 在标准化设备坐标中鼠标的二维坐标 —— X分量与Y分量应当在-1到1之间。
    camera—— 射线所来源的摄像机。根据鼠标在屏幕上的位置(归一化设备坐标)和相机位置来更新射线的原点和方向。
  3. intersectObject(object : Object3D, recursive : Boolean, optionalTarget : Array):检测射线与单个物体的相交情况。
  4. intersectObjects(objects : Array, recursive : Boolean, optionalTarget : Array)
    objects —— 检测和射线相交的一组物体。
    recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true
    optionalTarget —— (可选)设置结果的目标数组。如果不设置这个值,则一个新的Array会被实例化;如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)。检测射线与一组物体的相交情况。

二、代码准备及效果

2.1 演示代码准备

我们首先在场景添加三个甜甜圈,代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <script type="module">
        // 倒入轨道控制器
        import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
        import * as THREE from "three";
        // 创建场景
        const scene = new THREE.Scene();

        // 创建相机
        const camera = new THREE.PerspectiveCamera( // 透视相机
            45, // 视角 角度数
            window.innerWidth / window.innerHeight, // 宽高比 占据屏幕
            0.1, // 近平面(相机最近能看到物体)
            1000, // 远平面(相机最远能看到物体)
        );
        camera.position.set(0, 2, 20);
        // camera.lookAt(0, 0, 0);

        // 创建渲染器
        const renderer = new THREE.WebGLRenderer({
            antialias: true, // 抗锯齿
        });
        // 设置渲染器宽高
        renderer.setSize(window.innerWidth, window.innerHeight)
        // renderer(渲染器)的dom元素添加到我们的HTML文档中
        document.body.appendChild(renderer.domElement)

        // 开启坐标轴辅助器
        const axesHelper = new THREE.AxesHelper(5);
        scene.add(axesHelper);

		const geometry = new THREE.TorusGeometry( 1, 0.5, 20, 25 );
        const geometry1 = new THREE.TorusGeometry( 1, 0.5, 20, 25 );
        const geometry2 = new THREE.TorusGeometry( 1, 0.5, 20, 25 );

		const material = new THREE.MeshBasicMaterial({color: 0xff0000});
		const material1 = new THREE.MeshBasicMaterial({color: 0xff0000});
		const material2 = new THREE.MeshBasicMaterial({color: 0xff0000});

		const torus = new THREE.Mesh( geometry, material );
		const torus1 = new THREE.Mesh( geometry1, material1 );
		const torus2 = new THREE.Mesh( geometry2, material2 );

        torus1.position.x = 3;
        torus2.position.x = -3;


        torus.rotation.y = Math.PI / 2;
        torus1.rotation.y = Math.PI / 2;
        torus2.rotation.y = Math.PI / 2;


		scene.add(torus, torus1, torus2);

        // 控制器
        const control = new OrbitControls(camera, renderer.domElement)
        // 开启阻尼惯性,默认值为0.05
        control.enableDamping = true

        // 渲染循环动画
        function animate() {
            // 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)
            requestAnimationFrame(animate);
            // 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用
            control.update();
            renderer.render(scene, camera);
        };

        // 执行动画
        animate();
    </script>
</body>

</html>

2.2 效果

在这里插入图片描述
在这里插入图片描述
可以看到场景中就有了三个甜甜圈以及坐标轴辅助器。


三、创建射线Raycaster及效果

3.1 代码

在上述代码的基础上添加:

        // 添加光线投射
        const raycaster = new THREE.Raycaster();
        const rcOrigin = new THREE.Vector3(-5, 0, 0);
        const rcDirection = new THREE.Vector3(1, 0, 0);
        rcDirection.normalize(); // 标准化

        // 第一种构造方法设置
        // const raycaster = new THREE.Raycaster(rcOrigin, rcDirection);
        
        // 第二种方法设置
        raycaster.set(rcOrigin, rcDirection);

        let torusObjects = [torus, torus1, torus2];
        // 渲染循环动画
        function animate() {
            // 获取自时钟启动后的秒数
            const elapsedTime = clock.getElapsedTime();
            // 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)
            requestAnimationFrame(animate);
            // 一开始先将物体遍历设置颜色,如果不设置的话,下面判断相交的射线一开始会判定为相交
            for(const key in torusObjects){
                torusObjects[key].material.color.set(0xff0000);
                torusObjects[key].position.y = 4 * Math.sin(elapsedTime*((key+1)/10 + 0.2));
            }
			// 循环将与射线相交的物体设置为不同的颜色
            const itobjects = raycaster.intersectObjects(torusObjects);
            for(const itobject of itobjects){
                itobject.object.material.color.set(0xcc33ff);
            }
            
            // 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用
            control.update();
            renderer.render(scene, camera);
        };

可以知道我们往x正方向投射了光线,与之交互就会变颜色。

3.2 效果

请添加图片描述


四、完整代码

最后给出完整代码,如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <script type="module">
        // 倒入轨道控制器
        import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
        import * as THREE from "three";
        // 创建场景
        const scene = new THREE.Scene();

        // 创建相机
        const camera = new THREE.PerspectiveCamera( // 透视相机
            45, // 视角 角度数
            window.innerWidth / window.innerHeight, // 宽高比 占据屏幕
            0.1, // 近平面(相机最近能看到物体)
            1000, // 远平面(相机最远能看到物体)
        );
        camera.position.set(0, 2, 20);
        // camera.lookAt(0, 0, 0);

        // 创建渲染器
        const renderer = new THREE.WebGLRenderer({
            antialias: true, // 抗锯齿
        });
        // 设置渲染器宽高
        renderer.setSize(window.innerWidth, window.innerHeight)
        // renderer(渲染器)的dom元素添加到我们的HTML文档中
        document.body.appendChild(renderer.domElement)

        // 开启坐标轴辅助器
        const axesHelper = new THREE.AxesHelper(5);
        scene.add(axesHelper);

        const geometry = new THREE.TorusGeometry( 1, 0.5, 20, 25 );
        const geometry1 = new THREE.TorusGeometry( 1, 0.5, 20, 25 );
        const geometry2 = new THREE.TorusGeometry( 1, 0.5, 20, 25 );

		const material = new THREE.MeshBasicMaterial({color: 0xff0000});
		const material1 = new THREE.MeshBasicMaterial({color: 0xff0000});
		const material2 = new THREE.MeshBasicMaterial({color: 0xff0000});

		const torus = new THREE.Mesh( geometry, material );
		const torus1 = new THREE.Mesh( geometry1, material1 );
		const torus2 = new THREE.Mesh( geometry2, material2 );
        torus1.position.x = 3;
        torus2.position.x = -3;

        torus.rotation.y = Math.PI / 2;
        torus1.rotation.y = Math.PI / 2;
        torus2.rotation.y = Math.PI / 2;

		scene.add(torus, torus1, torus2);

        // 添加光线投射
        const raycaster = new THREE.Raycaster();
        const rcOrigin = new THREE.Vector3(-5, 0, 0);
        const rcDirection = new THREE.Vector3(1, 0, 0);
        rcDirection.normalize(); // 标准化

        // 第一种构造方法设置
        // const raycaster = new THREE.Raycaster(rcOrigin, rcDirection);
        
        // 第二种方法设置
        raycaster.set(rcOrigin, rcDirection);

        let torusObjects = [torus, torus1, torus2];


        // 控制器
        const control = new OrbitControls(camera, renderer.domElement)
        // 开启阻尼惯性,默认值为0.05
        control.enableDamping = true

        // 跟踪时间对象
        const clock = new THREE.Clock();
       
        // 渲染循环动画
        function animate() {
            // 获取自时钟启动后的秒数
            const elapsedTime = clock.getElapsedTime();
            // 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)
            requestAnimationFrame(animate);
            // 一开始先将物体遍历设置颜色,如果不设置的话,下面判断相交的射线一开始会判定为相交
            for(const key in torusObjects){
                torusObjects[key].material.color.set(0xff0000);
                torusObjects[key].position.y = 4 * Math.sin(elapsedTime*((key+1)/10 + 0.2));
            }
			// 循环将与射线相交的物体设置为不同的颜色
            const itobjects = raycaster.intersectObjects(torusObjects);
            for(const itobject of itobjects){
                itobject.object.material.color.set(0xcc33ff);
            }
            
            // 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用
            control.update();
            renderer.render(scene, camera);
        };

        // 执行动画
        animate();
    </script>
</body>
</html>

在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你华还是你华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值