Three.js中实现碰撞检测

three,js,实现,碰撞检测 · 浏览次数 : 377

小编点评

**代码分析** 代码分析包含以下三个部分: * **三维模型** * 创建两个三维模型,分别用于表示第一个和第二个Cube。 * 设置模型的位置和旋转信息。 * **物理引擎** * 创建两个物理引擎,分别用于表示第一个和第二个Cube。 * 设置模型的形状和体积信息。 * 设置物理引擎的碰撞检测参数。 * **动画** * 创建一个动画对象,用于控制模型的运动。 * 在动画中设置模型的位置和旋转信息。 * 使用 `requestAnimationFrame` 方法控制动画的帧率。 **精确性** 代码分析中没有明确的精确性信息,例如模型的材质、物理引擎的碰撞检测参数等。 **其他** * 代码使用了 `three` 库进行三维模型和物理引擎的创建和控制。 * 使用 `cannon` 库进行物理引擎的碰撞检测。 * 代码展示了如何创建一个三维模型,物理引擎,并动画控制其运动。

正文

1. 引言

碰撞检测是三维场景中常见的需求,Three.js是常用的前端三维JavaScript库,本文就如何在Three.js中进行碰撞检测进行记述

主要使用到的方法有:

  • 射线法Raycaster
  • 包围盒bounding box
  • 物理引擎Cannon.js

2. Raycaster

Raycaster用于进行raycasting(光线投射), 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)

在某些情况下也能用于初略的碰撞检测

示例如下:

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,
    body,
    canvas {
      height: 100%;
      width: 100%;
      margin: 0;
    }
  </style>

</head>

<body>
  <canvas id="canvas"></canvas>

  <script type="importmap">
		{
			"imports": {
				"three": "https://unpkg.com/three/build/three.module.js",
				"three/addons/": "https://unpkg.com/three/examples/jsm/"
			}
		}
	</script>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import Stats from 'three/addons/libs/stats.module.js'

    const scene = new THREE.Scene();

    const raycaster = new THREE.Raycaster();
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    // 创建性能监视器
    let stats = new Stats();
    // 将监视器添加到页面中
    document.body.appendChild(stats.domElement)

    const canvas = document.querySelector('#canvas');
    const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
    camera.position.set(0, 0, 10);

    // 添加环境光
    const ambient = new THREE.AmbientLight("#FFFFFF");
    ambient.intensity = 5;
    scene.add(ambient);
    // 添加平行光
    const directionalLight = new THREE.DirectionalLight("#FFFFFF");
    directionalLight.position.set(0, 0, 0);
    directionalLight.intensity = 16;
    scene.add(directionalLight);

    // 添加Box
    const box = new THREE.BoxGeometry(1, 1, 1);
    const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const boxMesh = new THREE.Mesh(box, boxMaterial);
    boxMesh.position.set(6, 0, 0);
    scene.add(boxMesh);

    const renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector('#canvas'),
      antialias: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight, false)

    const controls = new OrbitControls(camera, renderer.domElement);

    function animate() {
      // 更新帧数
      stats.update()

      boxMesh.position.x -= 0.01;

      cube.material.color.set(0x0000ff);

      raycaster.set(boxMesh.position, new THREE.Vector3(-1, 0, 0).normalize());
      const intersection = raycaster.intersectObject(cube);
      if (intersection.length > 0) {
        if (intersection[0].distance < 0.5) {
          intersection[0].object.material.color.set(0xff0000);
        }
      }

      raycaster.set(boxMesh.position, new THREE.Vector3(1, 0, 0).normalize());
      const intersection2 = raycaster.intersectObject(cube);
      if (intersection2.length > 0) {
        if (intersection2[0].distance < 0.5) {
          intersection2[0].object.material.color.set(0xff0000);
        }
      }

      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>

</html>

动画

可以看到,两个立方体在刚接触时和要分开时检测出了碰撞,但是在两个立方体接近重合时却没检测出碰撞

这是因为Raycaster使用的是一根射线来检测,射线需要起点和方向,上述例子中将起点设为绿色立方体的中心,当绿色立方体中心在蓝色立方体内时,就检测不出碰撞了

另外,射线是需要方向的,上述示例中设置为检测左右两个方向,然而方向是难以穷举的,太多的Raycaster也严重损耗性能

所以说,Raycaster在某些情况下也能用于初略的碰撞检测,然而问题是显著的

3. bounding box

bounding box,在Three.js中为Box3类,表示三维空间中的一个轴对齐包围盒(axis-aligned bounding box,AABB)

利用bounding box,可以用来检测物体是否相交(即,碰撞)

示例如下(和Raycaster部分的代码相比只修改了animate函数):

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,
    body,
    canvas {
      height: 100%;
      width: 100%;
      margin: 0;
    }
  </style>

</head>

<body>
  <canvas id="canvas"></canvas>

  <script type="importmap">
		{
			"imports": {
				"three": "https://unpkg.com/three/build/three.module.js",
				"three/addons/": "https://unpkg.com/three/examples/jsm/"
			}
		}
	</script>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import Stats from 'three/addons/libs/stats.module.js'

    const scene = new THREE.Scene();

    const raycaster = new THREE.Raycaster();
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    // 创建性能监视器
    let stats = new Stats();
    // 将监视器添加到页面中
    document.body.appendChild(stats.domElement)

    const canvas = document.querySelector('#canvas');
    const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
    camera.position.set(0, 0, 10);

    // 添加环境光
    const ambient = new THREE.AmbientLight("#FFFFFF");
    ambient.intensity = 5;
    scene.add(ambient);
    // 添加平行光
    const directionalLight = new THREE.DirectionalLight("#FFFFFF");
    directionalLight.position.set(0, 0, 0);
    directionalLight.intensity = 16;
    scene.add(directionalLight);

    // 添加Box
    const box = new THREE.BoxGeometry(1, 1, 1);
    const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const boxMesh = new THREE.Mesh(box, boxMaterial);
    boxMesh.position.set(6, 0, 0);
    scene.add(boxMesh);

    const renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector('#canvas'),
      antialias: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight, false)

    const controls = new OrbitControls(camera, renderer.domElement);

    function animate() {
      // 更新帧数
      stats.update()

      boxMesh.position.x -=  0.02;

      const cubeBox = new THREE.Box3().setFromObject(cube);
      const boxMeshBox = new THREE.Box3().setFromObject(boxMesh);
      cubeBox.intersectsBox(boxMeshBox) ? cube.material.color.set(0xff0000) : cube.material.color.set(0x0000ff);

      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }
    
    animate();
  </script>
