Cannon-es.js基础入门:3D 物理碰撞效果
本文目录
- 前言
- 效果预览
- 1、前置准备
- 1.1 项目依赖安装
- 1.2 代码
- 1.3 效果
- 2、刚体碰撞效果
- 2.1 代码
- 2.1.1 效果
- 2.2 碰撞组
- 2.2.1 collisionFilterGroup
- 2.2.2 collisionFilterMask
- 2.2.3 代码
- 2.3 效果
- 2.4 碰撞组修改
- 2.4.1 效果
前言
在现代网页开发领域,创建具有丰富交互性和逼真物理效果的3D场景已成为一项热门技术。Three.js 作为 JavaScript 编写的轻量级 3D 库,凭借其强大的渲染能力和灵活的 API,成为了开发复杂3D应用的首选。然而,仅凭 Three.js 难以处理复杂的物理模拟,如碰撞检测、重力影响等。这时,Cannon-ES 作为专为WebGL和Three.js设计的物理引擎,便成为了实现高级物理效果的理想伙伴。
本文旨在通过详实的步骤和代码示例,指导读者基于vue3如何将 Cannon-ES 与 Three.js 结合起来,实现逼真的刚体碰撞效果。我们将从项目的前置准备开始,包括必要的依赖安装、基本代码框架搭建及初步效果展示。随后,我们将深入探讨刚体碰撞的实现细节,包括如何通过碰撞组和碰撞过滤器来精细控制碰撞行为,以及如何通过调整物理参数来优化碰撞效果。通过本文的学习,读者将能够掌握在 Three.js 场景中集成 Cannon-ES 物理引擎,创建出既美观又具备物理真实感的3D应用。
效果预览
1、前置准备
1.1 项目依赖安装
运行命令vue create cannon-es-demo
安装vue3
:
在项目根目录下运行命令:yarn add three -S
,安装three.js
:
运行命令yarn add cannon-es -S
,安装cannon-es
:
可以看到我们已经安装的依赖:
接下来我们将在这项目下去演示cannon-es.js
。
1.2 代码
在components
文件夹下新建CannonDemo.vue
写入如下准备代码:
<template><canvas ref="cannonDemo" class="cannonDemo"></canvas>
</template><script setup>
import { onMounted, ref } from "vue"
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const cannonDemo = ref('null')onMounted(() => {const cannonDemoDomWidth = cannonDemo.value.offsetWidthconst cannonDemoDomHeight = cannonDemo.value.offsetHeight// 创建场景const scene = new THREE.Scene// 创建相机const camera = new THREE.PerspectiveCamera( // 透视相机45, // 视角 角度数cannonDemoDomWidth / cannonDemoDomHeight, // 宽高比 占据屏幕0.1, // 近平面(相机最近能看到物体)1000, // 远平面(相机最远能看到物体))camera.position.set(0, 2, 30)// 创建渲染器const renderer = new THREE.WebGLRenderer({antialias: true, // 抗锯齿canvas: cannonDemo.value})// 设置设备像素比renderer.setPixelRatio(window.devicePixelRatio)// 设置画布尺寸renderer.setSize(cannonDemoDomWidth, cannonDemoDomHeight)// 小球const sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16),new THREE.MeshBasicMaterial({ color: 0xff0000 }))sphere.position.set(0, 1, 0);// 正方体const box = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2),new THREE.MeshBasicMaterial({ color: 0x00ff00 }));box.position.set(-3, 1, 0);// 圆柱体const cylinder = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 2, 32),new THREE.MeshBasicMaterial({ color: 0x0066ff }));cylinder.position.set(3, 1, 0);// 地面const plane = new THREE.Mesh(new THREE.BoxGeometry(30, 0.2, 30),new THREE.MeshBasicMaterial())// 添加到场景中scene.add(sphere, plane, box, cylinder)// 创建物理世界const physicsWorld = new CANNON.World()// 设置y轴重力physicsWorld.gravity.set(0, -9.82, 0)// 创建物理材料const groundMaterial = new CANNON.Material('groundMaterial')groundMaterial.friction = 0.1// 创建物理地面const groundBody = new CANNON.Body({mass: 0, // 为0表示地面不受重力影响shape: new CANNON.Box(new CANNON.Vec3(15, 0.1, 15)), // material: groundMaterial,})physicsWorld.addBody(groundBody)// 设置小球材质const sphereMaterial = new CANNON.Material('sphereMaterial')sphereMaterial.friction = 0.1; // 摩擦系数// 创建物理小球const sphereBody = new CANNON.Body({mass: 1, // 质量设为1// position: new CANNON.Vec3(0,5,0), // 跟threejs中的小球位置一样position: sphere.position, // 或者这种写法也可以,直接将threejs中的小球位置拿过来material: sphereMaterial,})const sphereShape = new CANNON.Sphere(1) // 创建球刚体,并且传递半径跟threejs中的小球半径一致sphereBody.addShape(sphereShape) // 刚体添加形状也可这种写法// 设置正方体材质const boxMaterial = new CANNON.Material('boxMaterial')boxMaterial.friction = 0; // 摩擦系数// 创建正方体刚体const boxBody = new CANNON.Body({mass: 1, // 质量设为1position: box.position, // 或者这种写法也可以,直接将threejs中的正方体位置拿过来shape: new CANNON.Box(new CANNON.Vec3(1, 1, 1)), // 创建正方体刚体material: boxMaterial})// 设置圆柱体体材质const cylinderMaterial = new CANNON.Material('cylinderMaterial')cylinderMaterial.friction = 0.1; // 摩擦系数// 创建圆柱体刚体const cylinderBody = new CANNON.Body({mass: 1, // 质量设为1position: cylinder.position, // 或者这种写法也可以,直接将threejs中的圆柱体位置拿过来shape: new CANNON.Cylinder(1, 1, 2, 32), // 创建圆柱体刚体material: cylinderMaterial})physicsWorld.addBody(sphereBody)physicsWorld.addBody(boxBody)physicsWorld.addBody(cylinderBody)const updatePhysic = () => { // 因为这是实时更新的,所以需要放到渲染循环动画animate函数中physicsWorld.step(1 / 60)sphere.position.copy(sphereBody.position) // 将物理刚体小球的位置赋值给threejs的小球sphere.quaternion.copy(sphereBody.quaternion) // 将物理刚体小球的旋转赋值给threejs的小球box.position.copy(boxBody.position) // 将物理刚体正方体的位置赋值给threejs的正方体box.quaternion.copy(boxBody.quaternion) // 将物理刚体正方体的旋转 赋值给threejs的正方体cylinder.position.copy(cylinderBody.position) // 将物理刚体圆柱体的位置赋值给threejs的圆柱体cylinder.quaternion.copy(cylinderBody.quaternion) // 将物理刚体圆柱体的旋转 赋值给threejs的圆柱体}// 控制器const control = new OrbitControls(camera, renderer.domElement)// 开启阻尼惯性,默认值为0.05control.enableDamping = true// 渲染循环动画function animate() {// 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)requestAnimationFrame(animate)updatePhysic()// 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用control.update()renderer.render(scene, camera)}// 执行动画animate()
})</script>
<style scoped>
.cannonDemo {width: 100vw;height: 100vh;
}
</style>
- 我们设置3个
cannon-es.js
刚体分别对应绑定three.js
的物体,并使其的位置实时更新到我们three.js
的物体。 - 我们还分别设置了他们的材质里的摩擦属性。
1.3 效果
可以看到我们在场景中添加了三个物体,分别是正方体,球和圆柱体,并且分别赋值了cannon-es
的刚体。
2、刚体碰撞效果
2.1 代码
我们给正方体一个向x
轴正方向的速度:
boxBody.velocity.set(10,0,0)
,可以看到效果如下:
2.1.1 效果
可以看到我们圆柱体有个0.1的摩擦系数,并且当正方体撞过来时不会被穿透。
2.2 碰撞组
在cannon-es
中,collisionFilterGroup
和collisionFilterMask
是用于控制物理世界中实体之间碰撞行为的两个关键属性。这两个属性允许开发者通过定义碰撞过滤规则来精细控制哪些实体之间应该发生碰撞,哪些则不应该。
2.2.1 collisionFilterGroup
定义:collisionFilterGroup
用于指定一个实体所属的碰撞组。它是一个整数类型的属性,用于在逻辑上将实体分组。
作用:通过为不同的实体设置不同的碰撞组,可以定义哪些组的实体之间应该发生碰撞。
2.2.2 collisionFilterMask
定义:collisionFilterMask
用于指定一个实体可以与哪些碰撞组的实体发生碰撞。它同样是一个整数类型的属性,其位模式(bit pattern
)决定了哪些组的实体可以与当前实体碰撞。
作用:通过设置碰撞掩码,可以进一步细化哪些组的实体应该与当前实体碰撞,即使它们属于同一个碰撞组。
默认值:这个属性的默认值也可能因版本而异,但通常可能是一个特定的整数
2.2.3 代码
如果我们想圆柱体是可以被穿透的,它不受刚体的影响。
// 设置碰撞组 数值要用2 的幂const GROUP1 = 1const GROUP2 = 2const GROUP3 = 8const GROUP4 = 16
-
给物理地面设置:
collisionFilterGroup: GROUP1,collisionFilterMask: GROUP2 | GROUP3 | GROUP4
-
给物理小球设置:
collisionFilterGroup: GROUP2,collisionFilterMask: GROUP1 | GROUP3
-
给物理正方体设置:
collisionFilterGroup: GROUP3,collisionFilterMask: GROUP1 | GROUP2
-
给物理圆柱体设置:
collisionFilterGroup: GROUP4,collisionFilterMask: GROUP1
2.3 效果
可以看到圆柱体就不受其他两个物体碰撞的影响。
2.4 碰撞组修改
我们如果想立方体也能跟圆柱体有碰撞的话,我们需要把圆柱体的所属碰撞组给到立方体,如下所示:collisionFilterMask: GROUP1 | GROUP2 | GROUP4
并且圆柱体也要写入:collisionFilterMask: GROUP1 | GROUP3
2.4.1 效果
在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。