物理引擎cannon.js上手

4,821 阅读5分钟

物理引擎与cannon.js

Inspired by three.js and ammo.js, and driven by the fact that the web lacks a physics engine, here comes cannon.js.

不管是通过3D建模软件还是用代码建模(如three.js的内置geometries组合),我们在计算机里建立的模型都是静止的。当然我们可以通过简单的代码设置物体的位置,让其动起来,如下面的代码可以小球在x轴上左右往复移动。

spere.position.x = Math.sin(elapseTime); 
//elapseTime是一个匀速增长的值

但这仅仅是该该物体的简单运动,遇到一堵墙它还是会穿过去。如果要考虑多个物体之间的相互关系,那将是非常麻烦的事情,需要考虑众多的边界条件。物理引擎就是用来模拟现实世界里的重力、摩擦力、碰撞等过程,我们只要将小球、墙体、地面...告诉物理引擎,至于它们之间是如何相互作用的,物理引擎会帮我们计算好一切。

动画.gif

codepen演示与源代码

物理引擎的非常多中,简单的可以分为2D和3D的,three.js构建的是3D场景,自然需要3D的物理引擎,cannon.js就是这样可用于three.js的3D物理引擎。

基本原理

image.png

“灵魂出窍”用来比喻cannon.js(或者其他物理引擎)是最合适不过的。把人当做3D场景中的一个物体,有“灵魂”和“肉体”两个部分。“肉体”是简单的模型,如我们在three.js创建的mesh,如果要让mesh做很复杂的运动以及多物体间相互作用,单靠“肉体”是无法完成的,我们需要“灵魂”的指引,这个“灵魂”就是cannon.js(物体引擎),“灵魂”将一起都计算法,“肉体”只需安装“灵魂”的结构来运动即可。

使用cannon.js的过程就是在“两个世界”中游荡:(1)灵魂的世界-cannon.js的世界;(2)肉体的世界-three.js的世界。在3D场景中看到的一个物体实际上有两部分,一个是我们在three.js中创建的物体A,我们能看到,另一个是我们在cannon.js中创建的物体B,相当于“灵魂”,我们看不到B,但它包含了物理引擎计算好一切运动轨迹,这些计算结果将应用于物体A。要让B的计算结果能应用于A,B和A两物体的参数应该是完全相同的,如在three.js中创建一个半径为1的球,那在cannon.js的世界中也要创建一个半径为1的球,然后cannon.js模拟的计算结果才能正确用于three.js场景中的球。

three.js中创建一个球

const sphereG = new THREE.SphereGeometry(1, 32, 32)
const sphereM = new THREE.MeshStandardMaterial({color: 0x888888})
const mesh = new THREE.Mesh(sphereG, sphereM)
scene.add(mesh)

cannon.js中创建一个球

世界world

首先需要定义个一个cannon.js世界,这里的世界相当于three.js中的场景scene。

const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)

shape与body

然后,在这个世界中添加一个球,three.js中的球为mesh,mesh有geometry和material组成。在cannon.js中的物体我们是看不到的,所以在cannon.js中添加的物体只是记录three.js场景中物体的尺寸(形状,shape),即保证“灵魂”和“肉体”是一样大小。要进行正式世界的模拟,还需要在shape的基础上增加一些物理参数的设置,如质量、摩擦力、弹性...加上这些之后就是cannon.js中的body。所以,在three.js中scene.add(mesh),在cannon.js中为world.addBody(body)

const shape = new CANNON.Sphere(radius)
const body = new CANNON.Body({
        mass: 1,
        shape,
        position,
        material: defaultMaterial
})

world.addBody(body)

material

对于cannon.js中的一个物体(body),需要进行materil设置,不过这个material和three.js中的material的概念有所区别。

  • three.js material,指的是物体“表面”的材质,如灯笼表面的罩子,人的皮肤...
  • cannon.js material,值得是物体的“材料”属性,弹性如何、摩擦力如何...

cannon.js中material的添加过程也比three.js稍稍复杂:

  • 需要给物体(body)添加material,即定义该物体的一些物理参数
  • 需要将相互作用的两material(ContactMaterial)告诉world
//给body的material
const defaultMaterial = new CANNON.Material('default')

//给world的contactMaterial
const defalutContactMaterial = new CANNON.ContactMaterial(
    defaultMaterial,
    defaultMaterial,
    {
        friction: 0.1,
        restitution: 0.4,
    }
)
world.addContactMaterial(defalutContactMaterial)

将cannon.js计算结果给three.js

分别在three.js和cannon.js中常见了同样的东西,我们需要将cannon.js计算的结果交给three.js。这样,three.js中的物体才能真正动起来。three.js中的“肉体”和cannon.js中的“灵魂”都有position,只需要将cannon.js计算的position赋值给three.js中的物体即可。

mesh.position.x = body.position.x
mesh.position.y = body.position.y
mesh.position.z = body.position.z

//或者直接用three.js中的copy方法,更加简单
mesh.position.copy(body.position)

最后还有一部需要做,就是在动画函数中,定义step,这样每帧都会进行渲染。

let oldElapsedTime = 0
function animate() {
    const time = clock.getElapsedTime()
    let deltaTime = time - oldElapsedTime //每帧的时间间隔
    oldElapsedTime = time

    //step
    world.step(1 / 60, deltaTime, 3)
    
    //位置更新
    mesh.position.copy(body.position)
   
    requestAnimationFrame(animate)
    renderer.render(scene, camera)
}

添加平面

仅一个球还看不出来物理引擎的强大,显示的世界物体间都是有相互作用的,所以这里再添加一个地面(plane),让球与地面发生碰撞。过程和创建球是一样的,在three.js和cannon.js两个世界都添加该平面。

//Plane in Three.js
const planeG = new THREE.PlaneGeometry(100, 100)
const planeM = new THREE.MeshStandardMaterial({ color: 0x333333 })
const plane = new THREE.Mesh(planeG, planeM)
plane.rotation.x = -0.5 * Math.PI
scene.add(plane)

//Plane in Cannon.js
const planeShape = new CANNON.Plane()
const planeBody = new CANNON.Body({
    mass: 0, //质量为0,表示该物体固定
    shape: planeShape,
    material: defaultMaterial,
})
planeBody.quaternion.setFromAxisAngle(
    new CANNON.Vec3(-1, 0, 0),
    Math.PI * 0.5
)
world.addBody(planeBody)

总结

计算机模拟真实世界是一个非常复杂的过程,当利用现成的物理引擎却简单很多,我们不需要考虑物体间究竟是如何相互作用,不要要像解物体题一样去考虑...只用将场景中的东西告诉cannon.js,剩下的物理引擎来计算,然后我们将计算好的结果用于three.js中的物体,就能实现非常逼真的运动效果。