</body>

</html>

动画1

可以看到,在Three.js中使用bounding box来检测碰撞效果还可以,当然,AABB这种bounding box是将物体用一个立方体或长方体包围起来,如果物体的形状很不规则,那么使用bounding box来检测碰撞可能是不够精细的,比如下面这个例子:

动画2

示例中绿色立方体还没撞到蓝色锥体,但是bounding box已经检测出碰撞

所以,利用bounding box来检测物体是否相交是大体可行的

4. Cannon.js

Cannon.js是一个3d物理引擎,它能实现常见的碰撞检测,各种体形,接触,摩擦和约束功能

这里笔者想借助物理引擎来实现碰撞检测,当然,其他的物理引擎(如,Ammo.js,Oimo.js等)也是可以的

使用Cannon.js进行两个Cube的碰撞检测示例如下:

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    html,
    body,
    canvas {
      height: 100%;
      width: 100%;
      margin: 0;
    }
  </style>

</head>

<body>
  <canvas id="canvas"></canvas>

  <script type="importmap">
		{
			"imports": {
				"three": "https://unpkg.com/three/build/three.module.js",
				"three/addons/": "https://unpkg.com/three/examples/jsm/"
			}
		}
	</script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js"></script>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import Stats from 'three/addons/libs/stats.module.js'

    const scene = new THREE.Scene();
    const world = new CANNON.World()

    // 创建性能监视器
    let stats = new Stats();
    // 将监视器添加到页面中
    document.body.appendChild(stats.domElement)

    const canvas = document.querySelector('#canvas');
    const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
    camera.position.set(0, 0, 10);

    // 添加环境光
    const ambient = new THREE.AmbientLight("#FFFFFF");
    ambient.intensity = 5;
    scene.add(ambient);
    // 添加平行光
    const directionalLight = new THREE.DirectionalLight("#FFFFFF");
    directionalLight.position.set(0, 0, 0);
    directionalLight.intensity = 16;
    scene.add(directionalLight);

    // 创建第一个Cube的Three.js模型
    const cubeGeometry1 = new THREE.BoxGeometry(1, 1, 1);
    const cubeMaterial1 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
    const cube1 = new THREE.Mesh(cubeGeometry1, cubeMaterial1);
    scene.add(cube1);

    // 创建第一个Cube的Cannon.js刚体
    const cubeShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
    const cubeBody1 = new CANNON.Body({ mass: 1, shape: cubeShape1 });
    cubeBody1.position.set(1, 0, 0);
    world.addBody(cubeBody1);

    // 创建第二个Cube的Three.js模型
    const cubeGeometry2 = new THREE.BoxGeometry(1, 1, 1);
    const cubeMaterial2 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube2 = new THREE.Mesh(cubeGeometry2, cubeMaterial2);
    scene.add(cube2);

    // 创建第二个Cube的Cannon.js刚体
    const cubeShape2 = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
    const cubeBody2 = new CANNON.Body({ mass: 1, shape: cubeShape2 });
    cubeBody2.position.set(-1, 0, 0);
    world.addBody(cubeBody2);

    // 监听碰撞事件
    cubeBody2.addEventListener("collide", function (e) {
      cube2.material.color.set(0xff0000);
    });

    const renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector('#canvas'),
      antialias: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight, false)

    const controls = new OrbitControls(camera, renderer.domElement);

    function animate() {
      // 更新帧数
      stats.update()

      world.step(1 / 60);

      cubeBody1.position.x -= 0.02;

      // 更新Three.js模型的位置
      cube1.position.copy(cubeBody1.position);
      cube1.quaternion.copy(cubeBody1.quaternion);
      cube2.position.copy(cubeBody2.position);
      cube2.quaternion.copy(cubeBody2.quaternion);

      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }

    animate();
  </script>
