Three.js 进阶之旅:物理效果-碰撞和声音 💥

摘要
本文内容主要汇总如何在 Three.js 创建的 3D 世界中添加物理效果,使其更加真实。所谓物理效果指的是对象会有重力,它们可以相互碰撞,施加力之后可以移动,而且通过铰链和滑块还可以在移动过程中在对象上施加约束。 通过本文的阅读,你将学习到如何使用 Cannon.js 在 Three.js 中创建一个 3D 物理世界,并在物理世界更新对象、联系材质、施加外力、处理多个物体中添加物体之间的碰撞效果,通过检测碰撞激烈程度来添加撞击声音等。
效果
本文最终将实现如下所示的效果,点击 DAT.GUI 中创建立方体 和球体 的按钮,对应的物体将在拥有重力的三维世界中坠落,物体与地面及物体与物体之间发生碰撞时可以产生与碰撞强度匹配的撞击音频 ,点击重置按钮,创建的物体将被清除。

打开以下链接,在线预览效果,大屏访问效果更佳。
本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。
原理
在专栏之前的原理和示例学习中,我们已经可以使用光照、阴影、Raycaster 等特性生成一些简单的物理效果,但是如果需要实现像物体张力、摩擦力、拉伸、反弹等物理效果时,我们可以使用一些专业的物理特性开源库来实现。
为了实现物理效果,我们将在 Three.js 中创建一个物理世界,它纯粹是理论性质的,我们无法直接看到它,但是在其中,三维物体将产生掉落、碰撞、摩擦、滑动等物理特性。具体原理是当我们在 Three.js 中创建一个网格模型时,同时会将其添加到物理世界中,在每一帧渲染任何内容之前我们会告诉物理世界如何自行更新,然后我们将获取物理世界中更新的位移和旋转坐标数据,将其应用到 Three.js 三维网格中。

库
已经有很多功能完备的物理特性库,我们就没必要重复造轮子了。物理特性库可以分为 2D 库和 3D 库,虽然我们是使用 Three.js 开发三维功能,但是有些 2D库 在三维世界中同样是适用的而且它们的性能会更好,如果我们需要开发的物理功能是碰撞类的,则可以使用 2D 库,比如Ouigo Let's play就是一个使用 2D 库开发的优秀示例。下面是一些常用的物理特性库。
对于 3D 物理库,主要有以下三个:
Ammo.js
- 官网:http://schteppe.github.io/ammo.js-demos/
- 仓库:https://github.com/kripken/ammo.js/
- 文档:https://github.com/kripken/ammo.js/#readme
Bullet一个使用C++编写的物理引擎的JavaScript直接移植- 包比较重量级,当前仍然由社区更新维护
Cannon.js
- 官网:https://schteppe.github.io/cannon.js/
- 仓库:https://github.com/schteppe/cannon.js
- 文档:http://schteppe.github.io/cannon.js/docs/
- 比
Ammo.js更加轻量级,使用起来更舒服 - 主要由一个开发者维护,已经多年未更新,有一个维护的
fork是cannon-es
Oimo.js
- 官网:https://lo-th.github.io/Oimo.js/
- 仓库:https://github.com/lo-th/Oimo.js
- 文档:http://lo-th.github.io/Oimo.js/docs.html
- 比
Ammo.js轻量且更容易入手 - 主要由一个开发者维护,已经有两年没有更新
对于 2D 物理库,有很多,下面列出了比较流行的几个:
Matter.js
- 官网:https://brm.io/matter-js/
- 仓库:https://github.com/liabru/matter-js
- 文档:https://brm.io/matter-js/docs/
- 主要由一个开发者维护,目前仍在更新中
P2.js
- 官网:https://schteppe.github.io/p2.js/
- 仓库:https://github.com/schteppe/p2.js
- 文档:http://schteppe.github.io/p2.js/docs/
- 主要由一个开发者维护,已经有2年没有更新
Planck.js
- 官网:https://piqnt.com/planck.js/
- 仓库:https://github.com/shakiba/planck.js
- 文档:https://github.com/shakiba/planck.js/tree/master/docs
- 主要由一个开发者维护,目前仍在更新中
Box2D.js
- 官网:http://kripken.github.io/box2d.js/demo/webgl/box2d.html
- 仓库:https://github.com/kripken/box2d.js/
- 文档:无
- 主要由一个开发者维护,目前仍在更新中
本文内容及示例将使用 Cannon.js 库,因为它更容易理解和使用,对于其他库,使用原理基本上是一样的,大家感兴趣的话可以自行尝试。
Cannon.js
Cannon.js 是一个 3D 物理引擎,通过为物体赋予真实的物理属性的方式来计算运动、旋转和碰撞检测。Cannon.js 相较于其他常见的物理引擎来说,比较轻量级而且完全通过 JavaScript 来实现。主要有以下特性:
- 刚体动力学
- 离散碰撞检测
- 接触、摩擦和恢复
- 点到点约束、铰链约束、锁紧装置约束等
Gauss-Seidel约束求解器与孤岛分割算法- 碰撞过滤
- 刚体休眠
- 实验性
SPH流体支持 - 各种形状和碰撞算法
Cannon-es
Cannon.js 库已经多年没有更新了,但是另一库 Cannon-es 克隆了原仓库并致力于长期更新维护新的仓库,可以像下面这样安装并使用,Cannon-es 用法和 Cannon.js 用法是完全一致的。
实现
本文示例及相关教程翻译并整理自 three.js journey 相关课程。
开始
安装并引入
npm install cannon --save
// 或
npm install --save cannon-es
import CANNON from 'cannon';
// 或
import * as CANNON from 'cannon-es';
初始化场景是一个平面 和一个球体 ,为了更好观察物理特性,已经开启了阴影效果。

