Three.js 物理(Physics):让物体真的"掉下去、弹起来、撞一下"
原文出处:Three.js Manual – Physics
本文基于官方手册 Physics 章节整理,帮你理清 three.js 里"物理引擎"到底是怎么回事,以及该选哪条集成路线。
写在前面
前面所有文章里,物体的运动都是我们手动改position、rotation硬算出来的——比如让立方体匀速转、让球沿抛物线飞。这在简单场景够用,但一旦想要真实的重力、碰撞、堆叠、弹跳,手动算就疯了。
这时候就得上物理引擎(Physics Engine)。它帮你模拟重力、碰撞、受力这些物理现象。
核心思路很简单:你建一个平行于 three.js 的"物理世界",里面是一些"刚体(body)",它们会自己根据力、碰撞去动;然后每帧把物理世界里刚体的位置/旋转,同步给对应的 three.js 网格(Mesh)。视觉上,物体就像真的被物理规则驱动了一样。
一、先说一个关键点:物理不一定每帧都更新
为了表现稳定,物理通常用固定时间步长更新,而不是跟着渲染帧率跑。
比如你的游戏循环跑 60fps,但物理引擎可能只跑 30fps(每 1/30 ≈ 33.3ms 一算)。每帧渲染时,three.js 的网格就用物理引擎"最新的那次状态"去更新。这样既省算力,又不会因帧率波动导致物理表现忽快忽慢。
二、集成物理引擎的三条路线
three.js 官方手册把接入方式分成了三类,从"最省事"到"最硬核":
路线 1:用 three.js 自带的物理 Addons(最省事)
three.js 在examples/jsm/physics目录里给几个主流物理引擎包了封装层,帮你把"初始化物理世界"和"网格同步"这些活全干了:
- AmmoPhysics:封装 Ammo.js(Bullet Physics 的移植)
- JoltPhysics:封装 Jolt Physics
- RapierPhysics:封装 Rapier
对标准需求,这几条路线能让你几行代码就跑起来,隐藏了大量底层复杂度。官方示例:physics_ammo_instancing、physics_jolt_instancing、physics_rapier_instancing。
路线 2:用纯 JS/TS 写的第三方物理库(轻量好集成)
像cannon-es这种纯 JavaScript/TypeScript 的引擎,主打"轻量、和 three.js 天生好配"。用它们时,你得自己new物理世界和刚体,然后在动画循环里手动把刚体的position和quaternion(四元数)拷给 three.js 的 Mesh。
手册列的几个项目(注意维护状态):
| 库 | 说明 | 状态 |
|---|---|---|
| cannon-es | 纯 JS/TS 轻量 3D 物理引擎,MIT 许可 | 较新、常用(原 cannon.js 已停更,建议用它的 fork) |
| cannon.js | 纯 JS 轻量引擎,MIT 许可 | 已停更,建议换 cannon-es |
| phy | 纯 JS 写的 three.js 物理引擎,MIT 许可 | 当前在维护 |
| Oimo.js | 纯 JS 轻量引擎,MIT 许可 | 已停更,作者建议换 phy |
还有几个"披着 JS 皮、底层调别的引擎"的:
- Physijs:底层调 ammo.js,用 Web Worker 在独立线程算物理,MIT 许可(已停更)
- enable3d:基于 ammo.js 的 three.js 物理框架,LGPL-3.0(在维护)
路线 3:导入 WASM 版引擎(性能最强、最硬核)
要最大性能、最稳定、最高精度(尤其复杂模拟),可以用 C++/Rust 写的引擎编译成WebAssembly(WASM)的版本,比如Ammo.js(Bullet 移植)和Rapier。
代价是:这套方式要你自己管 WASM 的内存、直接调物理 API,接入代码更繁琐。但能力强、跑得快。
手册列出的几个底层引擎项目:
| 引擎 | 语言 | 许可 | 状态 |
|---|---|---|---|
| JoltPhysics | C++ | MIT | 在维护 |
| PhysX | C++ | BSD-3 | NVIDIA 出品,在维护、极稳定 |
| Rapier | Rust | MIT | 2D/3D,主打性能,在维护 |
| Bullet | C++ | ZLIB | 可能已停更 |
它们的现成 WASM 移植版:
- JoltPhysics.js:Emscripten 移植,MIT,在维护
- physx-js-webidl:PhysX 的 JS WASM 绑定,MIT,在维护
- Rapier.js:Rapier 官方 JS 绑定,Apache-2.0,在维护
- Ammo.js:Bullet 直接移植,但已停更(注意)
三、怎么选?
给个直觉性的判断:
- 只想快速出效果、标准需求→ 走路线 1(three.js Addons,Ammo/Jolt/Rapier 封装),最省心。
- 项目轻、想要可控、愿意自己同步→ 路线 2(cannon-es 这类纯 JS 库),集成自然。
- 要复杂模拟、追求性能和精度→ 路线 3(WASM 版 Rapier / PhysX / Jolt),代价是接入更麻烦。
总结
| 路线 | 代表 | 上手难度 | 性能 | 适合 |
|---|---|---|---|---|
| three.js Addons | Ammo/Jolt/Rapier 封装 | 最低 | 中-高 | 标准需求、快速起步 |
| 纯 JS/TS 库 | cannon-es、phy | 低 | 中 | 轻量、可控 |
| WASM 引擎 | Rapier.js、PhysX、Bullet | 高 | 最高 | 复杂/高性能模拟 |
记住那条主线:物理世界算状态 → 每帧同步给 Mesh。引擎只是"怎么算"的工具,这条同步逻辑三条路线都一样。
延伸阅读
- examples/jsm/physics(官方物理 Addons)
- cannon-es 仓库
- Rapier 物理引擎