</body>

</html>

动画3

至于精确性呢,使用Cannon.js也是不错的,示例如下:

动画4

看上去,使用Cannon.js的效果是相当不错的,在追求效果的情况下使用物理引擎是不错的选择,当然,增加的编码成本、计算开销也是不少

5. 参考资料

[1] Raycaster – three.js docs (three3d.cn)

[2] Box3 – three.js docs (threejs.org)

[3] schteppe/cannon.js: A lightweight 3D physics engine written in JavaScript. (github.com)

[4] Three.js - 物体碰撞检测(二十六) - 掘金 (juejin.cn)

[5] Three.js 进阶之旅:物理效果-碰撞和声音 💥 - 掘金 (juejin.cn)

[6] pmndrs/cannon-es: 💣 A lightweight 3D physics engine written in JavaScript. (github.com)

[7] Cannon.js -- 3d物理引擎_cannon-es_acqui~Zhang的博客-CSDN博客

与Three.js中实现碰撞检测相似的内容:

Three.js中实现碰撞检测

本文就如何在Three.js中进行碰撞检测进行记述

Three.js中实现对InstanceMesh的碰撞检测

1. 概述 之前的文章提到,在Three.js中使用InstanceMesh来实现性能优化,可以实现单个Mesh的拾取功能 那,能不能实现碰撞检测呢?肯定是可以的,不过Three.js中并没有直接的API可以实现对InstanceMesh的碰撞检测,需要手动实现 回顾本文的描述的Three.js的场

Three.js中实现一个OBBHelper

本文参考Box3Helper源码,并写出一个OBBHelper

Three.js使用InstancedMesh实现性能优化

本文记述在three.js中使用InstancedMesh来实现绘制大量几何体的性能优化

第135篇:Three.js基础入门

好家伙,这东西太帅了,我要学会 先放张帅图(都是用three.js做出来的,这我学习动力直接拉满) 还有另外一个 Junni is... 帧数太高,录不了 开始学习 官方文档 1.Three.js是什么? Three.js是一款运行在浏览器中的 3D 引擎(基于WebGL的API的封装),你可以用它

three.js高性能渲染室外场景

大家好,本文在相关文章的基础上,使用three.js渲染了高性能的室外场景,在移动端也有较好的性能,并给出了代码,分析了关键点,感谢大家~ 关键词:three.js、Web3D、WebGL、室外场景、Instanced Draw、大场景、LOD、Frustum Cull、优化、开源 代码:Githu

three.js实现相机碰撞,相机不穿墙壁、物体

大家好,本文实现了相机碰撞检测,使相机不穿墙壁、物体,并给出了思路和代码,感谢大家~ 关键词:数字孪生、three.js、Web3D、WebGL、相机碰撞、游戏相机 我正在承接Web3D数字孪生项目,具体介绍可看承接各种Web3D业务 目录实现原理参考资料 实现前: 移动第三人称相机时,相机可能会穿

如何使用webgl(three.js)实现煤矿隧道、井下人员定位、掘进面、纵采面可视化解决方案——第十九课(一)

three.js、webgl、3D煤矿隧道、三维井下人员定位、掘进面三维可视化、纵采面可视化、采集面可视化展示、设备检测、数字孪生、物联网3D、3d建筑、3d库房,bim管理系统

Three.js的基础使用

本文描述Three.js的基础使用与搭建一个场景

Three.js实现可透视的水面效果

本文描述使用Three.js实现可透视的水面效果