我们可以使用 WebGL 创建一个无重力的太空场景,但是为了模拟地球环境 ,就需要添加重力,在 Cannon.js 中可以通过修改 gravity 属性值来实现,它是一个 Cannon.js Vec3 值,和 Three.js 中的 Vector3 一样,它包含 x、y、z 属性且拥有一个 set(...) 方法
world.gravity.set(0, -9.82, 0);
我们使用 -9.82 作为重力的 y 值,是因为它是地球的重力系数,如果你想让物理坠落的更慢或者想创建一个火星重力环境 ,就可以把它改为其他数值。
基础
世界
首先,我们需要创建一个 Cannon.js 世界:
const world = new CANNON.World();
对象
我们在场景中已经创建了一个球体,现在来在 Cannon.js 世界中创建一个球体。为了实现它,我们首先必须创建一个刚体Body,刚体是一种简单的对象,可以坠落和其他刚体产生碰撞。创建刚提前,我们首先需要决定刚体的形状,有很多形状可选,比如 Box、Cylinder、Plane 等,我们创建一个和 Three.js 中球体相同半径的球状刚体:
const sphereShape = new CANNON.Sphere(0.5);
然后,创建一个初始化 mass 质量及 position 位置的 Body 刚体:
const sphereBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: sphereShape
});
最后,我们通过 addBody(...) 方法将创建的刚体添加到世界中:
world.addBody(sphereBody);
此时查看页面可以看到没有任何效果,我们还需要更新 Cannon.js 世界和 Three.js 球体坐标。为更新物理世界world,我们必须使用时间步长step(...)方法。
更新
现在需要实现更新 Cannon.js 世界和 Three.js 场景。此时我们需要使用 step(...) 方法,为了使其生效,必须提供一个固定时间步长、自上次调用函数以来经过的时间、以及每个函数调用可执行的最大固定步骤数作为参数。
step(dt, [timeSinceLastCalled], [maxSubSteps=10])
dt:固定时间戳,要使用的固定时间步长[timeSinceLastCalled]:自上次调用函数以来经过的时间[maxSubSteps=10]:每个函数调用可执行的最大固定步骤数
关于时间步长原理,可查看此文章
在动画函数中,我们希望以 60fps 运行,因此将第一个参数设置为 1/60,这个设置在更高或更低帧率的情况下都能以相同速度运行;对于第二个参数,我们需要计算自上一帧以来经过了多少时间,通过将前一帧的 elapsedTime 减去当前 elapsedTime 来获得,不要直接使用 Clock 类中的 getDelta() 方法,因为无法得到预期的结果还会弄乱内部逻辑;第三个迭代参数,可以随便设置一个值,运行体验是否丝滑并不重要。
const clock = new THREE.Clock();
let oldElapsedTime = 0;
const tick = () => {
const elapsedTime = clock.getElapsedTime();
const deltaTime = elapsedTime - oldElapsedTime;
oldElapsedTime = elapsedTime;
//更新物理世界
world.step(1/60,deltaTime,3)
controls.update()
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
此时查看页面,看起来仍然没有变化,但实际上物理世界中的球体刚体 sphereBody 正在不断下坠,可以通过如下的打印日志 可以观察到。
console.log(sphereBody.position.y);

现在我们需要使用物理世界的 sphereBody 刚体坐标来更新 Three.js 中的球体,可以使用如下两种方法实现该功能:
// 方法一
sphere.position.x = sphereBody.position.x;
sphere.position.y = sphereBody.position.y;
sphere.position.z = sphereBody.position.z;
// 方法二
sphere.position.copy(sphereBody.position);
copy方法在 Vector2、Vector3、Euler、Quaternion 甚至 Material、Object3D、Geometry 等类中都是可用的。

此时就能看到小球 坠落的效果,但是它直接穿过了地面,因为现在仅在 Three.js 场景中添加了地面,而没有在 Cannon.js 物理世界中创建地面的刚体。
现在我们使用平面形状 Plane 来创建地面刚体,地面不应该受到物理世界重力的影响而下沉,它应该是保持静止不动的,我们可以通过如下方法将 mass 设置为 0 来实现:
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
floorBody.mass = 0;
floorBody.addShape(floorShape);
world.addBody(floorBody);

此时你会发现小球 坠落的方向变了,并不是我们预期的结果,它应该落到地面上。因为物理世界中添加的平面是面向相机 的,我们需要像在 Three.js 中旋转平面一样对它进行旋转。在 Cannon.js 中,我们只能使用四元数 Quaternion 来对刚体进行旋转,可以通过 setFromAxisAngle(...) 方法:
- 第一个参数是旋转轴
- 第二个参数是角度
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(- 1, 0, 0), Math.PI * 0.5);

