一、前言
在 Web 3D 开发领域,Three.js 是创建交互式 3D 场景的强大工具,而物理引擎则能为场景赋予真实的物理效果,如碰撞检测、重力模拟、刚体运动等。将 Three.js 与物理引擎结合,能够打造出更加逼真、有趣的 3D 应用,比如物理类游戏、建筑仿真、虚拟实验等。本文将带你深入学习 Three.js 与物理引擎配合使用的核心知识点。
二、Three.js 与物理引擎基础概念
2.1 Three.js 简介
Three.js 是一个基于 JavaScript 的 3D 库,它简化了 WebGL 的复杂操作,提供了直观的 API 用于创建和渲染 3D 场景。通过 Three.js,开发者可以轻松创建几何图形、材质、灯光、相机等 3D 元素,并对它们进行动画和交互操作 。
2.2 物理引擎概述
物理引擎是一种模拟现实世界物理规律的软件库,它能够处理物体之间的碰撞、重力、摩擦力等物理现象。常见的用于 Web 开发的物理引擎有ammo.js 、cannon.js 和matter.js。
- ammo.js:基于 Bullet 物理引擎的 Web 版本,功能强大,支持复杂的物理模拟,但学习曲线相对较陡。
- cannon.js:专门为 JavaScript 设计的物理引擎,使用简单,对 Web 开发者友好,广泛应用于 Three.js 项目中。
- matter.js:轻量级的 2D 物理引擎,如果项目侧重于 2D 物理效果,matter.js 是不错的选择。
在本文中,我们将以cannon.js为例,讲解 Three.js 与物理引擎的配合使用。
三、Three.js 与 cannon.js 的环境搭建
3.1 引入库文件
在 HTML 文件中引入 Three.js 和 cannon.js 库文件,可以通过 CDN 的方式引入:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Three.js与cannon.js示例</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r151/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>
</head>
<body>
<script src="script.js"></script>
</body>
</html>
3.2 初始化 Three.js 场景和 cannon.js 世界
在 JavaScript 文件(如script.js)中,初始化 Three.js 的场景、相机、渲染器,同时创建 cannon.js 的物理世界:
js
// 创建Three.js场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建cannon.js物理世界
const world = new CANNON.World();
// 设置重力(沿y轴负方向,模拟地球重力)
world.gravity.set(0, -9.82, 0);
四、在场景中添加物理对象
4.1 创建 Three.js 对象与 cannon.js 刚体
我们需要为 Three.js 中的 3D 对象创建对应的 cannon.js 刚体,让刚体遵循物理规律运动,再将 Three.js 对象的位置和旋转与刚体同步。
js
// 创建Three.js立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cubeMesh = new THREE.Mesh(geometry, material);
scene.add(cubeMesh);
// 创建cannon.js刚体
const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
const cubeBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 2, 0),
shape: cubeShape
});
world.addBody(cubeBody);
// 同步Three.js对象与cannon.js刚体的位置和旋转
function updateMeshPosition() {
cubeMesh.position.x = cubeBody.position.x;
cubeMesh.position.y = cubeBody.position.y;
cubeMesh.position.z = cubeBody.position.z;
cubeMesh.quaternion.setFromEuler(cubeBody.quaternion.x, cubeBody.quaternion.y, cubeBody.quaternion.z, cubeBody.quaternion.w);
}
4.2 创建地面
在物理模拟中,地面是不可或缺的元素,用于承接物体的碰撞和支撑。
js
// 创建Three.js平面
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.rotation.x = -Math.PI / 2;
scene.add(planeMesh);
// 创建cannon.js平面刚体(质量为0表示静态刚体)
const planeShape = new CANNON.Plane();
const planeBody = new CANNON.Body({
mass: 0,
position: new CANNON.Vec3(0, 0, 0),
shape: planeShape
});
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(planeBody);
五、物理模拟循环
为了让物理模拟持续进行并更新 Three.js 场景,我们需要创建一个动画循环,在每次循环中更新物理世界并同步对象状态。
js
function animate() {
requestAnimationFrame(animate);
// 更新cannon.js物理世界
world.step(1 / 60);
// 更新Three.js对象的位置和旋转
updateMeshPosition();
renderer.render(scene, camera);
}
animate();
六、碰撞检测与响应
6.1 监听碰撞事件
cannon.js 提供了碰撞事件的监听机制,我们可以通过它来实现当物体发生碰撞时的特定响应。
js
// 监听碰撞事件
world.addEventListener('collide', function (event) {
console.log('发生碰撞!');
// 可以在这里添加碰撞后的逻辑,比如改变物体颜色、播放音效等
});
6.2 自定义碰撞响应
除了简单的日志输出,我们还可以根据碰撞的对象进行更复杂的响应。例如,当立方体与地面碰撞时,改变立方体的颜色。
ini
world.addEventListener('collide', function (event) {
const bodyA = event.bodyA;
const bodyB = event.bodyB;
if ((bodyA === cubeBody && bodyB === planeBody) || (bodyA === planeBody && bodyB === cubeBody)) {
cubeMesh.material.color.set(0xff0000);
}
});
七、更复杂的物理效果与应用
7.1 多物体交互
在实际项目中,往往会有多个物体相互作用。我们可以按照上述方法,创建更多的 Three.js 对象和对应的 cannon.js 刚体,让它们在物理世界中产生复杂的交互。比如创建多个立方体,让它们相互碰撞、堆叠。
js
// 创建多个立方体
const numCubes = 5;
const cubes = [];
for (let i = 0; i < numCubes; i++) {
const cubeMesh = new THREE.Mesh(geometry, material);
cubeMesh.position.x = i - (numCubes - 1) / 2;
cubeMesh.position.y = 2;
scene.add(cubeMesh);
const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
const cubeBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(i - (numCubes - 1) / 2, 2, 0),
shape: cubeShape
});
world.addBody(cubeBody);
cubes.push({ mesh: cubeMesh, body: cubeBody });
}
// 更新多个立方体的位置和旋转
function updateCubesPosition() {
cubes.forEach(cube => {
cube.mesh.position.x = cube.body.position.x;
cube.mesh.position.y = cube.body.position.y;
cube.mesh.position.z = cube.body.position.z;
cube.mesh.quaternion.setFromEuler(cube.body.quaternion.x, cube.body.quaternion.y, cube.body.quaternion.z, cube.body.quaternion.w);
});
}
// 在动画循环中调用更新函数
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60);
updateCubesPosition();
renderer.render(scene, camera);
}
animate();
7.2 力与约束
cannon.js 还支持施加力和添加约束,比如为物体添加弹簧约束,模拟弹性连接;或者施加持续的力,让物体运动。
js
// 为立方体添加弹簧约束
const springConstraint = new CANNON.Spring(cubeBody, planeBody, {
restLength: 1,
stiffness: 100,
damping: 10
});
world.addConstraint(springConstraint);
// 为立方体施加力
function applyForce() {
cubeBody.applyForce(new CANNON.Vec3(1, 0, 0), cubeBody.position);
}
// 可以在合适的时机调用applyForce函数,比如用户点击事件
八、总结与拓展
通过本文的学习,你已经掌握了 Three.js 与 cannon.js 配合使用的基本流程,包括环境搭建、创建物理对象、实现物理模拟循环、碰撞检测与响应,以及一些复杂物理效果的实现。后续你可以尝试使用其他物理引擎(如 ammo.js),探索更多高级功能,比如布料模拟、流体模拟等;也可以将这些技术应用到实际项目中,如开发 3D 游戏、虚拟展厅等。在实践过程中不断积累经验,提升自己在 Web 3D 开发领域的能力。
上述内容涵盖了 Three.js 与物理引擎配合的主要知识点与实践方法。你可以说说对内容深度、篇幅的看法,或想了解的其他拓展内容,我继续完善。