Three.js 物理引擎入门:与 Ammo.js 搭配实现逼真物理效果
3D 场景中的物理效果(如重力、碰撞、弹性反弹等)是让用户体验更加逼真的关键。Three.js 本身并不包含物理引擎,但可以结合第三方物理引擎来实现真实的物理模拟,例如 Ammo.js。
在这篇文章中,我们将介绍如何结合 Three.js 和 Ammo.js,快速实现重力、碰撞等物理效果。
一、什么是 Ammo.js?
Ammo.js 是基于 Bullet Physics Library 的 JavaScript 版本,它使用 WebAssembly 实现高效的物理模拟,适合在浏览器中运行。Ammo.js 可以处理以下常见的物理效果:
- 刚体动力学(Rigid Body Dynamics)
- 碰撞检测
- 物体约束(Constraint)
- 软体动力学(Soft Body Dynamics)
二、项目搭建
在使用 Ammo.js 和 Three.js 前,需要安装它们的依赖。
1. 安装依赖
可以通过 npm 安装 Three.js 和 Ammo.js:
npm install three @enable3d/ammo-on-node
2. 加载 Ammo.js
由于 Ammo.js 是基于 WebAssembly 的库,它的加载方式稍有不同。你需要异步加载它:
import * as THREE from 'three';
import Ammo from 'ammo.js';
let ammoLib;
Ammo().then((AmmoLib) => {
ammoLib = AmmoLib;
init(); // 初始化场景
});
三、基本的 Three.js 场景
在加入物理效果之前,先创建一个简单的 Three.js 场景,包含地板和立方体。
1. 初始化场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10);
scene.add(light);
// 创建地板
const floorGeometry = new THREE.BoxGeometry(20, 1, 20);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.set(0, -0.5, 0);
scene.add(floor);
// 创建一个立方体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 5, 0);
scene.add(cube);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
现在,我们有一个简单的场景,接下来为其添加物理效果。
四、引入 Ammo.js 实现物理效果
1. 初始化 Ammo.js 物理世界
Ammo.js 需要一个 物理世界(Ammo.btDiscreteDynamicsWorld
)来管理刚体、碰撞和物理运算。
let physicsWorld;
function initPhysicsWorld() {
const collisionConfiguration = new ammoLib.btDefaultCollisionConfiguration();
const dispatcher = new ammoLib.btCollisionDispatcher(collisionConfiguration);
const broadphase = new ammoLib.btDbvtBroadphase();
const solver = new ammoLib.btSequentialImpulseConstraintSolver();
physicsWorld = new ammoLib.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
physicsWorld.setGravity(new ammoLib.btVector3(0, -9.8, 0)); // 设置重力加速度
}
2. 创建物理刚体
每个物理对象需要一个 刚体,以描述它在物理世界中的行为。刚体包含以下内容:
- 形状:物体的碰撞几何形状。
- 质量:决定重力对物体的影响。
- 运动状态:定义物体初始位置和速度。
定义辅助函数
为了简化代码,定义一个函数来创建物理对象:
function createRigidBody(threeObject, physicsShape, mass) {
const transform = new ammoLib.btTransform();
transform.setIdentity();
const position = threeObject.position;
transform.setOrigin(new ammoLib.btVector3(position.x, position.y, position.z));
const motionState = new ammoLib.btDefaultMotionState(transform);
const localInertia = new ammoLib.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new ammoLib.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
const body = new ammoLib.btRigidBody(rbInfo);
return body;
}
创建地板刚体
地板的质量为 0,因此它是一个静态物体。
const floorShape = new ammoLib.btBoxShape(new ammoLib.btVector3(10, 0.5, 10));
const floorBody = createRigidBody(floor, floorShape, 0);
physicsWorld.addRigidBody(floorBody);
创建立方体刚体
立方体的质量不为 0,因此它会受重力影响。
const cubeShape = new ammoLib.btBoxShape(new ammoLib.btVector3(0.5, 0.5, 0.5));
const cubeBody = createRigidBody(cube, cubeShape, 1);
physicsWorld.addRigidBody(cubeBody);
3. 更新物理世界
每帧更新时,调用物理世界的 stepSimulation
方法,并将 Ammo.js 的物体位置同步到 Three.js 的对象上。
function updatePhysics(deltaTime) {
physicsWorld.stepSimulation(deltaTime, 10);
const transform = new ammoLib.btTransform();
cubeBody.getMotionState().getWorldTransform(transform);
const origin = transform.getOrigin();
cube.position.set(origin.x(), origin.y(), origin.z());
}
function animate() {
const deltaTime = clock.getDelta();
updatePhysics(deltaTime);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
五、完整代码
以下是完整的代码结构:
import * as THREE from 'three';
import Ammo from 'ammo.js';
let physicsWorld, ammoLib, cubeBody;
Ammo().then((AmmoLib) => {
ammoLib = AmmoLib;
initPhysicsWorld();
initScene();
});
function initPhysicsWorld() {
const collisionConfiguration = new ammoLib.btDefaultCollisionConfiguration();
const dispatcher = new ammoLib.btCollisionDispatcher(collisionConfiguration);
const broadphase = new ammoLib.btDbvtBroadphase();
const solver = new ammoLib.btSequentialImpulseConstraintSolver();
physicsWorld = new ammoLib.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
physicsWorld.setGravity(new ammoLib.btVector3(0, -9.8, 0));
}
function initScene() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10);
scene.add(light);
const floorGeometry = new THREE.BoxGeometry(20, 1, 20);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floor);
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 5, 0);
scene.add(cube);
const floorShape = new ammoLib.btBoxShape(new ammoLib.btVector3(10, 0.5, 10));
const floorBody = createRigidBody(floor, floorShape, 0);
physicsWorld.addRigidBody(floorBody);
const cubeShape = new ammoLib.btBoxShape(new ammoLib.btVector3(0.5, 0.5, 0.5));
cubeBody = createRigidBody(cube, cubeShape, 1);
physicsWorld.addRigidBody(cubeBody);
function createRigidBody(threeObject, physicsShape, mass) {
const transform = new ammoLib.btTransform();
transform.setIdentity();
const position = threeObject.position;
transform.setOrigin(new ammoLib.btVector3
(position.x, position.y, position.z));
const motionState = new ammoLib.btDefaultMotionState(transform);
const localInertia = new ammoLib.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
const rbInfo = new ammoLib.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
return new ammoLib.btRigidBody(rbInfo);
}
function updatePhysics(deltaTime) {
physicsWorld.stepSimulation(deltaTime, 10);
const transform = new ammoLib.btTransform();
cubeBody.getMotionState().getWorldTransform(transform);
const origin = transform.getOrigin();
cube.position.set(origin.x(), origin.y(), origin.z());
}
const clock = new THREE.Clock();
function animate() {
const deltaTime = clock.getDelta();
updatePhysics(deltaTime);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
}
六、总结
通过本文,你学习了如何结合 Three.js 和 Ammo.js,构建一个具有基本物理效果的 3D 场景。这只是开始,你可以进一步探索:
- 添加更多刚体形状(如球体、圆柱体等)。
- 实现物体间的复杂约束(如铰链、滑动等)。
- 优化性能以适应复杂的物理场景。
快开始试试吧!
标签:const,ammoLib,Three,js,new,THREE,Ammo,物理 From: https://blog.csdn.net/mmc123125/article/details/145187102