现在可以看到小球 从高处下落并且停在地面上,因为地面是静止不动的,因此我们不需要使用 Cannon.js 中的地面来更新 Three.js 中的地面。
联系材质
从上图可以观察到,小球 坠落到地面后并没有反复弹跳,我们可以通过修改设置 Cannon.js 中的 Material 和 ContactMaterial 来添加摩擦和弹跳效果。
一个 Material 仅仅是一个类,你可以用它创建一种材质并命名后将它关联到 Body 刚体上,对于场景中所有的材质,都可以通过此方法进行创建。比如,假设世界中的所有物体都是塑料材质的,此时你只需创建一种材质即可,可以将它命名为 default 或 plastic;如果场景中地面和小球是不同材质的,就需要根据它们的类型创建多种材质。下面我们为示例中的两类物体分别创建名为混凝土 concrete 和 塑料 plastic 的材质:
const concreteMaterial = new CANNON.Material('concrete');
const plasticMaterial = new CANNON.Material('plastic');
接下来,我们使用创建的两种材质来创建联系材质 ContactMaterial,它是两种材质的组合,包含对象碰撞时的属性。然后使用 addContactMaterial(...) 方法将它添加到世界中:
const concretePlasticContactMaterial = new CANNON.ContactMaterial(
concreteMaterial,
plasticMaterial,
{
friction: 0.1,
restitution: 0.7
}
)
world.addContactMaterial(concretePlasticContactMaterial)
ContactMaterial (material1, material2 , [options])
- 前两个参数是材质
- 第三个参数是碰撞属性对象,包含摩擦系数和恢复系数,两者的默认值均为
0.3
接着我们将创建好的 Material 应用到 Body 上,可以在实例化主体时直接传递材质,也可以在实例化之后使用材质属性传递材质。现在可以看到小球 下落后在停止之前会返回弹跳多次:
const sphereBody = new CANNON.Body({
material: plasticMaterial
})
// 或者
const floorBody = new CANNON.Body()
floorBody.material = concreteMaterial

场景中一般会有多种材质 Materials 的物体,为每种两两组合创建 ContactMaterial 会费时费解,为了简化这一操作,我们来使用一种默认材质来替换创建联系材质时的两种材质,并将它应用到所有刚体上:
const defaultMaterial = new CANNON.Material('default');
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial,
defaultMaterial,
{
friction: 0.1,
restitution: 0.7
}
);
world.addContactMaterial(defaultContactMaterial)l
sphereBody.material = defaultMaterial;
floorBody.material = defaultMaterial;

