基于three.js实现第一人称的碰撞检测

简介

通过three.js我们可以在网站中实现比较不错的3D效果,但three.js仅仅只是一个渲染库,如果想要做碰撞检测的话,通常情况会加上物理系统。
但今天,我想聊的是通过three.js的内置模块Octree八叉树来实现第一人称的碰撞效果。

咱们先来看看实现的效果吧,效果如下图:

demo4.gif

还感兴趣的话,可以进入示例Demo里面体验一下,需注意模型资源有点大,加载需要等待一段时间。好了,现在咱们就来聊聊一些实现细节。

Octree-八叉树

八叉树碰撞的检测原理我也不怎么清楚,感兴趣的可以自行百度搜索了解。
我们主要是运用three.js封装好的内置Octree模块,使用该模块只需要通过如下引入方式即可:

import { Octree } from 'three/examples/jsm/math/Octree';

this.octree = new Octree();

引入该模块后,我们可以通过fromGraphNode为需要的场景构建节点,通过构建好的节点我们就可以实现碰撞等操作,代码很简单,如下:

const model = this.resource.models.find(item => item.userData.name === 'modular_dungeon') as GLTF;  // 这是我加载的一个模型,不限于任何方式,只需要获取加载好的模型即可
this.octree.fromGraphNode(model.scene);  // 通过Octree对象来构建节点
this.scene.add(model.scene);  // 将模型添加到场景中

通过以上的简单几个步骤,我们就为碰撞检测的前提条件做好了准备,现在我们需要一个碰撞对象。
在three.js中,我们可以通过添加一个Capsule对象来实现碰撞检测。

Capsule-胶囊体

为什么要使用Capsule对象呢? 因为,在Octree中提供了Capsule对象碰撞的方法,让我们可以直接使用来更容易的实现碰撞检测。

import { Capsule } from 'three/examples/jsm/math/Capsule';

通过以上方式我们引入一个Capsule对象,注意该对象并不是一个几何体。我的意思是,它并不会在场景中存在,它仅仅用于我们碰撞检测的一个数学对象。

如果你想知道这个对象的实际样子的话,可以引入对应的CapsuleGeometry来看一看,大致如下:

image.png

好了,现在一切都准备好了,我们开始进行我们的核心问题之一——碰撞检测。

碰撞检测

Octree对象中,我们可以通过capsuleIntersect方法来捕获Capsule胶囊体与所构建了八叉树节点的场景是否进行了碰撞,检测方式如下:

const result = world.hall.octree.capsuleIntersect(this.capsule);

没错,非常非常非常的简单,只需要一个方法我们就知道了Capsule对象是否与场景进行了碰撞,当发生了碰撞后将会把碰撞的信息通过返回值返回给我们,上面我使用了result属性来存储。

现在,我们来说一说这个result属性吧,该属性是一个对象,一共包含了两个值,分别是:

  • depth: 碰撞的深度,可以理解为物体和场景中相机的比例
  • normal:碰撞的法线向量,可以理解为碰撞的方向

或许有一点不大明白,我尝试用一张拙劣的画图来表示一下:

image.png

知道了这些信息后,我们就可以很好很简单的处理碰撞的逻辑了,我们只需要如下操作:

Capsule对象与场景物体碰撞后,将depthnormal法线向量相乘,得到一个新的数值。
这个数值就是我们需要将Capsule对象偏移的值,偏移该值后Capsule对象也就不再与场景物体相碰撞了。

const result = world.hall.octree.capsuleIntersect(this.capsule);

if (result) {
  const { normal, depth } = result;
  this.capsule.translate(normal.multiplyScalar(depth));
}

上面的translate方法接收一个Vector3对象,表示想较与当前的位置,进行一个偏移。

同步

注意,我们现在进行的一切操作,在场景中都是得不到体现的!
尽管,我们可能会在不断的移动逻辑中通过translate修改Capsule的位置,又通过capsuleIntersect检测来修复Capsule的位置,但这一切都只是一个数学上的运算。

所以,我们需要将Capsule对象的信息同步到场景中的物体上,可以是一个简单的几何体、也可以是一个模型、当然也可以是拍摄的相机。

PointerLockControls控件

例如,这里我介绍PointerLockControls控件,通过该控件可以比较方便实现第一人称的一个操作方式,使用方式如下:

import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls';
document.addEventListener('mouseup', () => {
  if (!this.controls.isLocked) {
    this.controls.lock();  // 执行该方法后,鼠标将无法控制,转而控制摄像机的镜头,实现第一人称的操作
  }
});

通过PointerLockControls控件再配合Capsule对象来同步摄像机的位置,我们就可以比较容易的实现一个第一人称的碰撞逻辑了。

结束

篇幅可能有点长了,所以更多一些没有用的细节不会再去介绍了,若有兴趣可以参考源码仓库
参考:three.js官方fps案例

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYUsuWEw' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片