一、物理引擎初探:什么是物理引擎?为什么3D场景需要它?Three.js与物理引擎的生态概览。

大家好,欢迎来到这门实战课的第一章。

说实话,我见过太多Three.js新手,花了好几个月把场景搭建得漂漂亮亮,结果一运行——嗯,物体直接穿模、悬空、或者像幽灵一样互相穿透。这时候他们才意识到:少了物理引擎。

这一章,我们就来聊聊物理引擎到底是什么,为什么你的3D场景离不开它,以及Three.js生态里有哪些物理引擎可以选。

1.1 什么是物理引擎?

物理引擎,说白了就是一套帮你模拟真实世界物理规律的代码库。它负责计算物体的运动、碰撞、重力、摩擦力等等。

你想想看,如果没有物理引擎,你在Three.js里创建一个球体,它只会呆呆地悬在空中。你想让它掉下来?得自己写重力计算。想让它撞到地面弹起来?得自己算碰撞检测和反弹系数。想让它和另一个球碰撞后改变方向?那更复杂了——你得处理动量守恒、能量损失……

我个人习惯把物理引擎比作「自动化的物理老师」。你只需要告诉它:这个物体有多重、形状是什么、初始速度是多少。剩下的——重力怎么加速、碰撞怎么响应、摩擦力怎么消耗能量——它全帮你算好了。

核心能力:
  • 碰撞检测:判断两个物体是否接触,以及接触的精确位置和深度。
  • 碰撞响应:根据物理定律(动量守恒、能量守恒)计算碰撞后的运动状态。
  • 约束求解:处理关节、弹簧、铰链等连接关系。
  • 连续物理模拟:处理高速运动物体的穿透问题(比如子弹穿过薄墙)。

1.2 为什么3D场景需要它?

这个问题,我在项目中被问过无数次。我的回答很简单:没有物理引擎的3D场景,就像没有骨骼的皮影戏——看着能动,但一碰就散架。

具体来说,物理引擎能帮你解决三大痛点:

  1. 真实感:用户能直观感受到物体的重量、惯性、弹性。比如一个箱子从高处落下,它会碎吗?会弹起来吗?会滚到一边吗?物理引擎帮你自动算出这些。
  2. 交互性:用户点击、拖拽、投掷物体时,物理引擎能给出符合直觉的反馈。我在做VR家居展示项目时,用户把杯子扔到地上,杯子碎了——这个反馈让用户觉得「哇,这场景是活的」。
  3. 开发效率:你不需要自己写碰撞检测算法、不需要调重力参数、不需要处理复杂的多体碰撞。物理引擎把这些脏活累活全包了。
避坑指南: 我曾经在一个建筑可视化项目里,为了省事没集成物理引擎,直接用动画曲线模拟物体掉落。结果客户反馈说「这个球掉下去的感觉像在月球上」。后来老老实实上了物理引擎,效果立竿见影。所以,别偷懒。

1.3 Three.js与物理引擎的生态概览

Three.js本身不包含物理引擎。它只负责渲染——把3D物体画到屏幕上。物理计算需要额外集成第三方库。

目前主流的搭配方案有这几种,我按使用场景给你列出来:

物理引擎 特点 适合场景 我的评价
Cannon-es 轻量、纯JS、API简洁 中小型项目、原型验证 入门首选,我第一个物理项目用的就是它
Ammo.js 基于Bullet引擎的Emscripten移植 大型项目、需要高级物理特性 功能强大,但体积大、调试麻烦
Rapier Rust编写、WASM运行、性能极佳 高性能需求、移动端、WebXR 目前我的首选,速度快到离谱
PhysX(通过WebAssembly) NVIDIA出品、工业级 需要GPU加速的复杂场景 门槛高,但上限也高

嗯,这里要注意:选物理引擎不是越强越好。我见过有人用Ammo.js做一个小球弹跳的Demo,结果加载时间比运行时间还长。要根据项目规模来选。

1.4 一个最简单的例子:让球掉下来

光说不练假把式。我们来看一个最基础的集成示例——用Three.js + Cannon-es让一个球掉到地面上。

// 1. 创建Three.js场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 5, 10);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 2. 创建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // 真实重力值

// 3. 创建物理球体
const sphereBody = new CANNON.Body({
  mass: 1,
  shape: new CANNON.Sphere(1)
});
sphereBody.position.set(0, 10, 0);
world.addBody(sphereBody);

// 4. 创建物理地面
const groundBody = new CANNON.Body({
  mass: 0, // 质量为0表示静态物体
  shape: new CANNON.Plane()
});
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);

// 5. 创建Three.js视觉球体
const sphereMesh = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
scene.add(sphereMesh);

// 6. 动画循环:同步物理和视觉
function animate() {
  world.step(1 / 60); // 固定时间步长
  sphereMesh.position.copy(sphereBody.position);
  sphereMesh.quaternion.copy(sphereBody.quaternion);
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
animate();

这段代码的核心逻辑就三步:

  • 创建物理世界并设置重力
  • 创建物理物体(球体和地面)
  • 每帧把物理引擎计算出的位置和旋转同步到Three.js的Mesh上

你看,物理引擎帮我们处理了重力加速、碰撞检测、碰撞响应。我们只需要写几行配置代码,一个逼真的自由落体就完成了。

注意: 物理引擎的坐标系和Three.js的坐标系默认是匹配的(Y轴向上)。但有些引擎(比如Ammo.js)默认是Z轴向上,需要手动旋转。这个坑我踩过,后面章节会详细讲。

1.5 本章小结

物理引擎不是什么黑魔法。它就是一个帮你算物理的库。你给它输入,它给你输出。Three.js负责把输出画出来。

我个人建议:只要你的场景涉及物体运动、碰撞、交互,就老老实实上物理引擎。别想着自己写——你写不过那些专门做物理引擎的团队,而且调试起来会疯掉。

下一章,我们会手把手搭建第一个完整的物理场景。到时候你会看到,集成物理引擎其实比想象中简单得多。

课后练习: 把上面代码中的重力值改成 -1,看看球体会怎么运动。再改成 -100,又会怎样?动手试试,你会对物理参数有更直观的理解。

好了,第一章就到这里。记住:物理引擎不是可选项,而是现代3D应用的必需品。我们下章见。