可以观察到效果是相同的。或者我们直接设置世界的默认联系材质defaultContactMaterial 属性,然后移除 sphereBody 和 floorBody 的 material 属性,这样世界中的所有材质就都是相同的默认材质。
world.defaultContactMaterial = defaultContactMaterial;
施加外力
对一个刚体 Body 有以下几种施加外力的方法:
applyForce(force, worldPoint):从空间中的一个特殊点对刚体施加力(不一定在刚体的表面),比如就像风推动所有物体一样,或微弱但突然的力推向多米诺骨牌,或者像强烈且突然的力把愤怒的小鸟推向城堡一样。force:力的大小Vec3worldPoint:施加力的世界点Vec3
applyImpulse:类似于applyForce,但它不是因为增加导致加速度改变,而是直接作用于加速度。applyLocalForce(force, localPoint):与applyForce相同,但是坐标系是刚体的局部坐标,即(0, 0, 0)将是刚体的中点,从物体的内部施力。force:要应用的力向量Vec3localPoint:刚体中中要施加力的局部点Vec3
applyLocalImpulse:与applyImpulse相同,但是坐标系是刚体的局部坐标,即从物体的内部施力。
现在我们使用 applyLocalForce(...) 来为小球刚体 sphereBody 开始时施加一个小冲击力:
sphereBody.applyLocalForce(new CANNON.Vec3(150, 0, 0), new CANNON.Vec3(0, 0, 0));
可以看到小球 向右弹跳并滚动。

现在我们使用 applyForce(...) 方法来施加一点风力 ,因为风是永久性的,因此在更新 World 之前,我们需要将这种力施加到每一帧。要正确应用此力,受力点应该是小球的位置 sphereBody.position:
const tick = () => {
// ...
sphereBody.applyForce(new CANNON.Vec3(- 0.5, 0, 0), sphereBody.position)
world.step(1 / 60, deltaTime, 3)
// ...
}

处理多个物体
对一个或两个物体添加物理效果比较简单,但是为很多个物体都按上述方法添加就会非常复杂,我们需要添加一个自动化处理方法。
自动处理函数
首先,移除或注释掉 Cannon.js 世界和 Three.js 中的球体,还有动画函数 tick() 中球体的设置,然后创建一个 createSphere 方法来生成小球:
const createSphere = (radius, position) => {
// Three.js mesh
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(radius, 20, 20),
new THREE.MeshStandardMaterial({
metalness: 0.4,
roughness: 0.4,
color: 0xfffc00
})
);
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: shape,
material: defaultMaterial
});
body.position.copy(position);
world.addBody(body);
}
接着使用如下方法来创建一个小球 ,其中 position 参数不必是 Three.js 中的 Vector3 或者 Cannon.js 中的 Vec3,只需使用 x, y ,z 即可:
createSphere(0.5, { x: 0, y: 3, z: 0 });
可以看到地面顶部的创建的小球,但是由于我们移除了将 Cannon.js 世界中小球的 position 拷贝到 Three.js 中的方法,现在的小球暂时没有物理下坠效果。

使用一个对象数组
为了使批量创建的小球得到更新,我们使用一个数组 objectsToUpdate 在创建函数中保存它们:
const objectsToUpdate = [];
const createSphere = (radius, position) => {
// ...
objectsToUpdate.push({
mesh,
body
});
}
然后在动画方法 tick() 中批量将小球的 body.position 拷贝到 mesh.position:
const tick = () => {
// ...
for (const object of objectsToUpdate) {
object.mesh.position.copy(object.body.position);
}
}
此时,批量创建的小球 也有物理效果了。

添加Dat.GUI
为了方便调试,我们给页面按如下方式添加 Dat.GUI 调试工具,并添加一个 createSphere 来在场景中创建多个小球:
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
}
)
}
gui.add(debugObject, 'createSphere');

