提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
课程回顾
物理库
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){// removeobject.body.removeEventListener('collide',playHitSound)world.removeBody(object.body)// Remove scenescene.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 sizessizes.width = window.innerWidthsizes.height = window.innerHeight// Update cameracamera.aspect = sizes.width / sizes.heightcamera.updateProjectionMatrix()// Update rendererrenderer.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 meshconst mesh = new THREE.Mesh(sphereGeometry,sphereMaterial)mesh.scale.set(radius,radius,radius)mesh.castShadow = true ; // 投射阴影mesh.position.copy(position)scene.add(mesh)// CANNON.js bodyconst 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) // 监听碰撞 collideworld.addBody(body)// save in objects to updateobjectsToUpdate.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 meshconst mesh = new THREE.Mesh(boxGeometry,boxMaterial)mesh.scale.set(width,height,depth)mesh.castShadow = true ; // 投射阴影mesh.position.copy(position)scene.add(mesh)// CANNON.js bodyconst 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) // 监听碰撞 collideworld.addBody(body)// save in objects to updateobjectsToUpdate.push({mesh,body})
}/*** Animate*/
const clock = new THREE.Clock()
let oldElapsedTime = 0 // 创建变量 标识上一刻度时间const tick = () =>
{const elapsedTime = clock.getElapsedTime()const deltaTime = elapsedTime - oldElapsedTimeoldElapsedTime = elapsedTime// Update physics worldworld.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 controlscontrols.update()// Renderrenderer.render(scene, camera)// Call tick again on the next framewindow.requestAnimationFrame(tick)
}tick()
二、知识点
1.图形
2.three.js 和cannon.js
1.下载,引入
npm install --save cannon
import CANNON from 'cannon'
由于cannonJS ,不更新 ;因此在它的基础上cannonEs更新,替换掉
npm uninstall --save cannon
npm install --save cannon-es@0.15.1
效果:
Physice
总结
学习,学呗!