介绍
物理是WebGL可以添加到项目体验中最酷的功能之一。人们喜欢真实物理感的物体,看到它们碰撞、倒塌、坠落和弹跳,就像我的作品集一样: https: //bruno-simon.com/
有很多方法可以将物理功能添加到您的项目中,这取决于您想要实现的目标。您可以使用一些数学和解决方案(例如Raycaster)来创建自己的物理学
理论
这个想法很简单。我们将创建一个物理世界。这个物理世界是纯理论的。在这个物理世界上,东西会产生掉落、碰撞、摩擦、滑动等等交互。
当我们创建一个 Three.js 网格时,我们还将在物理世界中创建该网格的一个物理版本。如果我们在 Three.js 中创建一个 Box,我们也会在物理世界中创建一个Box框。
然后,在每一帧上,在渲染任何东西之前,我们告诉物理世界进行自我更新;我们获取物理对象的坐标(位置和旋转)并将它们应用于相应的 Three.js 网格。
就是这么简单的原理。这里最困难的是将我们的代码组织成一个合理的结构。这是一个完全和原本文件路径分开的路径部分。每个开发人员都会有自己的习惯,这也取决于你想做什么以及你想把这个物理世界变得多复杂。
首先,我们将简单地创建球体和盒子。
物理功能依赖库
物理功能有多个可用的库。首先,您必须决定是需要 3D 库还是 2D 库。虽然您可能认为它必须是一个 3D 库,因为 Three.js 完全是关于 3D 的,但您可能错了。2D 库通常性能更高,如果您可以总结 2D 碰撞的物理经验,则最好使用 2D 库。
举一个例子是如果你想创建一个类似⚾️弹球游戏。球可以在墙上碰撞和弹跳,您就可以使用 2D 库将所有东西投影到二维平面上。您可以将球设计成物理世界中的圆圈,而墙壁是简单的矩形。事实上,这么做您将无法通过击球底部来使球跳过其他球。
像这样完成的项目的一个很好的例子是Merci Michel的Ouigo Let’s play。他们使用了 2D 物理库,因为每个碰撞和动画都可以在 2D 空间中表示。
3D物理
对于 3D 物理,主要有三个库:
ammo.js
cannon.js
Oimo.js
2D物理
对于 2D 物理,有很多库,但这里是最流行的:
matter.js
P2.js
planck.js
Box2D.js
我们不会在本课中使用 2D 库,但 2D 库代码与 3D 库代码非常相似。主要区别在于您必须更新的轴。
已经有尝试将 Three.js 与Physijs等库结合起来的解决方案。尽管如此,我们不会使用这些已经做好封装的现成解决方案,我们要手动结合物理库来获得更好的学习体验并更好地理解内部运行的逻辑。
虽然 Ammo.js 是最常用的库,尤其是在 Three.js中,正如您在示例中看到的那样,我们将选择 Cannon.js。这个库在我们的项目中实现起来更舒服,也更容易使用。
导入 Cannon.js
要将 Cannon.js
添加到我们的项目中,我们首先需要添加依赖项。
在您的终端的项目文件夹中,运行此命令npm install --save cannon
。
我们现在可以使用经典的 JavaScript 在我们的 JavaScript 中import
导入 Cannon.js :
import CANNON from 'cannon'
我们需要的一切都在CANNON
变量中可用。
设置
我们的启动器由平面上的一个球体组成,并且出于美学原因已经启用了阴影。
![](https://img-blog.csdnimg.cn/img_convert/d2b9be161e4b8f7f44b678e792b624da.png)
基础
世界
首先,我们需要创建一个 Cannon.js世界:
/**
* Physics
*/
const world = new CANNON.World()
现在我们获得了一个,感觉在没有重力漂浮在太空中的 WebGL 体验感,让我们增加重力脚踏实地。您可以使用Cannon.js Vec3 的 gravity
属性更改重力。
**Cannon.js **Vec3就像 Three.js Vector3一样。它也有**x**
、**y**
和**z**
属性,还有一个**set(...)**
方法:
world.gravity.set(0, - 9.82, 0)
我们把第二个参数值 改为 - 9.82
是因为,- 9.82
它是地球上的重力常数,但如果您想让物体下落得更慢或者如果您的场景发生在火星上,您可以使用其他重力值。
目的
因为我们的场景中已经有了一个球体,所以让我们在 Cannon.js World中也创建一个球体。
为此,我们必须创建一个Body。Body是会掉落的并与其他物体碰撞的。
在我们创建一个Body之前,我们必须决定一个形状。有许多可用的基本形状,如Box、Cylinder、Plane等。我们将选择一个与 Three.js 球体具有相同半径的Sphere :
const sphereShape = new CANNON.Sphere(0.5)
然后我们可以创建我们的body并指定质量和位置:
const sphereBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: sphereShape
})
最后,我们可以将Body 通过addBody(...)
添加到世界中:
world.addBody(sphereBody)
现在页面里什么都没有发生,因为我们仍然需要更新我们的 Cannon.js 世界并相应地更新我们的 Three.js 球体。
更新 Cannon.js 世界和 Three.js 场景
要更新我们的world世界,我们必须使用step(...)
. 该方法底层的代码很难理解,我们不会在本课中对其进行解释,但您可以在本文中找到更多相关信息。
要让它工作,您必须提供一个固定的时间步长、自上一步以来经过了多少时间,以及世界world可以应用多少次迭代来赶上潜在的延迟。
我们不会解释什么是时间步长,但我们希望体验以 60fps 的速度运行,所以我们将使用1 / 60来表示. 别担心,在帧率更高和更低的设备上,体验将以相同的速度运行。
迭代次数由你决定,但体验是否流畅就没那么重要了。
对于三角洲时间,它有点复杂。我们需要计算自上一帧以来经过了多少时间。不要使用Clock类中的getDelta()
方法。你不会得到预期的结果,而且你会搞乱类的内部逻辑。
为了获得正确的增量时间,我们需要从前一帧elapsedTime
减去当前帧elapsedTime
获得:
const clock = new THREE.Clock()
let oldElapsedTime = 0
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - oldElapsedTime
oldElapsedTime = elapsedTime
// ...
}
我们终于可以更新我们的世界了:
const tick = () =>
{
// ...
// Update physics
world.step(1 / 60, deltaTime, 3)
}
似乎没有任何东西在移动。其实现实是我们的sphereBody
正在无限的堕入深渊,只是因为相机一直跟着物体坠落所以你难以发现,你可以通过在更新world世界后记录它的位置来看到:
world.step(1 / 60, deltaTime, 3)
console.log(sphereBody.position.y)
我们现在需要做的是使用sphereBody
坐标更新我们的sphere
。 Three.js 有两种方法可以做到这一点。您可以单独更新每个position
属性:
sphere.position.x = sphereBody.position.x
sphere.position.y = sphereBody.position.y
sphere.position.z = sphereBody.position.z
或者您可以使用以下方法将所有属性作为一个复制copy(...)
:
sphere.position.copy(sphereBody.position)
copy(...)
在许多类中可用,例如Vector2、Vector3、Euler、Quaternion,甚至类如Material、Object3D、Geometry等。
![tutieshi_640x301_2s.gif](https://img-blog.csdnimg.cn/img_convert/9f7a935bed6b841af3f1f7725ddffa62.gif)
你最终应该看到你的项目中球体正在自由落体。问题是我们的球体似乎从地板上掉了下来。这是因为该地板存在于 Three.js 场景中,但不存在于 Cannon.js 世界中。
我们可以使用Plane形状简单地添加一个新的Body,但我们不希望我们的地板受到重力影响而掉落。换句话说,我们希望我们的地板是静态的。要使Body静态,请将其设置为:mass = 0
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
world.addBody(floorBody)
![tutieshi_640x303_2s.gif](https://img-blog.csdnimg.cn/img_convert/0cbb7d5cc0fc69d01fe93354feeb1a9f.gif)
如您所见,这次我们的做法大不相同。我们创建了一个没有参数的Body ,然后我们设置了这些参数。结果是一样的,我们这样做的唯一原因是为了上课讲解。一件有趣的事情是您可以创建一个由多个Shapes组成的Body。它对于复杂但坚固的物体很有用。
您应该看到球体朝一个方向(可能朝向相机)跳跃。这不是预期的结果。原因是我们的地板plane默认正对着相机。我们需要像在 Three.js 中旋转地板一样旋转它让他转到离开相机的区域。
使用 Cannon.js 进行旋转比使用 Three.js 稍微困难一些,因为您必须使用Quaternion来实现。有多种旋转Body的方法,但必须使用其quaternion
属性。我们将使用setFromAxisAngle(...)
方法旋转body.
第一个参数是一个轴。您可以将其想象成穿过身体的一根线。第二个参数是角度。这是你围绕这条线旋转身体的角度。
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(- 1, 0, 0), Math.PI * 0.5)
![tutieshi_640x400_1s.gif](https://img-blog.csdnimg.cn/img_convert/900bb504fd19aa3ad8a767518ae59ad4.gif)
我们将轴设置为负轴(相对于相机的左侧)穿过身体的线,并将x
角度设置为(四分之一圆)。Math.PI * 0.5
您现在应该看到球体下落然后停在地板上。
我们不需要用 Cannon.js 地板更新 Three.js 地板,因为这个对象不会再移动了。
如您所见,球落地后基本不会弹跳。这是默认行为,我们可以使用Material(不是 Three.js 中的 Material)和ContactMaterial来让它变的富有弹性。
材料只是一个参考。您可以给它起一个名字并将它与一个Body相关联。然后为场景中的每种材质创建一个材质。
如果场景中有多种材质,假设一种木料材质用于地板,一种金属材质用于球。然后,您应该创建各种材质并为它们命名,例如'concrete'
和'plastic'
。
(假设你世界里的一切都是塑料材质制成的。在这种情况下,您只需创建一种材料并将其命名为'default
’即可。)
你可以给他们互相关联到'ground'
和'ball'
中。尽管如此,如果您想对墙壁和立方体等其他对象使用相同的材质,都名为'ground'
即可.
在创建球体和地板之前,创建这两个材质:
const concreteMaterial = new CANNON.Material('concrete')
const plasticMaterial = new CANNON.Material('plastic')
现在我们有了Material,我们必须创建一个ContactMaterial。它是两种材质的组合,用来关联两种材料并模拟
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)