优化
因为 Three.js 网格 Mesh 的 geometry 和 material 都是一样的,我们应该将其移出 createSphere 方法,由于我们使用 radius 来创建几何体的,为了兼容之前的方法,我们可以按如下方式将 SphereGeometry 半径设置为 1,并使用 scale 来调整几何体的大小,得到的结果和上面是一致的,但是性能得以提升:
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial({
metalness: 0.4,
roughness: 0.4,
color: 0xfffc00
});
const createSphere = (radius, position) => {
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
mesh.castShadow = true;
mesh.scale.set(radius, radius, radius);
mesh.position.copy(position);
scene.add(mesh)
// ...
}
添加立方体
现在我们使用相同的流程添加一个创建立方体 的方法 createBox,其中传入的参数将是 width,height,depth,position。需要注意的是,Cannon.js 中创建Box 与 Three.js 创建 Box 不同,在 Three.js 中,创建几何体BoxBufferGeometry 只需要直接提供立方体的宽高深就行,但是在Cannon.js中,它是根据立方体对角线距离的一半来计算生成形状,因此其宽高深必须乘以0.5。
// 创建立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshStandardMaterial({
metalness: 0.4,
roughness: 0.4,
color: 0x0091ff
})
const createBox = (width, height, depth, position) => {
// Three.js 网格
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 刚体
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: shape,
material: defaultMaterial
})
body.position.copy(position);
world.addBody(body);
// 保存在更新对象数组中
objectsToUpdate.push({ mesh, body });
}
createBox(1, 1.5, 2, { x: 0, y: 3, z: 0 });
// 添加到DAT.GUI
debugObject.createBox = () => {
createBox(
Math.random(),
Math.random(),
Math.random(),
{
x: (Math.random() - 0.5) * 3,
y: 3,
z: (Math.random() - 0.5) * 3
}
)
}
gui.add(debugObject, 'createBox');
先移除创建小球的方法,页面运行可以得到如下的结果:

现在可以创建随机的立方体了,但是看起来有点奇怪不太逼真是不是?因为立方体掉下来后没有翻转,原因是 Three.js 中的网格没有像 Cannon.js 中的刚体一样旋转,在球体的示例中我们没有发现是因为无论球体是否旋转都是和原来一样的,而在立方体中不一样。我们可以通过如下将刚体的 quaternion 属性拷贝到网格的 quaternion 属性来实现,就像之前拷贝位置属性 position 一样:
const tick = () => {
// ...
for (const object of objectsToUpdate) {
object.mesh.position.copy(object.body.position);
object.mesh.quaternion.copy(object.body.quaternion);
}
// ...
}
现在立方体 坠落时的旋转也正常了。

性能优化
Broadphase
测试物体之间的碰撞时,一种方法是检测一个刚体与另外所有其他刚体之间的碰撞,虽然这一操作很容易实现,但是非常耗费性能。此时就需要 Broadphase,它会在测试之前对刚体进行粗略的分类,想象一下,两堆相距很远的立方体,为什么要用一堆立方体来测试另一堆立方体之间的碰撞关系能,它们相距很远,不会发生碰撞,因此就没必要测试来耗费性能。
在 Cannon.js 中共有 3 种 Broadphase 算法:
NaiveBroadphase:测试每个刚体与其他所有刚体之间的碰撞,默认算法。GridBroadphase: 使用四边形栅格覆盖world,仅针对同一栅格或相邻栅格中的其他刚体进行碰撞测试。SAPBroadphase:扫描剪枝算法,在多个步骤的任意轴上测试刚体。
NaiveBroadphase 是默认检测方法,但是推荐使用 SAPBroadphase 算法,虽然这种算法有时可能会产生检测不会发生碰撞的错误,但是它的检测速度非常快。通过如下方式,简单设置 world.broadphase 属性即可修改碰撞检测算法:
world.broadphase = new CANNON.SAPBroadphase(world);
Sleep
即使我们使用改进的 Broadphase 碰撞检测算法,有可能所有的刚体都会被检测,即使是那些不再发生移动的刚体。此时我们可以使用称为 Sleep 的特性,当刚体的速度逐渐变小不再发生移动,它就会进入睡眠状态,此时就不会对它进行碰撞检测,除非使用代码让其施加一个足够的力再次运动或有其它的刚体击中它。可以通过对 world 设置 allowSleep 属性为 true 来实现:
world.allowSleep = true;
你也可以使用 sleepSpeedLimit、sleepTimeLimit 属性对睡眠速度和时间进行详细设置,但是一般不会改变默认值。
事件
可以对刚体的事件进行监听,比如你想在物体发生碰撞时播放呻吟或者在射击游戏中检测是否命中敌人等情况下是非常有用的。你可以在刚体上监听 colide、sleep、wakeup 等事件。
现在,我们来实现一下当场景中的小球 和立方体 互相之间发生碰撞时播放声音 的功能。首先在 JavaScript 中创建音频,并添加一个方法来播放它。
有些浏览器比如Chrome默认会静音除非用户与页面发生交互,例如点击任意区域,所以不要担心首次加载时不播放声音的问题
const hitSound = new Audio('/sounds/hit.mp3');
const playHitSound = () => {
// 播放时间重置为0,解决多次调用时声音间断问题
hitSound.currentTime = 0
hitSound.play()
}
然后在创建立方体方法 createBox 中调用:
const createBox = (width, height, depth, position) => {
// ...
body.addEventListener('collide', playHitSound);
// ...
}

