7.4 物理引擎联动:给装配线加上碰撞台 本节摘要:Three.js 本体不认识物理,物理引擎是外聘的仿真工头:它每帧算坐标,Three.js 照单摆放。本节讲清两套世界解耦对账的合同结构,用翻斗卸货演示刚体与网格的联动流程,并交代"哪些该物理、哪些该假装物理"的取舍线。 仿真工头与展示部门的合同 很多初学者以为 Three.js 里"接个物理"是加个插件那么简单——实际接入的是一整套世界观。Three.js 是展示部门:维护画面里"东西看起来在哪";物理引擎是仿真工头:维护仿真世界里"东西实际在哪"。两套世界彼此独立,靠一份每帧对账的合同协作:工头 step 一步仿真、报出每个刚体的新坐标,展示部门把这些坐标抄给自己的网格对象。
本节摘要:Three.js 本体不认识物理,物理引擎是外聘的仿真工头:它每帧算坐标,Three.js 照单摆放。本节讲清两套世界解耦对账的合同结构,用翻斗卸货演示刚体与网格的联动流程,并交代"哪些该物理、哪些该假装物理"的取舍线。
很多初学者以为 Three.js 里"接个物理"是加个插件那么简单——实际接入的是一整套世界观。Three.js 是展示部门:维护画面里"东西看起来在哪";物理引擎是仿真工头:维护仿真世界里"东西实际在哪"。两套世界彼此独立,靠一份每帧对账的合同协作:工头 step 一步仿真、报出每个刚体的新坐标,展示部门把这些坐标抄给自己的网格对象。
理解了合同结构,物理引擎的选型就只是选工头:轻量刚体需求(掉落、碰撞、翻倒)选 cannon-es 这类纯 JS 方案;高精度与大量刚体选由专业物理内核编译到 Web 的方案(如 Rapier)。无论哪家,接入流程都是同一个三步:建仿真世界,登记"刚体与网格"的对,每帧 step 后对账。
休眠机制是工头送的一份大礼:稳定下来的刚体不再参与逐帧求解, CPU 直接放空——一吨货倾泻而下时,前几秒热闹、后几秒安静,靠的就是它。
背景:沙盘要加一段"翻斗车倾泻货箱"的演示:二十只小货箱随翻斗倾斜滚落到地面,互相碰撞堆叠,全程不穿模、不爆帧。
操作:按合同三步走:建仿真世界,地面与货箱登记成对,每帧 step 后对账。
import * as CANNON from 'cannon-es'; // 1. 建仿真世界 const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) }); world.broadphase = new CANNON.SAPBroadphase(world); // 空间扫描粗筛,万级刚体友好 world.allowSleep = true; // 打开休眠,稳了就不再算 // 2. 登记成对:地面一对、货箱二十对 const groundBody = new CANNON.Body({ mass: 0, // 质量零 = 静态,地面永不动 shape: new CANNON.Plane() }); groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0); // 平面默认朝上,转成地面向天 world.addBody(groundBody); const crates = []; for (let i = 0; i < 20; i++) { const body = new CANNON.Body({ mass: 2, // 动态刚体:有质量才参与仿真 shape: new CANNON.Box(new CANNON.Vec3(0.2, 0.2, 0.2)), position: new CANNON.Vec3( (Math.random() - 0.5) * 0.6, 3 + i * 0.5, (Math.random() - 0.5) * 0.6 ) }); world.addBody(body); const mesh = new THREE.Mesh( new THREE.BoxGeometry(0.4, 0.4, 0.4), sharedCrateMat // 共料:7.3 的纪律 ); stage.scene.add(mesh); crates.push({ body, mesh }); } // 3. 每帧对账:工头 step,展示部门照单摆放 stage.loop((dt) => { world.step(1 / 60, Math.min(dt, 0.05), 3); // 固定步长 钳制dt 最多补3步 for (const c of crates) { c.mesh.position.copy(c.body.position); c.mesh.quaternion.copy(c.body.quaternion); } stage.renderer.render(stage.scene, stage.camera); });
结果:翻斗一倾,货箱翻滚落地、彼此碰撞、堆成随机的小丘,静止后彻底安静(休眠生效);再倒一箱,砸中处被唤醒重新翻滚——仿真世界的因果链与直觉完全一致。
解读:三处细节是合同的关键条款。第一,world.step 的三参形态:固定步长 1/60 保证仿真确定性(同一初值永远同一结果),后两参让真实帧间隔最多拆成 3 个补步——长帧钳制与 6.1 是同一个思想在物理侧的再现。第二,静态物用 mass 0:地面、墙、斗体都不该被撞动,静态刚体还省下求解开销。第三,quad 形状对齐:CANNON.Box 的参数是"半边长",与 BoxGeometry 的"全边长"差一倍,忘除以 2 的现象是所有货箱凭空浮起一格——物理对接的报障榜常客。
变式:取舍线在本节收尾: twenty 只货箱值得真物理,两万颗碎石绝不该真物理——常见的"假装物理"手法是预烘焙(动画师在建模软件里调好翻滚动画,运行时当剪辑播放,6.2 的 mixer 直接接管),观感相近、帧开销归零。判定标准是"交互性":用户或玩法会实时改变落点与堆叠,才值得真仿真;纯演示型倾泻,烘焙永远是首选。
再补一条材料对照的实务:物理形状不必与显示网格一一对应——显示用的高模加物理用的低模(甚至一个包围盒)是行业标准做法,二十只货箱用盒形,复杂的叉车用几个盒拼合近似即可;物理形状的面数直接计入求解开销,能简则简。同理,地面用无限平面、墙体用薄盒,永远别把渲染网格原样交给仿真工头——它只关心碰撞正确性,不关心造型精度。
精装修工段全部完工。下一章进质检与出货:帧预算的仪表盘、优化清单的过检流程、生态与实战的收束——产线要交货了。