BabylonJS - 物理引擎
Babylon.js - 物理引擎集成
Section titled “Babylon.js - 物理引擎集成”为了为你的 Babylon.js 场景添加逼真的物理交互——例如碰撞(collisions)、重力(gravity)、弹跳(bouncing)、摩擦(friction)和关节约束(joint constraints)——你需要集成一个物理引擎(physics engine)。Babylon.js 使用插件系统(plugin system),允许你从几个流行的物理引擎中选择。
物理引擎运行一个独立于视觉渲染(visual rendering)的模拟(simulation)。它根据物理定律计算物体应该如何移动和交互。然后,Babylon.js 在每一帧更新你的视觉网格(visual meshes)的位置和方向,以匹配物理模拟结果。这创造了诸如球弹跳、物体掉落或结构坍塌等物理行为的错觉。
可用的物理插件
Section titled “可用的物理插件”Babylon.js 支持几种物理引擎插件:
- Havok: (官方集成)一个高度优化、商业级的物理引擎,以其性能和稳定性著称。商业用途需要单独订阅,但为非商业项目和评估提供免费版本。对于复杂模拟通常是最佳选择。
- Ammo.js (WASM): 流行 Bullet 物理引擎的直接 JavaScript 移植版本,编译为 WebAssembly (WASM) 以实现接近原生的性能。它是一个功能丰富、开源的选项,也是一个非常常见的选择。
- Cannon.js: 一个轻量级的纯 JavaScript 物理引擎。初始设置更容易,但在复杂场景下通常不如 Ammo.js 或 Havok 性能好且可能不如它们稳定。
- Oimo.js: 另一个纯 JavaScript 物理引擎,范围类似于 Cannon.js。性能也低于 WASM/Havok 选项。
选择取决于你的项目性能需求、复杂性、许可要求和熟悉程度。
启用物理和物理替身
Section titled “启用物理和物理替身”-
包含物理引擎库: 你需要在项目中包含所选物理引擎(例如
ammo.js,cannon.js,oimo.js, 或 Havok 库)的 JavaScript 文件,通常通过 CDN 或 npm 安装。 -
在场景中启用物理: 你需要明确地为场景启用物理模拟(physics simulation),通常需要指定重力(gravity)。
-
创建物理替身: 要使网格(mesh)参与物理模拟,它需要一个
PhysicsImpostor。这个替身(impostor)是一个不可见的、简化的物理形状(如 box, sphere, cylinder, plane, 或 mesh approximation),它在物理世界中代表视觉网格。你可以定义替身的类型、质量(mass)、摩擦力(friction)、恢复系数(restitution)(弹跳度)和其他物理属性(physical properties)。
示例:使用 Ammo.js 启用物理
Section titled “示例:使用 Ammo.js 启用物理”// 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.*)”SphereImpostorBoxImpostorPlaneImpostorCylinderImpostorCapsuleImpostor(取决于插件,例如 Ammo/Havok)MeshImpostor(近似网格形状 - 成本更高)HeightmapImpostor(用于地形)NoImpostor(用于从对象移除物理)
mass(number):质量,单位为千克。mass: 0创建一个静态对象(如地面或墙壁),它不会移动但参与碰撞。restitution(number):恢复系数,弹跳度(0 = 不弹跳,1 = 完全弹性弹跳)。friction(number):物体之间滑动运动的阻力(0 = 无摩擦)。
施加力和冲量
Section titled “施加力和冲量”你可以通过施加力(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); } }));演示:弹跳的球体
Section titled “演示:弹跳的球体”这个演示创建了多个球体掉落到地面平面上,使用 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 未能加载,球体可能会保持在其初始位置静态(取决于错误处理)。
重要注意事项
Section titled “重要注意事项”- 性能: 物理模拟可能计算密集。保持替身形状尽可能简单(Sphere, Box 比 Mesh 更便宜)。如果性能至关重要,避免大量动态对象。
- 异步加载: 基于 WASM 的引擎(如 Ammo.js)是异步加载的。在启用物理或创建替身之前,确保引擎已加载(
await Ammo())。 - 单位: 单位要保持一致。Babylon.js 通常使用米。
- 调试: Babylon.js 检查器(Inspector)有物理查看器(针对某些插件),可以显示不可见的替身形状,这对于调试碰撞问题非常有帮助。