此时,当立方体 撞击到地面或相互碰撞时可以听到撞击声音 ,看起来似乎是正确的,但是当添加多个立方体时,我们会听到很多立方体之间相互撞击的声音是一样的,而现实中的声音应该是根据声音随着立方体之间的撞击程度而不同,撞击程度足够小的话就听不到声音。为了获取撞击的强度,我们需要获取撞击信息,可以通过如下给 playHitSound 方法添加参数 collision 的方式来获取撞击信息:
const playHitSound = (collision) => {
const impactStrength = collision.contact.getImpactVelocityAlongNormal();
// 只有撞击强度足够大时才播放撞击音频
if (impactStrength > 1.5) {
// 为了更加真实,可以给音量添加一些随机性
hitSound.volume = Math.random();
hitSound.currentTime = 0;
hitSound.play();
}
}
然后在创建球体的方法 createSphere 中同样调用播放撞击音频方法:
const createSphere = (radius, position) => {
// ...
body.addEventListener('collide', playHitSound)
// ...
}
移除物体
当页面上添加过多物体时,我们可以通过在 Dat.GUI 添加一个重置按钮来移除已添加的物体,通过遍历 objectsToUpdate 数组,将每个数组项对应的的 object.body 从 物理世界 world 中移除,将 object.mesh 从 Three.js 场景中移除,并清除 collide 碰撞事件的 eventListener:
debugObject.reset = () => {
for (const object of objectsToUpdate) {
object.body.removeEventListener('collide', playHitSound);
world.removeBody(object.body);
scene.remove(object.mesh);
}
}
gui.add(debugObject, 'reset');
总结
本文中主要包含的知识点包括:
Three.js中添加物理效果基本原理- 常用
3D与2D物理物理引擎汇总 Cannon.js与Cannon-es安装与引用- 物理世界创建、对象更新、联系材质、施加外力、处理多个物体
- 碰撞事件监听、音频添加
- 性能优化、物理世界移除物体等
想了解其他前端知识或其他未在本文中详细描述的Web 3D开发技术相关知识,可阅读我往期的文章。如果有疑问可以在评论中留言,如果觉得文章对你有帮助,不要忘了一键三连哦 。
附录
- [1]. Three.js 打造缤纷夏日3D梦中情岛
- [2]. Three.js 实现炫酷的赛博朋克风格3D数字地球大屏
- [3]. Three.js 实现2022冬奥主题3D趣味页面,含冰墩墩
- [4]. Three.js 实现3D开放世界小游戏:阿狸的多元宇宙
- [5]. 掘金1000粉!使用Three.js实现一个创意纪念页面
...- 【Three.js 进阶之旅】系列专栏访问
- 更多往期【3D】专栏访问
- 更多往期【前端】专栏访问
参考
- [1]. three.js journey
- [2]. threejs.org
本文作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/17121487.html
Three.js 进阶之旅:物理效果-碰撞和声音 💥的更多相关文章
- Three.js 进阶之旅:新春特典-Rabbit craft go 🐇
声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 摘要 兔年到了,祝大家身体健,康万事顺利.本文内容作为兔年新春纪念页面,将使用 ...
- js进阶 13-6 jquery动画效果相关常用函数有哪些
js进阶 13-6 jquery动画效果相关常用函数有哪些 一.总结 一句话总结:animate(),stop(),finish(),delat()四个. 1.stop()方法的基本用法是什么(sto ...
- 我的Android进阶之旅------>Android利用温度传感器实现带动画效果的电子温度计
要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...
- 我的Android进阶之旅------>Android疯狂连连看游戏的实现之游戏效果预览(一)
今天看完了李刚老师的<疯狂Android讲义>一书中的第18章<疯狂连连看>,从而学会了如何编写一个简单的Android疯狂连连看游戏. 开发这个流行的小游戏,难度适中,而且能 ...
- js进阶 13-7 如何实现滑动面板效果
js进阶 13-7 如何实现滑动面板效果 一.总结 一句话总结:就是普通的jquery动画中的滑动效果.$('#content').slideToggle().滑动效果的实质是通过调整高度. 1.滑动 ...
- js进阶 13-2 jquery动画滑动效果哪些注意事项
js进阶 13-2 jquery动画滑动效果哪些注意事项 一.总结 一句话总结:滑动里面这里up是隐藏,down是显示. 1.jquery动画默认的两个切换效果是什么(swing默认和linear的区 ...
- 用Physijs在场景中添加物理效果
1.创建可用Physijs的基本Three.js场景 创建一个可用Physijs的Three.js场景非常简单,只要几个步骤即可.首先我们要包含正确的文件, 需要引入physi.js文件.实际模拟物理 ...
- 2. web前端开发分享-css,js进阶篇
一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...
- web前端开发分享-css,js进阶篇
一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践 经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无 从 ...
- [置顶] 我的Android进阶之旅------>介绍一款集录制与剪辑为一体的屏幕GIF 动画制作工具 GifCam
由于上一篇文章:我的Android进阶之旅------>Android之动画之Frame Animation实例 中展示的是Frame动画效果,但是之前我是将图片截取下来,不好说明确切的动画过程 ...
随机推荐
- 基于python的数学建模---蒙特卡洛算法
import math import random m = input('请输入一个较大的整数') n = 0 for i in range(int(m)): x = random.random() ...
- Dubbo-Activate实现原理
前言 在Dubbo中有Filter使用,对于Filter来说我们会遇到这样的问题,Filter自身有很多的实现,我们希望某种条件下使用A实现,另外情况下使用B实现,这个时候我们前面介绍@SPI和@Ad ...
- C ++:树
C++:树 树的概念: 所谓"树"是输就结构的一种,树大概可以分为两大类: 有根树 和 无根树 有根树使有一个确定的根节点,反之为无根树 · 子节点:从树根开始,通过树边向下扩展的 ...
- 一行代码实现shell if else逻辑
前言 前几天学习 shell 脚本,发现这种好用的写法,简单记录一下. if else 一行实现 if [ 1=1 ] ;then echo "条件成立";else echo &q ...
- c3 linearization详解
MRO MRO 全称方法解析顺序(Method Resolution Order),在多重继承和多继承存在的时候,寻找属性及方法的顺序. 深度优先(DFS)与广度优先(BFS) python2 所用的 ...
- CSP-S 游寄
\(\text{reflection}\) 初赛. 本来以为上午要愉快地周测,但是伟大的虎哥让我们在四楼接着练习 然后就目睹了一个万能头+return 0编译 1min30sec 的奇迹 Win7 打 ...
- Spring03:案例转账功能(事务问题)、动态代理解决、AOP
今日内容--核心2AOP 完善Account案例 分析案例中的问题 回顾之前讲过的技术--动态代理 动态代理的另一种实现方式 解决案例中的问题 AOP的概念 Spring中的AOP相关术语 Sprin ...
- 全网最全的linux上docker安装oracle的详细文档,遇到了n个问题,查了几十篇文章,最终汇总版,再有解决不了的,私聊我,我帮你解决
目录 全网最全的linux上docker安装oracle的详细文档,遇到了n个问题,查了几十篇文章,最终汇总版,再有解决不了的,私聊我,我帮你解决 1. 拉取阿里镜像oracle 2. 创建初始化数据 ...
- Day37:正则表达式详解
正则表达式 1.1 概述 正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性. 比如我们在网站上输入用户账号,要求我们输入的账号信息要符合账号的格式,而校验我们输入的账号格式是否正确 ...
- PAM8403 3.3V音频功放调试笔记
做I2S输出用了PT8211(实际上买到的丝印是GH8211), 双声道, LSB格式, 工作正常但是输出功率非常低, 喇叭声音要贴近了才能勉强听到, 所以打算做一个PT8211带功放的I2S模块. ...