物理引擎与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是一个匀速增长的值
但这仅仅是该该物体的简单运动,遇到一堵墙它还是会穿过去。如果要考虑多个物体之间的相互关系,那将是非常麻烦的事情,需要考虑众多的边界条件。物理引擎就是用来模拟现实世界里的重力、摩擦力、碰撞等过程,我们只要将小球、墙体、地面...告诉物理引擎,至于它们之间是如何相互作用的,物理引擎会帮我们计算好一切。
物理引擎的非常多中,简单的可以分为2D和3D的,three.js构建的是3D场景,自然需要3D的物理引擎,cannon.js就是这样可用于three.js的3D物理引擎。
基本原理
“灵魂出窍”用来比喻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中的物体,就能实现非常逼真的运动效果。