Three.js 奇幻物理世界:让你的 3D 物体跳起真实的华尔兹

在 Three.js 的魔法世界里,我们已经能够创造出美轮美奂的 3D 场景,那些炫酷的模型、变幻的光影让人沉醉。但如果这些物体只是静静地待在那里,像被施了定身咒,是不是总觉得少了点什么?别担心,今天我们就来打破这个 "魔咒",借助物理引擎,让物体之间的交互遵循真实世界的物理规则,实现超真实的物体碰撞交互,比如像在现实中一样拖拽、抛掷物体。

初识物理引擎:Three.js 世界的 "物理老师"

想象一下,Three.js 搭建的 3D 世界是一个巨大的游乐场,而物理引擎就是这个游乐场里掌管规则的 "物理老师"。没有它的时候,物体之间的互动可以说是 "随心所欲",一个小球撞到墙壁上,可能直接穿过去,仿佛墙壁是空气;而有了物理引擎,它会告诉所有物体:"嘿!在我的地盘,得按物理规则来!"

目前,在 Three.js 中常用的物理引擎有ammo.jscannon.js。它们就像是不同风格的 "物理老师",ammo.js 更像是一位严厉但高效的老教授,擅长处理复杂的物理模拟;cannon.js 则像是一位亲切的年轻讲师,上手更加容易,对初学者十分友好。这里我们以 cannon.js 为例,开启这场奇妙的物理交互之旅。

搭建舞台:引入物理引擎与 Three.js 的融合

在开始编写代码之前,我们需要先把 cannon.js 这个 "物理老师" 邀请到 Three.js 的世界里。就像举办一场盛大的派对,要提前给嘉宾发好邀请函。在 HTML 文件中,通过

xml 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>

接下来,在 JavaScript 代码中,我们要创建 Three.js 的场景、相机和渲染器,这是搭建 3D 世界的基础工作。就好比建造一座房子,得先打好地基。

ini 复制代码
// 创建场景
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 和 Three.js 的世界连接起来。创建一个 cannon.js 的世界,就像在 Three.js 的游乐场里划分出一个遵循物理规则的区域。

csharp 复制代码
// 创建cannon.js世界
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // 设置重力,让物体能像在现实中一样下落

这里设置的重力,就像是给这个物理区域施加了 "地心引力",物体不再能随意漂浮,而是会乖乖下落。

创造角色:将 Three.js 物体赋予物理属性

在 Three.js 的世界里,我们已经会创建各种物体,比如正方体、球体等,这些物体就像是舞台上的演员。但现在,我们要给这些演员穿上 "物理戏服",让它们遵循物理规则。

以创建一个正方体为例,先在 Three.js 中创建一个普通的正方体:

ini 复制代码
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

然后,在 cannon.js 的世界里,为这个正方体创建对应的物理体,给它赋予质量和形状:

arduino 复制代码
const cubeBody = new CANNON.Body({
  mass: 1, // 物体质量,质量会影响物体的惯性等物理特性
  position: new CANNON.Vec3(0, 1, 0), // 初始位置
  shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)) // 形状,与Three.js中的正方体尺寸对应
});
world.addBody(cubeBody);

这里的质量就像是物体的 "体重",体重越大,就越不容易被推动;形状则保证了物理体和 Three.js 物体的外观一致。

但是,现在 cannon.js 世界里的物理体和 Three.js 世界里的物体还没有建立联系,我们需要把它们 "绑定" 在一起,就像给演员和他们的戏服牢牢固定住。

scss 复制代码
function updatePhysics() {
  world.step(1 / 60); // 更新物理世界,就像让时间往前走
  cube.position.copy(cubeBody.position);
  cube.quaternion.copy(cubeBody.quaternion);
}

在渲染循环中调用updatePhysics函数,这样物理体的位置和旋转信息就会同步到 Three.js 物体上,它们就能一起 "行动" 了。

实现交互:拖拽与抛掷,让物体动起来

现在,我们终于来到了最激动人心的部分 ------ 实现物体的拖拽和抛掷。这就像是给演员们安排精彩的剧情,让它们在舞台上尽情表演。

首先,实现拖拽功能。我们需要监听鼠标事件,当鼠标按下时,记录鼠标位置和物体的初始位置;当鼠标移动时,根据鼠标移动的距离来移动物体;当鼠标松开时,停止拖拽。

