首页 > 其他分享 >Three.js 物理引擎入门:与 Ammo.js 搭配实现逼真物理效果

Three.js 物理引擎入门:与 Ammo.js 搭配实现逼真物理效果

时间:2025-01-17 09:58:38浏览次数:3  
标签:const ammoLib Three js new THREE Ammo 物理

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

相关文章

  • 深入理解 Three.js 加载器:如何导入外部模型(GLTF、OBJ、FBX)
    深入理解Three.js加载器:如何导入外部模型(GLTF、OBJ、FBX)Three.js提供了强大的加载器系统,可以轻松地将外部模型(如GLTF、OBJ、FBX等格式)加载到场景中,为你的3D项目增添真实感。在这篇文章中,我们将深入讲解Three.js加载器的使用方法,并结合实际案例展示如何在Vue项......
  • JSON.stringify有什么局限性和哪些技巧?
    JSON.stringify是JavaScript中用于将对象转换为JSON字符串的方法,但它在某些情况下具有局限性,同时也有一些技巧可以帮助开发者更有效地使用它。以下是关于JSON.stringify的局限性和技巧的详细解答:局限性:循环引用问题:当对象之间存在循环引用时,JSON.stringify会抛出错误。例如,一......
  • JS字符串属性与方法大全
    JS字符串属性与方法大全字符串基本概念示例一:创建字符串常用字符串方法详解查找子串示例二:查找子串提取子串示例三:提取子串替换子串示例四:替换子串字符串分割与连接示例五:分割与连接开发技巧与最佳实践在Web前端开发中,JavaScript的字符串处理能力是开发者工具......
  • 【Html.js——页面布局】给页面化个妆(蓝桥杯真题-1769)【合集】
    目录......
  • 【Vue.js——关键字匹配】搜一搜呀(蓝桥杯真题-1762)【合集】
    目录......
  • JSP旅游景点推荐系统06mia(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景随着人们生活水平的提高和旅游观念的转变,越来越多的人倾向于选择个性化、多样化的旅游方式。然而,面对海量的旅游景点信息,游客往往难以......
  • JSP驴友网站管理系统w51si程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、研究背景随着旅游业的繁荣和驴友文化的兴起,越来越多的驴友倾向于通过网络平台获取旅游信息、分享旅行经验。为了满足驴友群体的需求,构建一个功......
  • 基于SSM实现的基于web的汽车售后服务管理系统的设计与实现+jsp源码+论文
    项目简介基于SSM实现的基于web的汽车售后服务管理系统的设计与实现+jsp源码+论文,主要功能如下:审核说明项目收集于互联网,经过我们仔细验证,可以正常运行;本项目属于学习项目,适合个人学习使用,不适合商用;精力有限,运行过程中若有小问题属正常现象,需要自行看源码进行简单的修......
  • 基于SSM实现的青海大学昆仑学院学生档案管理系统的设计+jsp.zip源码+论文
    项目简介基于SSM实现的青海大学昆仑学院学生档案管理系统的设计+jsp.zip源码+论文,主要功能如下:审核说明项目收集于互联网,经过我们仔细验证,可以正常运行;本项目属于学习项目,适合个人学习使用,不适合商用;精力有限,运行过程中若有小问题属正常现象,需要自行看源码进行简单的修......
  • JSP旅游门户网站ud718(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景随着互联网的飞速发展,旅游行业也迎来了数字化转型的浪潮。为了方便游客获取旅游信息、进行在线预订和享受个性化服务,开发一个功能全面......