提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
课程回顾
物理库
3D
Ammo.js
Cannon.js
Oimo.js
2D
Matter.js
P2.js
Planck.js
Box2D.js
补充:一些看似3D的效果实际使用2D库来实现的
物理 和 three.js的结合 概念补充
假象在当前物体或场景外, 有一个看不见的物理量世界 ,那么遵循物理世界规则,若有一个小球在
半空中掉落,碰触到地面,然后在滚落到旁边。这个时候 three.js 展示的每一帧都要获取这个物理量
世界的位置信息,然后更新到three.js中,实现展示,因此需要资料库
这里采用cannon.js
1.下载,引入
npm install --save cannon
import CANNON from 'cannon'
2.创建一个物理世界
const world = new CANNON.world()
world.gravity(0,-9.82,0) 添加重力 9.82 重力常量
vec3 文档 https://schteppe.github.io/cannon.js/docs/classes/Vec3.html
vec3 和 vector 区别
vec3 是cannon.js中局部位置
vector是three.js
和three.js一样 想要创建网格 ,但是网格必须有个几何体
同样cannon.js 想要创建身体 ,首先创建一个形状
3.如何通过物理方式动起来
在物理世界有了一个和three.js 一样的物理,但是要更新在tick中
首先应该确保在跟新步长时候,保证精度(准确性)
保证上一个更新帧率 和这次跟新帧率没有问题
然后将物理世界的球的位置 设置到 three.js中
4.球会一直往下,再来个地板
此时会发现,地板是正对着摄像头,因为在three.js中我们旋转地板,那么在物理世界同样
cannon.js中的旋转是使用四元数(上方文档)
5.球应该弹起来
需要材料,创建两种材料对应 球和地板
例如:混泥土材料 (当然由于球和地板设置相同材质 ,所以 简化代码)
塑料材料
同时创建接触材料,就是混泥土遇到塑料材料情况
new CANNON.ContactMaterial()
放到world中,同时在物理世界中的物体也要应用上 这和three.js相似
也不用一个一个设置,统一设置应用
world.defaultContactMaterial = defaultContactMaterial
6. 对物体施力的方法
applyforce-从空间的指定点施加一个力(不一定是物体表面)比如风,在多米诺骨牌上轻轻一推,或者在愤怒的小鸟上施加一个很大的力
applylmpulse类似applyforce,但不是增加力,而是增加速度
applyLocalForce-与applyForce相同,但坐标是主体的局部(e,e,e将是主体的中心
applyLocallmpulse-与applylmpulse相同,但坐标是主体的局部
7. 将球写成一个函数,这样方便调用
球体之间的碰撞 显示不出,但是不同物体 应当要旋转 quaternion
8.GPU要测试每一个物体 之间是否碰撞 ,性能消耗 ,帧率下降
Gannon.js 有范宽阶段
例子:当一个球体向某一方向形式,反方向有一堆物体, 它不会去尽心测试是否该物体会碰撞反方向的物体,减少消耗
NaiveBroadphase-tests every Bodies against every other Bodies
GridBroadphase -quadrilles the world and only tests Bodies against other // 网格范宽
Bodies in the same grid box or the neighbors' grid boxes
SAPBroadphase ((Sweep And Prune)-tests Bodies on arb!trary axes duringmultiples steps // 清除与清扫 效果更好
world.allowSleep = true
加入睡眠属性 :效果:让一些静止不动的物体,让它保持,当运动物体再次碰撞 ,在动起来
同样可以优化性能问题:解决帧率下降的问题
同时可以更改睡眠限时
比如物体静止,过一秒,好的这个物体在睡觉
9.添加声音
10.如何移出物体
11.限制条件
HingeConstraint -acts like a door hinge. (铰链约束:像门一样)
DistanceConstraint -forces the bodies to keep a distance between each other (距离约束)
LockConstraint -merges the bodies like if they were one piece (锁定约束)
PointToPointConstraint -glues the bodies to a specific point (对点约束)
12.cannonES
由于cannonJS ,不更新 ;因此在它的基础上cannonEs更新,替换掉
npm uninstall --save cannon
npm install --save cannon-es@0.15.1
13. Amon.js
- import './style.css'
- import * as THREE from 'three'
- import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
- import * as dat from 'dat.gui'
- import CANNON from 'cannon'
-
-
- /**
- * Debug
- */
- const gui = new dat.GUI()
- const debugObject = {}
- debugObject.createSphere = () =>{
- createSphere(Math.random() * 0.5,
- {
- x: (Math.random() - 0.5) * 3,
- y:3,
- z:(Math.random() - 0.5) * 3
- }
- )
- }
- debugObject.createBox = () =>{
- createBox(
- Math.random(),
- Math.random(),
- Math.random(),
- {
- x: (Math.random() - 0.5) * 3,
- y:3,
- z:(Math.random() - 0.5) * 3
- }
- )
- }
- debugObject.reset = () => {
- for(const object of objectsToUpdate){
- // remove
- object.body.removeEventListener('collide',playHitSound)
- world.removeBody(object.body)
-
- // Remove scene
- scene.remove(object.mesh)
- }
- objectsToUpdate.splice(0,objectsToUpdate.length)
- }
-
- gui.add(debugObject,'createSphere')
- gui.add(debugObject,'createBox')
- gui.add(debugObject,'reset')
-
- /**
- * Base
- */
- // Canvas
- const canvas = document.querySelector('canvas.webgl')
-
- // Scene
- const scene = new THREE.Scene()
-
- /*
- * sounds
- */
- const hitSound = new Audio('/sounds/hit.mp3')
- const playHitSound = (collisition) => {
- // 加入变量 ,判断碰撞产生的 冲击强度 ,小于一定程度不让播放
- // console.log(collisition.contact.getImpactVelocityAlongNormal())
- let impactStrength = collisition.contact.getImpactVelocityAlongNormal()
- if(impactStrength > 1.5){
- hitSound.volume = Math.random() // 让声音产生不同大小
- hitSound.currentTime = 0 // 将播放时间重置0,不至于碰撞-》播放结束;碰撞-》播放结束
- hitSound.play()
- }
-
- }
-
- /**
- * Textures
- */
- const textureLoader = new THREE.TextureLoader()
- const cubeTextureLoader = new THREE.CubeTextureLoader()
-
- const environmentMapTexture = cubeTextureLoader.load([
- '/textures/environmentMaps/0/px.png',
- '/textures/environmentMaps/0/nx.png',
- '/textures/environmentMaps/0/py.png',
- '/textures/environmentMaps/0/ny.png',
- '/textures/environmentMaps/0/pz.png',
- '/textures/environmentMaps/0/nz.png'
- ])
-
- /*
- Physice
- */
-
- // World
- const world = new CANNON.World()
- world.broadphase = new CANNON.SAPBroadphase(world) // 优化,帧率下降;通过范宽阶段,减少GPU为计算每个物体碰撞的计算量
- world.allowSleep = true
- world.gravity.set(0,-9.82,0)
-
- // materials
- const defaultMaterial = new CANNON.Material('default') // 中间的参数其实只是命名 , 球和地板都用这个材质
- const defaultContactMaterial = new CANNON.ContactMaterial( // 接触材质
- defaultMaterial,
- defaultMaterial,
- {
- friction:0.1, // 摩擦系数
- restitution:0.7 //
- }
- )
- world.addContactMaterial(defaultContactMaterial) // 将材质添加物理世界
- world.defaultContactMaterial = defaultContactMaterial // 统一 设置材质(当然也可以在body中单独设置)
-
-
- /*
- Floor
- */
- const floorShape = new CANNON.Plane()
- const floorBody = new CANNON.Body()
- floorBody.mass = 0
- floorBody.addShape(floorShape);
- // 设置四元数 旋转
- floorBody.quaternion.setFromAxisAngle(
- new CANNON.Vec3(-1,0,0),
- Math.PI * 0.5
- )
- world.add(floorBody);
-
-
- /**
- * Floor
- */
- const floor = new THREE.Mesh(
- new THREE.PlaneBufferGeometry(10, 10),
- new THREE.MeshStandardMaterial({
- color: '#777777',
- metalness: 0.3,
- roughness: 0.4,
- envMap: environmentMapTexture
- })
- )
- floor.receiveShadow = true
- floor.rotation.x = - Math.PI * 0.5
- scene.add(floor)
-
- /**
- * Lights
- */
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.7)
- scene.add(ambientLight)
-
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.2)
- directionalLight.castShadow = true
- directionalLight.shadow.mapSize.set(1024, 1024)
- directionalLight.shadow.camera.far = 15
- directionalLight.shadow.camera.left = - 7
- directionalLight.shadow.camera.top = 7
- directionalLight.shadow.camera.right = 7
- directionalLight.shadow.camera.bottom = - 7
- directionalLight.position.set(5, 5, 5)
- scene.add(directionalLight)
-
- /**
- * Sizes
- */
- const sizes = {
- width: window.innerWidth,
- height: window.innerHeight
- }
-
- window.addEventListener('resize', () =>
- {
- // Update sizes
- sizes.width = window.innerWidth
- sizes.height = window.innerHeight
-
- // Update camera
- camera.aspect = sizes.width / sizes.height
- camera.updateProjectionMatrix()
-
- // Update renderer
- renderer.setSize(sizes.width, sizes.height)
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
- })
-
- /**
- * Camera
- */
- // Base camera
- const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
- camera.position.set(- 3, 3, 3)
- scene.add(camera)
-
- // Controls
- const controls = new OrbitControls(camera, canvas)
- controls.enableDamping = true
-
- /**
- * Renderer
- */
- const renderer = new THREE.WebGLRenderer({
- canvas: canvas
- })
- renderer.shadowMap.enabled = true
- renderer.shadowMap.type = THREE.PCFSoftShadowMap
- renderer.setSize(sizes.width, sizes.height)
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
-
- /*
- Unites
- */
- const objectsToUpdate = [];
-
- // sphere
- // 将几何体,材质放到外部,避免性能消耗,防止重复创建时,都要创建一次几何体和材质
- const sphereGeometry = new THREE.SphereBufferGeometry(1,20,20)
- const sphereMaterial = new THREE.MeshStandardMaterial({
- metalness:0.3,
- roughness:0.4,
- envMap:environmentMapTexture
- })
-
- const createSphere = (radius,position) =>{
- // Three.js mesh
- const mesh = new THREE.Mesh(
- sphereGeometry,
- sphereMaterial
- )
- mesh.scale.set(radius,radius,radius)
- mesh.castShadow = true ; // 投射阴影
- mesh.position.copy(position)
- scene.add(mesh)
-
- // CANNON.js body
- const shape = new CANNON.Sphere(radius)
- const body = new CANNON.Body({
- mass:1,
- position:new CANNON.Vec3(0,3,0),
- shape, // 这里注意大小写
- material:defaultMaterial
- })
- body.position.copy(position)
- body.addEventListener('collide',playHitSound) // 监听碰撞 collide
- world.addBody(body)
-
- // save in objects to update
- objectsToUpdate.push({
- mesh,
- body
- })
- }
- createSphere(0.5,{x:0,y:3,z:0})
-
- //box
- const boxGeometry = new THREE.BoxBufferGeometry(1,1,1) // 宽度,深度,高度
- const boxMaterial = new THREE.MeshStandardMaterial({
- metalness:0.3,
- roughness:0.4,
- envMap:environmentMapTexture
- })
-
- const createBox = (width,height,depth,position) =>{
- // Three.js mesh
- const mesh = new THREE.Mesh(
- boxGeometry,
- boxMaterial
- )
- mesh.scale.set(width,height,depth)
- mesh.castShadow = true ; // 投射阴影
- mesh.position.copy(position)
- scene.add(mesh)
-
- // CANNON.js body
- const shape = new CANNON.Box(new CANNON.Vec3(width * 0.5,height * 0.5,depth * 0.5))
- const body = new CANNON.Body({
- mass:1,
- position:new CANNON.Vec3(0,3,0),
- shape, // 这里注意大小写
- material:defaultMaterial
- })
- body.position.copy(position)
- body.addEventListener('collide',playHitSound) // 监听碰撞 collide
- world.addBody(body)
-
- // save in objects to update
- objectsToUpdate.push({
- mesh,
- body
- })
- }
-
- /**
- * Animate
- */
- const clock = new THREE.Clock()
- let oldElapsedTime = 0 // 创建变量 标识上一刻度时间
-
- const tick = () =>
- {
- const elapsedTime = clock.getElapsedTime()
- const deltaTime = elapsedTime - oldElapsedTime
- oldElapsedTime = elapsedTime
-
- // Update physics world
- world.step(1/60,deltaTime,3) // 每秒60帧率 , 上一刻度用来多少时间 ,
-
- for(const objects of objectsToUpdate){
- objects.mesh.position.copy(objects.body.position)
- objects.mesh.quaternion.copy(objects.body.quaternion)
- }
-
- // Update controls
- controls.update()
-
- // Render
- renderer.render(scene, camera)
-
- // Call tick again on the next frame
- window.requestAnimationFrame(tick)
- }
-
- tick()

1.下载,引入
npm install --save cannon
import CANNON from 'cannon'
由于cannonJS ,不更新 ;因此在它的基础上cannonEs更新,替换掉
npm uninstall --save cannon
npm install --save cannon-es@0.15.1
效果:
Physice
学习,学呗!