ini 复制代码
let isDragging = false;
let startMouseX, startMouseY;
let startCubeX, startCubeY;
function onMouseDown(event) {
  event.preventDefault();
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects([cube]);
  if (intersects.length > 0) {
    isDragging = true;
    startMouseX = event.clientX;
    startMouseY = event.clientY;
    startCubeX = cube.position.x;
    startCubeY = cube.position.y;
  }
}
function onMouseMove(event) {
  if (isDragging) {
    const dx = event.clientX - startMouseX;
    const dy = event.clientY - startMouseY;
    cubeBody.position.x = startCubeX + dx * 0.01; // 调整系数控制拖拽灵敏度
    cubeBody.position.y = startCubeY + dy * 0.01;
    startMouseX = event.clientX;
    startMouseY = event.clientY;
  }
}
function onMouseUp() {
  isDragging = false;
}
document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);

而对于抛掷物体,我们可以根据鼠标松开时的速度来给物体一个初始的力。想象一下,我们用力扔出一个球,球会带着我们施加的力飞出去。

ini 复制代码
function onMouseUp() {
  if (isDragging) {
    const endMouseX = event.clientX;
    const endMouseY = event.clientY;
    const dx = endMouseX - startMouseX;
    const dy = endMouseY - startMouseY;
    // 根据鼠标移动距离计算抛掷的力
    cubeBody.velocity.x = dx * 0.1;
    cubeBody.velocity.y = dy * 0.1;
  }
  isDragging = false;
}

这样,我们就实现了物体的拖拽和抛掷,而且这些动作都遵循物理规则。当物体撞到其他物体或墙壁时,会像在现实中一样反弹、滚动,是不是很神奇?

完善场景:添加更多物体与互动

为了让这个物理世界更加丰富有趣,我们可以添加更多的物体,比如地面、其他形状的物体等。给地面创建一个物理体时,因为地面是固定不动的,所以它的质量设为 0:

php 复制代码
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({
  mass: 0,
  position: new CANNON.Vec3(0, 0, 0),
  quaternion: new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
});
groundBody.addShape(groundShape);
world.addBody(groundBody);

同时,也创建对应的 Three.js 物体添加到场景中,让它们和物理体同步,这样整个场景就更加真实了。

总结与展望

通过将 cannon.js 物理引擎集成到 Three.js 中,我们成功地实现了基于物理的交互,让 3D 物体之间的碰撞和运动变得真实有趣。这只是物理交互的入门,在这个充满无限可能的 Three.js 世界里,还有更多好玩的功能等待我们去探索。比如添加更多复杂的物理效果,模拟流体、布料等;或者结合更多的传感器,实现更丰富的交互方式。快发挥你的想象力,让你的 3D 世界变得更加精彩吧!

上述文章涵盖了从基础搭建到交互实现的全过程。你对文章的内容深度、代码示例是否满意?若有其他修改方向,欢迎随时告诉我。

相关推荐
tianzhiyi1989sq12 分钟前
Vue框架深度解析:从Vue2到Vue3的技术演进与实践指南
前端·javascript·vue.js
秉承初心19 分钟前
webpack和vite对比解析(AI)
前端·webpack·node.js
团酱20 分钟前
sass-loader与webpack版本冲突解决方案
前端·vue.js·webpack·sass
我是来人间凑数的25 分钟前
electron 配置特定文件右键打开
前端·javascript·electron
安心不心安1 小时前
React封装框架dvajs(状态管理+异步操作+数据订阅等)
前端·react.js·前端框架
未来之窗软件服务1 小时前
js调用微信支付 第二步 获取access_token ——仙盟创梦IDE
开发语言·javascript·微信·微信支付·仙盟创梦ide·东方仙盟
洛小豆2 小时前
为什么可以通过域名访问接口,但不能通过IP地址访问接口?
前端·javascript·vue.js
要加油哦~2 小时前
vue | rollup 打包 | 配置 rollup.config.js 文件,更改 rollup的行为
前端
洛小豆2 小时前
她问我Pinia两种Store定义方式,到底选哪种写法,我说我也不知道...
前端·vue.js·代码规范
ew452182 小时前
【VUE】某时间某空间占用情况效果展示,vue2+element ui实现。场景:会议室占用、教室占用等。
前端·vue.js·ui·elementui