Skip to content

BabylonJS - 物理引擎

为了为你的 Babylon.js 场景添加逼真的物理交互——例如碰撞(collisions)、重力(gravity)、弹跳(bouncing)、摩擦(friction)和关节约束(joint constraints)——你需要集成一个物理引擎(physics engine)。Babylon.js 使用插件系统(plugin system),允许你从几个流行的物理引擎中选择。

物理引擎运行一个独立于视觉渲染(visual rendering)的模拟(simulation)。它根据物理定律计算物体应该如何移动和交互。然后,Babylon.js 在每一帧更新你的视觉网格(visual meshes)的位置和方向,以匹配物理模拟结果。这创造了诸如球弹跳、物体掉落或结构坍塌等物理行为的错觉。

Babylon.js 支持几种物理引擎插件:

  • Havok: (官方集成)一个高度优化、商业级的物理引擎,以其性能和稳定性著称。商业用途需要单独订阅,但为非商业项目和评估提供免费版本。对于复杂模拟通常是最佳选择。
  • Ammo.js (WASM): 流行 Bullet 物理引擎的直接 JavaScript 移植版本,编译为 WebAssembly (WASM) 以实现接近原生的性能。它是一个功能丰富、开源的选项,也是一个非常常见的选择。
  • Cannon.js: 一个轻量级的纯 JavaScript 物理引擎。初始设置更容易,但在复杂场景下通常不如 Ammo.js 或 Havok 性能好且可能不如它们稳定。
  • Oimo.js: 另一个纯 JavaScript 物理引擎,范围类似于 Cannon.js。性能也低于 WASM/Havok 选项。

选择取决于你的项目性能需求、复杂性、许可要求和熟悉程度。

  1. 包含物理引擎库: 你需要在项目中包含所选物理引擎(例如 ammo.js, cannon.js, oimo.js, 或 Havok 库)的 JavaScript 文件,通常通过 CDN 或 npm 安装。

  2. 在场景中启用物理: 你需要明确地为场景启用物理模拟(physics simulation),通常需要指定重力(gravity)。

  3. 创建物理替身: 要使网格(mesh)参与物理模拟,它需要一个 PhysicsImpostor。这个替身(impostor)是一个不可见的、简化的物理形状(如 box, sphere, cylinder, plane, 或 mesh approximation),它在物理世界中代表视觉网格。你可以定义替身的类型、质量(mass)、摩擦力(friction)、恢复系数(restitution)(弹跳度)和其他物理属性(physical properties)。

// 1. 包含 Ammo.js(例如,通过 HTML 中的 CDN 或如果使用 npm 则通过 import)
// <script src="https://cdn.babylonjs.com/ammo.js"></script>
// 或者如果使用 npm/打包器:
// import Ammo from 'ammo.js'; // 根据你的设置调整路径
// --- 在你的异步场景创建函数内部 ---
// 确保 Ammo 已加载(它是异步加载的)
await Ammo(); // 等待 WASM 模块准备就绪
// 2. 使用 AmmoJSPlugin 启用物理
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.AmmoJSPlugin(); // 传递 true 开启 worker 线程(可选)
scene.enablePhysics(gravityVector, physicsPlugin);
// 3. 为网格创建替身
// 示例:静态地面替身(质量 = 0)
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 50, height: 50}, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor, // 将地面表示为薄盒子
{ mass: 0, restitution: 0.9, friction: 0.5 },
scene
);
// 示例:动态球体替身(质量 > 0)
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);
sphere.position = new BABYLON.Vector3(0, 10, 0); // 从地面上方开始
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor, // 为物理使用球体形状
{ mass: 1, restitution: 0.7, friction: 0.4 }, // 有弹跳,有摩擦
scene
);

替身类型(BABYLON.PhysicsImpostor.*)

Section titled “替身类型(BABYLON.PhysicsImpostor.*)”
  • SphereImpostor
  • BoxImpostor
  • PlaneImpostor
  • CylinderImpostor
  • CapsuleImpostor(取决于插件,例如 Ammo/Havok)
  • MeshImpostor(近似网格形状 - 成本更高)
  • HeightmapImpostor(用于地形)
  • NoImpostor(用于从对象移除物理)
  • mass(number):质量,单位为千克。mass: 0 创建一个静态对象(如地面或墙壁),它不会移动但参与碰撞。
  • restitution(number):恢复系数,弹跳度(0 = 不弹跳,1 = 完全弹性弹跳)。
  • friction(number):物体之间滑动运动的阻力(0 = 无摩擦)。

