目录
1.物理引擎介绍(Physics)
在我们日常生活中,物体之间是存在相互作用力的,他们之间存在弹力、摩擦力等相关系数,物体自身也存在重力等属性,因此并不是简单的碰撞。在Three中,物理引擎主要依赖于第三方插件进行实现,例如Cannon.js,Ammo.js,Oimo.js。
1.1 类库介绍
1.三维库
1)Ammo.js
受three.js和ammo.js的启发,以及 web 缺乏物理引擎这一事实的驱动,cannon.js 出现了。刚体物理引擎包括简单的碰撞检测、各种身体形状、接触、摩擦和约束。GitHub - schteppe/cannon.js: A lightweight 3D physics engine written in JavaScript.A lightweight 3D physics engine written in JavaScript. - GitHub - schteppe/cannon.js: A lightweight 3D physics engine written in JavaScript.https://github.com/schteppe/cannon.js官网:
Oimo.jshttps://lo-th.github.io/Oimo.js/#basicGitHub - lo-th/Oimo.js: Lightweight 3d physics engine for javascriptLightweight 3d physics engine for javascript. Contribute to lo-th/Oimo.js development by creating an account on GitHub.
https://github.com/lo-th/Oimo.js
2.二维库
当使用时,仅涉及水平方向时,可以将三维交互转换为二维交互,使用以下二维类库进行实现。
Matter.js
P2.js
Planck.js
Box2D.js
1.2 实现原理
使用中,我们会创建一个Three.js世界和一个Physics物理世界,虽然我们看不见后者但它是真实存在的,每当我们往Three.js世界添加对象时,相应的物理世界也会添加相同对象。物理世界在每一帧更新时都会相应更新到Three.js世界中(通过实体的.position.copy方法获取物理世界实体的位置)。
例如物理世界中的球体在平面上进行真实弹跳效果时,我们会取其每一帧更新后的坐标并将坐标应用到Three.js世界中的对应球体。
2. Cannon.js物理引擎
2.1 Cannon安装
1)安装
cnpm install cannon --save
2)导入
import * as CANNON from "cannon";
3)使用
创建球和平面:
// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
scene.add(sphere);
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(20, 20),
new THREE.MeshStandardMaterial()
);
floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
创建Cannon物理世界
//start
// 创建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);//9.8重力加速度
// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);//半径为一的球
//设置物体材质
const sphereWorldMaterial = new CANNON.Material();
// 创建物理世界的物体
const sphereBody = new CANNON.Body({
shape: sphereShape,
//三维坐标
position: new CANNON.Vec3(0, 0, 0),//三维坐标
// 小球质量
mass: 1,
// 物体材质
material: sphereWorldMaterial,
});
// 将物体添加至物理世界
world.addBody(sphereBody);
// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);//按x轴旋转
world.addBody(floorBody);
//end
Three世界获取物理世界坐标
world.step(1 / 120, deltaTime);
sphere.position.copy(sphereBody.position);
实现效果:
全部代码(main.js):
import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入connon引擎
import * as CANNON from "cannon-es";
console.log(CANNON);
// 1、创建场景
const scene = new THREE.Scene();
// 2、创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
300
);
// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);
// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
scene.add(sphere);
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(20, 20),
new THREE.MeshStandardMaterial()
);
floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// 创建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);
//设置物体材质
const sphereWorldMaterial = new CANNON.Material();
// 创建物理世界的物体
const sphereBody = new CANNON.Body({
shape: sphereShape,
position: new CANNON.Vec3(0, 0, 0),
// 小球质量
mass: 1,
// 物体材质
material: sphereWorldMaterial,
});
// 将物体添加至物理世界
world.addBody(sphereBody);
// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);//按x轴旋转
world.addBody(floorBody);
//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true;
scene.add(dirLight);
// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);
// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;
// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();
function render() {
// let time = clock.getElapsedTime();
let deltaTime = clock.getDelta();
// 更新物理引擎里世界的物体
world.step(1 / 120, deltaTime);
sphere.position.copy(sphereBody.position);
renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {
// console.log("画面变化了");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio);
});
3. 案例:点击屏幕向外弹出立方体
import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入connon引擎
import * as CANNON from "cannon-es";
// 目标:设置cube跟着旋转
console.log(CANNON);
// 1、创建场景
const scene = new THREE.Scene();
// 2、创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
300
);
// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);
const cubeArr = [];
//设置物体材质
const cubeWorldMaterial = new CANNON.Material("cube");
function createCube() {
// 创建立方体和平面
const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial();
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
scene.add(cube);
// 创建物理cube形状
const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
// 创建物理世界的物体
const cubeBody = new CANNON.Body({
shape: cubeShape,
position: new CANNON.Vec3(0, 0, 0),
// 小球质量
mass: 1,
// 物体材质
material: cubeWorldMaterial,
});
cubeBody.applyLocalForce(
new CANNON.Vec3(300, 0, 0), //添加的力的大小和方向
new CANNON.Vec3(0, 0, 0) //施加的力所在的位置
);
// 将物体添加至物理世界
world.addBody(cubeBody);
// 添加监听碰撞事件
function HitEvent(e) {
// 获取碰撞的强度
// console.log("hit", e);
const impactStrength = e.contact.getImpactVelocityAlongNormal();
console.log(impactStrength);
if (impactStrength > 2) {
// 重新从零开始播放
hitSound.currentTime = 0;
hitSound.volume = impactStrength / 12;
hitSound.play();
}
}
cubeBody.addEventListener("collide", HitEvent);
cubeArr.push({
mesh: cube,
body: cubeBody,
});
}
window.addEventListener("click", createCube);
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(20, 20),
new THREE.MeshStandardMaterial()
);
floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// 创建物理世界
// const world = new CANNON.World({ gravity: 9.8 });
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
// 创建击打声音
const hitSound = new Audio("assets/metalHit.mp3");
// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
const floorMaterial = new CANNON.Material("floor");
floorBody.material = floorMaterial;
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(floorBody);
// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(
cubeWorldMaterial,
floorMaterial,
{
// 摩擦力
friction: 0.1,
// 弹性
restitution: 0.7,
}
);
// 将材料的关联设置添加的物理世界
world.addContactMaterial(defaultContactMaterial);
// 设置世界碰撞的默认材料,如果材料没有设置,都用这个
world.defaultContactMaterial = defaultContactMaterial;
//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true;
scene.add(dirLight);
// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);
// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;
// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();
function render() {
// let time = clock.getElapsedTime();
let deltaTime = clock.getDelta();
// 更新物理引擎里世界的物体
world.step(1 / 120, deltaTime);
// cube.position.copy(cubeBody.position);
cubeArr.forEach((item) => {
item.mesh.position.copy(item.body.position);
// 设置渲染的物体跟随物理的物体旋转
item.mesh.quaternion.copy(item.body.quaternion);
});
renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {
// console.log("画面变化了");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio);
});
实现效果: