拓冰建站拓冰建站
首页 / 资讯中心 / 正文

three.js AmmoPhysics 组件:用 Ammo.js 物理引擎为 three.js 应用添加刚体模拟

three.js AmmoPhysics 组件用 Ammo.js 物理引擎为 three.js 应用添加刚体模拟【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsAmmoPhysics是 three.js 官方提供的一个物理引擎 Addon它把基于 Bullet 物理引擎移植而来的 Ammo.jsWASM 版本包装进three.js应用中让开发者用几行代码即可为场景中的Mesh/InstancedMesh添加刚体碰撞、重力下落与回弹模拟。读完本文你将掌握该组件的初始化方式、三个核心 API.addMesh、.addScene、.setMeshPosition的完整参数与默认值以及它在源码层面的碰撞管线构建、运动状态同步机制和适用限制。组件定位官方 Addon 之一AmmoPhysics是一个 addon附加组件不属于three核心包必须显式导入。仓库中它位于 examples/jsm/physics/AmmoPhysics.js与同为 WASM 物理封装的JoltPhysics、RapierPhysics并列存放在examples/jsm/physics/目录下。官方手册 manual/pages/physics.html 中也对三者做了区分AmmoPhysicsAmmo.jsBullet Physics的封装JoltPhysicsJolt Physics 的封装RapierPhysicsRapier 的封装。手册同时指出像 Ammo.js 这类 C 物理引擎编译到 WebAssembly 的方案“在性能、稳定性和精度上表现最好尤其适合复杂模拟”但往往需要更多搭建代码——而AmmoPhysics正是为了降低这套搭建成本而存在的薄封装层。需要注意手册中的一句提示Ammo.js 目前“不再活跃维护”选型时建议同时对比RapierPhysics等仍在维护的方案。import { AmmoPhysics } from three/addons/physics/AmmoPhysics.js;初始化await AmmoPhysics()的加载机制组件通过一个异步工厂函数初始化const physics await AmmoPhysics();文档明确强调组件会自动从 CDN 导入 Ammo.js因此必须保证运行时处于联网状态。这一点在源码中可以直接印证——examples/jsm/physics/AmmoPhysics.js 的开头硬编码了 Ammo.js 的 CDN 地址固定到 Ammo.js 的一个具体 commitconst AMMO_PATH https://cdn.jsdelivr.net/gh/kripken/ammo.js79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js;AmmoPhysics()内部的加载逻辑是检查全局Ammo是否已存在例如页面已经手动引入过ammo.wasm.js脚本则可跳过这一步若不存在则动态创建script标签注入document.headonload时 resolve、onerror时 reject加载完成后执行await Ammo()初始化 WASM 模块拿到AmmoLib。拿到AmmoLib后组件立即构建一套完整的 Bullet 碰撞管线源码 L35-L42const frameRate 60; const collisionConfiguration new AmmoLib.btDefaultCollisionConfiguration(); const dispatcher new AmmoLib.btCollisionDispatcher( collisionConfiguration ); const broadphase new AmmoLib.btDbvtBroadphase(); const solver new AmmoLib.btSequentialImpulseConstraintSolver(); const world new AmmoLib.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); world.setGravity( new AmmoLib.btVector3( 0, - 9.8, 0 ) );从源码结构看这套配置是写死的重力固定为(0, -9.8, 0)帧率固定为 60均不对外暴露修改接口。物理世界通过setInterval( step, 1000 / frameRate )源码 L292以约 60fps 的固定节奏驱动每帧计算真实流逝的 delta 后调用world.stepSimulation( delta, 10 )其中第二个参数 10 表示每帧最多允许的固定时间步数。AmmoPhysics()最终返回一个包含三个方法的对象addScene、addMesh、setMeshPosition。下面逐一说明。API 详解.addMesh( mesh, mass, restitution )把单个网格加入物理模拟。参数类型默认值说明meshMesh必填要加入模拟的网格massnumber0质量单位kg。传0表示静态刚体不随模拟运动但仍参与碰撞restitutionnumber0恢复系数通常取 0~1表示碰撞时物体有多“弹”源码实现handleMeshL125-L155揭示了两个关键行为mass 决定刚体类型只有mass 0的网格才会被推进内部meshes更新列表并建立mesh → body的WeakMap映射mass 0的刚体虽然也加入世界但属于静态体不参与每帧的位置回写。惯性自动计算组件调用shape.calculateLocalInertia( mass, localInertia )由碰撞形状自动计算转动惯量无需手动提供。同时addMesh对几何体类型有硬性限制。内部getShape()源码 L48-L80只支持两种映射three.js 几何体Bullet 碰撞形状尺寸取值BoxGeometrybtBoxShapewidth / height / depth的一半未提供时各半轴默认 0.5SphereGeometry/IcosahedronGeometrybtSphereShapeparameters.radius未提供时默认 1两种形状都会setMargin( 0.05 )给碰撞形状加上 0.05 的外扩余量。传入其他几何体如CylinderGeometry、PlaneGeometry、加载出的 GLTF 几何体时组件只会console.error( AmmoPhysics: Unsupported geometry type:, ... )并静默跳过不会抛出异常——这是一个容易踩的坑。若需要处理任意凸体需绕过封装直接使用 Ammo API后文示例会展示。const physics await AmmoPhysics(); // 动态刚体1kg 的箱子恢复系数 0.3 physics.addMesh( box, 1, 0.3 ); // 静态刚体地面 physics.addMesh( ground, 0 );.addScene( scene )把整个场景或任意Object3D子树加入模拟。它会scene.traverse()遍历所有子节点仅处理isMesh且userData.physics字段非空的网格源码 L85-L103function addScene( scene ) { scene.traverse( function ( child ) { if ( child.isMesh ) { const physics child.userData.physics; if ( physics ) { addMesh( child, physics.mass, physics.restitution ); } } } ); }也就是说userData.physics是网格与物理世界的“契约”可以携带mass和restitution两个字段box.userData.physics { mass: 1 }; sphere.userData.physics { mass: 0.5, restitution: 0.6 }; floor.userData.physics { mass: 0 }; // 静态碰撞体这个设计很适合“批量声明式”地为场景装配物理属性先搭好整个场景图再一次性physics.addScene( scene )。没有userData.physics的网格会被自动忽略所以它天然与“普通装饰物体”共存。.setMeshPosition( mesh, position, index )设置已加入模拟的网格的位置。文档特别指出调用该方法会重置该网格当前模拟的线速度与角速度——源码中它对对应刚体先执行setAngularVelocity( 0,0,0 )和setLinearVelocity( 0,0,0 )再用body.setWorldTransform()写入新位置源码 L197-L224。参数类型默认值说明meshMesh必填要更新位置的网格positionVector3必填新位置indexnumber0若网格是InstancedMeshindex表示实例 ID普通Mesh忽略该参数index参数正是该组件支持实例化物理的体现。内部机制运动状态如何回写到渲染网格每帧的step()函数源码 L230-L288是数据同步的核心流程为world.stepSimulation( delta, 10 )推进物理世界遍历mass 0的网格从对应btRigidBody的btDefaultMotionState中取回世界变换普通Mesh直接mesh.position.set(...)/mesh.quaternion.set(...)回写InstancedMesh调用文件末尾的compose()工具函数源码 L336-L364把四元数与位置手工展开为列主序 4x4 矩阵直接写入instanceMatrix.array对应实例的 16 个元素然后设置mesh.instanceMatrix.needsUpdate true并computeBoundingSphere()。对InstancedMesh的建立过程也值得一看handleInstancedMeshL157-L193它读取mesh.instanceMatrix.array每 16 个元素用btTransform.setFromOpenGLMatrix()取出一个实例的初始变换为每个实例创建独立的刚体并把“一个 Mesh 对应一个 body 数组”存入meshMap。这就是setMeshPosition中index参数的用武之地——它选择bodies[index]来单独操作某一个实例。从源码结构看组件还留有扩展占位返回对象附近有一行注释掉的// addCompoundMesh可以推断组合刚体compound shape封装在规划中但尚未实现。实战示例一400400 实例的批量刚体官方示例 examples/physics_ammo_instancing.html 是该组件最完整的用例完整代码可直接阅读该文件关键片段如下import { AmmoPhysics } from three/addons/physics/AmmoPhysics.js; physics await AmmoPhysics(); // 静态地面mass 为 0不参与运动 const floorCollider new THREE.Mesh( new THREE.BoxGeometry( 10, 5, 10 ), new THREE.MeshBasicMaterial( { color: 0x666666 } ) ); floorCollider.position.y - 2.5; floorCollider.userData.physics { mass: 0 }; floorCollider.visible false; scene.add( floorCollider ); // 400 个实例的盒子 const geometryBox new THREE.BoxGeometry( 0.075, 0.075, 0.075 ); boxes new THREE.InstancedMesh( geometryBox, material, 400 ); boxes.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // 每帧更新 boxes.userData.physics { mass: 1 }; scene.add( boxes ); for ( let i 0; i boxes.count; i ) { matrix.setPosition( Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5 ); boxes.setMatrixAt( i, matrix ); } // 400 个实例的二十面体IcosahedronGeometry 会被映射为球体碰撞形状 const geometrySphere new THREE.IcosahedronGeometry( 0.05, 4 ); spheres new THREE.InstancedMesh( geometrySphere, material, 400 ); spheres.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); spheres.userData.physics { mass: 1 }; scene.add( spheres ); // 一次遍历装配整个场景 physics.addScene( scene ); // 每帧随机重置一个实例的位置观察其重新坠落 setInterval( () { let index Math.floor( Math.random() * boxes.count ); position.set( 0, Math.random() 1, 0 ); physics.setMeshPosition( boxes, position, index ); }, 1000 / 60 );这个示例把组件的三个要点全部覆盖了userData.physics声明式装配、InstancedMesh批量刚体、setMeshPosition( mesh, position, index )按实例 ID 重置位置。DynamicDrawUsage的提示也很重要——实例矩阵每帧都会被物理结果改写应告知 GPU 该缓冲是高频写入的。实战示例二封装之外的底层 Ammo.js 用法examples/physics_ammo_break.html 演示的是“凸物体实时破碎”页面用ConvexObjectBreaker把塔楼、桥梁、山体拆成凸碎片当接触冲量超过fractureImpulse 250时对受冲击的凸体递归细分。它没有使用AmmoPhysics封装而是直接操作 Ammo API是理解封装内部机制、也是突破封装限制如仅支持 Box/Sphere 形状的参考实现手动构建世界btDefaultCollisionConfiguration→btCollisionDispatcher→btDbvtBroadphase→btSequentialImpulseConstraintSolver→btDiscreteDynamicsWorld与封装内完全一致任意凸体形状btConvexHullShape逐点addPoint()这是addMesh所不支持的接触点与冲量读取通过dispatcher.getNumManifolds()/getManifoldByIndexInternal( i )遍历接触流形再用contactPoint.getAppliedImpulse()判断是否触发破碎用body.setUserPointer()把 three.js 对象挂到刚体上实现物理回调中反查渲染对象。如果你的需求凹面碰撞、凸包刚体、接触事件回调、约束关节等超出AmmoPhysics三个方法的覆盖范围这个示例展示了在同一套 Ammo API 下自行搭建设计的路径。同一目录下还有physics_ammo_cloth、physics_ammo_rope、physics_ammo_terrain、physics_ammo_volume等示例分别覆盖布料、绳索、地形、体积雾等场景。限制与注意事项结合文档声明与源码实现使用该组件前需要明确以下边界强依赖网络默认从 CDN 拉取固定 commit 的ammo.wasm.js离线环境会加载失败。规避方式是页面预先加载并暴露全局Ammo组件检测到typeof Ammo undefined为假时会跳过注入逻辑。几何体支持有限addMesh/addScene仅支持BoxGeometry与SphereGeometry/IcosahedronGeometry且只读取geometry.parameters不满足条件的几何体仅打印错误日志而不生效。物理参数不可调重力(-9.8)、帧率 60、形状 margin 0.05、每帧最多 10 个固定步均写死在源码中摩擦系数行body.setFriction( 4 )处于注释状态即未显式设置摩擦。setMeshPosition会清零速度用它重置位置时刚体不会保留原来的运动状态而是从静止开始这一点在需要“接住再抛出”的交互设计时要注意。无销毁接口组件返回对象只有三个方法模拟由setInterval常驻驱动页面卸载前需要自行管理InstancedMesh等资源的释放可参考官方手册 manual/pages/how-to-dispose-of-objects.html 的释放流程。引擎维护状态如 manual/pages/physics.html 所述Ammo.js 本身已不再活跃维护新项目若需要长期演进可对比同目录的 examples/jsm/physics/RapierPhysics.js 与 examples/jsm/physics/JoltPhysics.js。参考文件组件文档docs/pages/AmmoPhysics.html.md组件源码examples/jsm/physics/AmmoPhysics.js实例化示例examples/physics_ammo_instancing.html凸体破碎示例底层用法examples/physics_ammo_break.html物理引擎选型说明manual/pages/physics.htmluserData字段说明docs/pages/Object3D.html【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门