你可以通过施加力(forces)或冲量(impulses)来与动态物理对象交互:

  • applyForce(forceVector, contactPoint):在特定点施加一个持续的力(例如推动)。
  • applyImpulse(impulseVector, contactPoint):在特定点施加一个瞬时的动量变化(例如撞击)。
  • setLinearVelocity(velocityVector):直接设置对象的移动速度和方向。
  • setAngularVelocity(angularVelocityVector):直接设置对象的旋转速度。
// 示例:点击球体时对其施加向上冲量
sphere.actionManager = new BABYLON.ActionManager(scene);
sphere.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
if (sphere.physicsImpostor) {
const impulse = new BABYLON.Vector3(0, 10, 0); // 向上冲量
const contactPoint = sphere.getAbsolutePosition(); // 在中心点施加
sphere.physicsImpostor.applyImpulse(impulse, contactPoint);
}
})
);

这个演示创建了多个球体掉落到地面平面上,使用 Ammo.js 物理引擎相互作用。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Babylon.js - Physics Demo (Ammo.js)</title>
<!-- 如果使用独立脚本,请确保 Ammo.js 在 Babylon.js 之前加载 -->
<script src="https://cdn.babylonjs.com/ammo.js"></script>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<style>
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; }
#renderCanvas { width: 100%; height: 100%; touch-action: none; }
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = async function () {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.15, 1.0);
const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 4, Math.PI / 3, 40, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
camera.wheelPrecision = 50;
const light = new BABYLON.HemisphericLight("hemi", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;
const pointLight = new BABYLON.PointLight("point", new BABYLON.Vector3(10, 10, -10), scene);
pointLight.intensity = 0.5;
// --- 物理设置 (Ammo.js) ---
try {
await Ammo(); // 等待 Ammo 初始化
console.log("Ammo.js loaded.");
const gravityVector = new BABYLON.Vector3(0, -9.81, 0);
const physicsPlugin = new BABYLON.AmmoJSPlugin();
scene.enablePhysics(gravityVector, physicsPlugin);
console.log("Physics enabled.");
} catch (e) {
console.error("Ammo.js failed to load or physics failed to enable:", e);
// 适当处理错误,也许向用户显示消息
return scene; // 如果失败,返回没有物理的场景
}
// --------------------------------
// 地面
const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 30, height: 30 }, scene);
const groundMat = new BABYLON.StandardMaterial("groundMat", scene);
groundMat.diffuseColor = new BABYLON.Color3(0.5, 0.6, 0.5);
ground.material = groundMat;
ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, restitution: 0.8, friction: 0.5 }, scene);
// 创建多个球体
const sphereRadius = 1;
const sphereMat = new BABYLON.StandardMaterial("sphereMat", scene);
sphereMat.diffuseColor = new BABYLON.Color3(0.9, 0.4, 0.4); // 偏红色
sphereMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);
for (let i = 0; i < 20; i++) {
const sphere = BABYLON.MeshBuilder.CreateSphere(`sphere${i}`, { diameter: sphereRadius * 2 }, scene);
sphere.material = sphereMat;
// 交错初始位置
sphere.position = new BABYLON.Vector3(
Math.random() * 10 - 5,
10 + i * 3,
Math.random() * 10 - 5
);
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
sphere,
BABYLON.PhysicsImpostor.SphereImpostor,
{ mass: 1, restitution: 0.6 + Math.random() * 0.3, friction: 0.4 }, // 稍微随机的弹跳度
scene
);
}
return scene;
};
createScene().then(sceneToRender => {
if (!sceneToRender) return;
engine.runRenderLoop(function () {
sceneToRender.render();
});
window.addEventListener("resize", function () {
engine.resize();
});
}).catch(error => {
console.error("Scene creation failed:", error);
});
</script>
</body>
</html>

如果 Ammo.js 加载正确,这个演示将显示大约 20 个偏红色的球体以交错的高度出现在绿色地面平面上方。由于物理引擎启用的重力,球体将掉落,从地面弹起(弹跳度略有不同),彼此真实地碰撞,并最终静止。如果 Ammo.js 未能加载,球体可能会保持在其初始位置静态(取决于错误处理)。

  • 性能: 物理模拟可能计算密集。保持替身形状尽可能简单(Sphere, Box 比 Mesh 更便宜)。如果性能至关重要,避免大量动态对象。
  • 异步加载: 基于 WASM 的引擎(如 Ammo.js)是异步加载的。在启用物理或创建替身之前,确保引擎已加载(await Ammo())。
  • 单位: 单位要保持一致。Babylon.js 通常使用米。
  • 调试: Babylon.js 检查器(Inspector)有物理查看器(针对某些插件),可以显示不可见的替身形状,这对于调试碰撞问题非常有帮助。