之前有做过关于Threejs基础功能的一些演示,包括绘制简单的模型,以及给这个模型添加一些动画,但是这些都还是停留在绘制的阶段,动画也是匀速移动,而非模仿自然界中的重力加速度以及阻力等效果实现的。
今天讲一个可以模拟自然界中一些物理属性的组件,这里引入一个新的组件Cannon,这是一个开源3D物理引擎,可以用来开发和模拟真实世界的物理效果,包括碰撞,重力,约束等,
Cannon.js的特点如下:
轻量级和高性能:Cannon.js被设计为一个快速而轻便的物理引擎,代码简洁且易于理解。 真实的物理模拟:Cannon.js提供了一套完整的3D物理模拟功能,包括刚体碰撞、力学模拟和约束等。这使得开发者可以模拟真实世界中的物理效果。 灵活的约束系统:Cannon.js的约束系统非常灵活,并且支持各种类型的约束,如距离约束、弹簧约束、旋转约束等。开发者可以根据需要创建各种类型的约束。 基于WebGL:Cannon.js与WebGL技术结合使用,可以轻松实现在浏览器中展示3D物理效果,并且与其他WebGL应用程序进行集成。 跨平台兼容性:Cannon.js可以在多种浏览器上运行,并且支持移动设备和桌面设备。这使得开发者可以轻松地在各种平台上开发和运行物理模拟应用程序。 下面来制作一个通过cannon来制作的一个自由落体的效果,因为传统threejs,即使做成动画效果一般也只是匀速移动,很难做出下落时有重力加速度的效果,首先还是需要用threejs绘制一个场景,在场景中去添加cannon的一些属性和方法,主要流程如下,先创建一个物理世界,在物理世界中添加一个球体和一个底面,让球落到底面,但是因为cannon中不提供具体的模型显示,所以我们需要根据物理模型来构建实体模型,并同步物理模型中的球体位置到实体模型中的物体位置。
按照threejs的步骤搭建一个3D场景,下面在物理场景中添加一个地面以及一个球体:
// 实体模型的网格小球,这里是用来对应显示物理模型下的球体位置
const geometry = new THREE.SphereGeometry(3);
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
});
this.boxmesh = new THREE.Mesh(geometry, material);
this.scene.add(this.boxmesh)
//实体地面的网格模型,这里用来显示对应物理对应的地面
const planeGeometry = new THREE.PlaneGeometry(200, 200);
const planeMaterial = new THREE.MeshLambertMaterial({
color:0x777777,
});
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.rotateX(-Math.PI / 2);
this.scene.add(planeMesh)
效果如下
下面要给这个小球添加物理属性,比如重力加速度以及落地后根据材质的不同设置回弹的系数,物理属性实际上是配置另一套物理模型,新建物理世界,在物理世界中创建小球模型和地面模型,然后将threejs中我们绘制的模型与物理世界的模型绑定起来,当物理世界的球体位置发生变化的时候,threejs场景中球体位置也随着发生变化,那么就等于是吧物理引擎的一套规则投射到threejs场景中了,下面就需要创建一套物理世界的规则:
//新建一个物理模型世界
this.world = new CANNON.World();
// 设置物理世界重力加速度,此处设置为y轴的反方向,也就是往y轴反方向存在重力
this.world.gravity.set(0, -9.8, 0);
// 新建一个物理小球:对应threejs的网格小球
const sphereMaterial = new CANNON.Material()
this.box = new CANNON.Body({
mass: 1,//碰撞体质量
material: sphereMaterial,//设置小球的材质
shape:new CANNON.Sphere(3),//设置小球的半径大小
});
this.box.position.y = 100;//设置小球的位置
this.world.addBody(this.box);//将小球添加到物理世界中
// 新建物理地面
const groundMaterial = new CANNON.Material()
const groundBody = new CANNON.Body({
mass: 0, // 质量为0,始终保持静止,不会受到力碰撞或加速度影响
shape:new CANNON.Plane(),//新建物理模型的底面
material: groundMaterial,//地面材质
});
// 改变平面默认的方向,法线默认沿着z轴,旋转到平面向上朝着y方向
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
this.world.addBody(groundBody);
创建完成后为了动态效果还需要再渲染中刷新页面:
initAnimate() {
requestAnimationFrame(this.initAnimate);
this.world.step(1/60);
// 渲染循环中,同步物理球body与网格球mesh的位置
this.boxmesh.position.copy(this.box.position);
this.renderer.render(this.scene, this.camera);
},
最终的完整代码如下:
initCarton(){
//新建一个物理模型世界
this.world = new CANNON.World();
// 设置物理世界重力加速度,此处设置为y轴的反方向,也就是往y轴反方向存在重力
this.world.gravity.set(0, -9.8, 0);
// 新建一个物理小球:对应threejs的网格小球
const sphereMaterial = new CANNON.Material()
this.box = new CANNON.Body({
mass: 1,//碰撞体质量
material: sphereMaterial,//设置小球的材质
shape:new CANNON.Sphere(3),//设置小球的半径大小
});
this.box.position.y = 100;//设置小球的位置
this.world.addBody(this.box);//将小球添加到物理世界中
// 新建物理地面
const groundMaterial = new CANNON.Material()
const groundBody = new CANNON.Body({
mass: 0, // 质量为0,始终保持静止,不会受到力碰撞或加速度影响
shape:new CANNON.Plane(),//新建物理模型的底面
material: groundMaterial,//地面材质
});
// 改变平面默认的方向,法线默认沿着z轴,旋转到平面向上朝着y方向
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
this.world.addBody(groundBody);
// 设置地面材质和小球材质之间的碰撞反弹恢复系数,也就是底面和小球的材质之间存在的反弹系数,
const contactMaterial = new CANNON.ContactMaterial(groundMaterial, sphereMaterial, {
restitution: 0.5 //反弹恢复系数
})
// 把关联的材质添加到物理世界中
this.world.addContactMaterial(contactMaterial)
// 实体模型的网格小球,这里是用来对应显示物理模型下的球体位置
const geometry = new THREE.SphereGeometry(3);
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
});
this.boxmesh = new THREE.Mesh(geometry, material);
this.scene.add(this.boxmesh)
//实体地面的网格模型,这里用来显示对应物理对应的地面
const planeGeometry = new THREE.PlaneGeometry(200, 200);
const planeMaterial = new THREE.MeshLambertMaterial({
color:0x777777,
});
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.rotateX(-Math.PI / 2);
this.scene.add(planeMesh)
},
initAnimate() {
requestAnimationFrame(this.initAnimate);
this.world.step(1/60);
// 渲染循环中,同步物理球body与网格球mesh的位置
this.boxmesh.position.copy(this.box.position);
this.renderer.render(this.scene, this.camera);
},
实现效果如下:
这里不支持上传视频,我就只能上传个图片了,如果想看动态效果可以私我,我发给你视频,或者需要源码也可以给我私信: