在 Three.js 的魔法世界里,我们已经能够创造出美轮美奂的 3D 场景,那些炫酷的模型、变幻的光影让人沉醉。但如果这些物体只是静静地待在那里,像被施了定身咒,是不是总觉得少了点什么?别担心,今天我们就来打破这个 "魔咒",借助物理引擎,让物体之间的交互遵循真实世界的物理规则,实现超真实的物体碰撞交互,比如像在现实中一样拖拽、抛掷物体。
初识物理引擎:Three.js 世界的 "物理老师"
想象一下,Three.js 搭建的 3D 世界是一个巨大的游乐场,而物理引擎就是这个游乐场里掌管规则的 "物理老师"。没有它的时候,物体之间的互动可以说是 "随心所欲",一个小球撞到墙壁上,可能直接穿过去,仿佛墙壁是空气;而有了物理引擎,它会告诉所有物体:"嘿!在我的地盘,得按物理规则来!"
目前,在 Three.js 中常用的物理引擎有ammo.js 和cannon.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 世界变得更加精彩吧!
上述文章涵盖了从基础搭建到交互实现的全过程。你对文章的内容深度、代码示例是否满意?若有其他修改方向,欢迎随时告诉我。