通过构建一个酷炫的 WebGL Babylon.js 演示并结合 Oimo.js 来理解碰撞与物理






4.11/5 (2投票s)
今天,我想通过使用 WebGL babylon.js 引擎和一个名为 oimo.js 的物理引擎伴侣,与您分享碰撞、物理和边界框的基础知识。
这是我们将一起构建的演示: babylon.js Espilit 物理演示与 Oimo.js
您可以在支持 WebGL 的浏览器中启动它——例如 IE11、Firefox、Chrome、Opera Safari 8,或者 Windows 10 技术预览版中的 Microsoft Edge——然后,像在 FPS 游戏中一样在场景中移动。按“s”键可以发射一些球体/球,按“b”键可以发射一些箱体。使用鼠标,您还可以单击其中一个球体或箱体来施加一些冲量。
理解碰撞
查看维基百科 碰撞检测 的定义,我们可以读到:“碰撞检测通常指的是检测两个或多个物体相交的计算问题。虽然该主题最常与其在 电子游戏和其他 物理模拟中的应用相关联,但它在 机器人学中也有应用。除了确定两个物体是否发生碰撞外,碰撞检测系统还可以计算碰撞时间 (TOI),并报告接触点(相交点的集合)。[1] 碰撞响应处理模拟检测到碰撞时发生的情况(参见 物理引擎, 布娃娃物理)。解决碰撞检测问题需要广泛运用 线性代数和 计算几何中的概念。”
现在,让我们将该定义解构到一个炫酷的 3D 场景中,该场景将作为本教程的起点。
您可以像在现实世界中一样在这个宏伟的博物馆中移动。您不会穿过地板、墙壁或飞起来。我们正在模拟重力。这一切看似很明显,但需要大量的计算才能在 3D 虚拟世界中进行模拟。当我们考虑碰撞检测时,需要解决的第一个问题是它的复杂程度应该是多少?事实上,测试两个复杂网格是否发生碰撞可能会消耗大量 CPU,在 JavaScript 引擎中更是如此,因为它很难将这些计算转移到 UI 线程之外。
为了更好地理解我们如何处理这种复杂性,请导航到 Espilit 博物馆,靠近这张桌子。
您会被桌子挡住,即使右侧似乎有空隙。这是我们碰撞算法中的一个错误吗?不,不是(babylon.js 没有错误!;-))。这是因为 3D 艺术家 Michel Rousseau 构建此场景时,是出于选择。为了简化碰撞检测,他使用了一个特定的碰撞器。
什么是碰撞器?
与其测试与完整详细网格的碰撞,不如将它们放入简单的不可见几何体中。这些碰撞器将充当网格的表示,并由碰撞引擎代替使用。大多数情况下,您不会看到差异,但这将使我们能够使用更少的 CPU,因为其背后的数学计算要简单得多。
每个引擎至少支持两种类型的碰撞器:边界框和边界球体。通过查看这张图片,您会更好地理解。
这张漂亮的黄色甲板是要显示的网格。与其测试它每个面的碰撞,不如尝试将其插入到最佳的边界几何体中。在这种情况下,箱体似乎比球体更能充当网格的替代体。但选择确实取决于网格本身。
让我们回到 Espilit 场景,并将不可见的边界元素显示为半透明的红色。
现在您应该明白为什么您无法向右侧移动。这是因为您(或者说 babylon.js 相机)正在与这个箱体发生碰撞。如果您想这样做,只需降低宽度即可更改其大小,使其完美地贴合桌子的宽度。
注意:如果您想开始学习 babylon.js,可以关注我们的Microsoft Virtual Academy (MVA) 上的免费培训课程。例如,您可以直接跳转到“使用 HTML5 和 Babylon.js 进行 WebGL 3D 入门 - Babylon.js 入门”,其中我们涵盖了 Babylon.js 的碰撞部分。您还可以查看我们交互式 Playground 工具中的代码:Babylon.js Playground - 碰撞示例。
根据碰撞或物理引擎的复杂性,还有其他类型的碰撞器可用:例如胶囊体和网格。
胶囊体对于人类或类人生物很有用,因为它比箱体或球体更适合我们的身体。网格几乎从不等于完整的网格本身——而只是您目标原始网格的简化版本——但仍然比箱体、球体或胶囊体更精确。
加载起始场景
要加载我们的 Espilit 场景,您有多种选择。
选项 1:从我们的 GitHub 存储库下载,然后按照我们 MVA 课程的“使用 HTML5 和 Babylon.js 进行 WebGL 3D 入门 - 加载资源”模块学习如何加载 .babylon 场景。基本上,您需要将资源和 Babylon.js 引擎托管在 Web 服务器上,并为 .babylon 扩展设置正确的 MIME 类型。
选项 2:下载 此预制 Visual Studio 解决方案(.zip 文件)。
注意:如果您不熟悉 Visual Studio,请参阅这篇文章: Web 开发者,Visual Studio 可以成为一个很棒的免费开发工具…… 请注意,Pro 版本现在对许多不同场景都是免费的。它名为 Visual Studio 2013 Community Edition。
当然,如果您不想使用 Visual Studio,仍然可以继续本教程。这是加载场景的代码。请记住,现在大多数浏览器都支持 WebGL——请记住 测试 Internet Explorer,即使在您的 Mac 上也是如此。
/// <reference path="/scripts/babylon.js" /> var engine; var canvas; var scene; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { canvas = document.getElementById("renderCanvas"); engine = new BABYLON.Engine(canvas, true); BABYLON.SceneLoader.Load("Espilit/", "Espilit.babylon", engine, function (loadedScene) { scene = loadedScene; // Wait for textures and shaders to be ready scene.executeWhenReady(function () { // Attach camera to canvas inputs scene.activeCamera.attachControl(canvas); // Once the scene is loaded, just register a render loop to render it engine.runRenderLoop(function () { scene.render(); }); }); }, function (progress) { // To do: give progress feedback to user }); } }
使用此材料,您将仅受益于 Babylon.js 的内置碰撞引擎。事实上,我们在碰撞引擎和物理引擎之间进行了区分。碰撞引擎主要用于相机与场景的交互。您可以启用或禁用相机的重力,可以为相机和各种网格启用“checkCollision”选项。碰撞引擎还可以帮助您了解两个网格是否正在发生碰撞。但仅此而已(实际上已经很多了!)。碰撞引擎不会在两个 Babylon.js 对象发生碰撞后生成动作、力和冲量。您需要一个物理引擎来为对象赋予生命。
我们将物理集成到 Babylon.js 中的方式是通过插件机制。您可以在此处阅读更多关于此的信息: 为 Babylon.js 添加您自己的物理引擎插件。我们支持两个开源物理引擎:cannon.js 和 oimo.js。 Oimo 现在是首选的默认物理引擎。
如果您选择了“选项 1”来加载场景,那么您需要 从我们的 GitHub 下载 Oimo.js。这是我们进行了一些更新的版本,以便更好地支持 Babylon.js。如果您选择了“选项 2”,它已经在 VS 解决方案的“scripts”文件夹中被引用并可用。
在场景中启用物理支持并转换碰撞器为物理替代体
首先要做的是在场景中启用物理。为此,请添加这行代码。
scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.OimoJSPlugin()); //scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.CannonJSPlugin());
您正在设置重力水平(在此示例代码中为 Y 轴上的 -10,这与地球上的重力差不多)以及您想使用的物理引擎。我们将使用 Oimo.js,但注释掉的行显示了如何使用 cannon.js。
现在,我们需要遍历所有非可见的碰撞器,由碰撞引擎使用,并激活其物理属性。为此,您只需找到所有“checkCollisions”设置为 true 但在场景中不可见的网格。
for (var i = 1; i < scene.meshes.length; i++) { if (scene.meshes[i].checkCollisions && scene.meshes[i].isVisible === false) { scene.meshes[i].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0, friction: 0.5, restitution: 0.7 }); meshesColliderList.push(scene.meshes[i]); } }
请也声明 meshesColliderList。
var meshesColliderList = [];
这样我们就完成了!我们已经准备好将一些物体扔进场景,并在这个美丽但过于平静的博物馆里制造一些混乱。
创建具有物理状态的球体和箱体
我们现在将向场景中添加一些球体(带有 Amiga 纹理)和一些箱体(带有木纹)。这些网格将具有物理状态。例如,这意味着如果您将它们扔到空中,它们会在地板上反弹,在检测到碰撞后在它们之间反弹等等。物理引擎需要知道您想为网格使用哪种类型的替代体(今天有平面、球体或箱体),以及质量和摩擦属性。
如果您选择了“选项 1”,可以在此处下载两个纹理: physicsassets.zip
将此代码添加到您的项目中。
function CreateMaterials() { materialAmiga = new BABYLON.StandardMaterial("amiga", scene); materialAmiga.diffuseTexture = new BABYLON.Texture("assets/amiga.jpg", scene); materialAmiga.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5); materialAmiga.diffuseTexture.uScale = 5; materialAmiga.diffuseTexture.vScale = 5; materialWood = new BABYLON.StandardMaterial("wood", scene); materialWood.diffuseTexture = new BABYLON.Texture("assets/wood.jpg", scene); materialWood.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5); } function addListeners() { window.addEventListener("keydown", function (evt) { // s for sphere if (evt.keyCode == 83) { for (var index = 0; index < 25; index++) { var sphere = BABYLON.Mesh.CreateSphere("Sphere0", 10, 0.5, scene); sphere.material = materialAmiga; sphere.position = new BABYLON.Vector3(0 + index / 10, 3, 5 + index / 10); sphere.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, { mass: 1 }); } } // b for box if (evt.keyCode == 66) { for (var index = 0; index < 10; index++) { var box0 = BABYLON.Mesh.CreateBox("Box0", 0.5, scene); box0.position = new BABYLON.Vector3(0 + index / 5, 3, 5 + index / 5); box0.material = materialWood; box0.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 4 }); } } }); }
您可以看到箱体的重量是球体的 4 倍。
注意:如果您需要了解 babylon.js 中材质的工作原理,请观看此模块: 使用 HTML5 和 Babylon.js 进行 WebGL 3D 入门 - 理解材质和输入,或尝试我们专门的 Playground 示例: Babylon.js Playground - 材质示例。
在 scene.enablePhysics 行之后添加这两行代码。
CreateMaterials(); addListeners();
然后启动 Web 项目。导航到博物馆中心,然后按“s”或“b”键。您将获得这个有趣的结果。
添加拾取支持以点击网格
让我们添加另一个很酷的功能:单击其中一个对象即可将其抛出。为此,您需要从鼠标的 2D 坐标向 3D 场景发送一条射线,检查该射线是否碰到了其中一个感兴趣的网格,如果碰到了,则在该网格上施加冲量以尝试移动它。
注意:要了解拾取的工作原理,请观看此 MVA 模块: 使用 HTML5 和 Babylon.js 进行 WebGL 3D 入门 - 高级功能,或尝试我们的在线示例:Babylon.js Playground - 拾取示例。
将此代码添加到 addListeners() 函数中。
canvas.addEventListener("mousedown", function (evt) { var pickResult = scene.pick(evt.clientX, evt.clientY, function (mesh) { if (mesh.name.indexOf("Sphere0") !== -1 || mesh.name.indexOf("Box0") !== -1) { return true; } return false; }); if (pickResult.hit) { var dir = pickResult.pickedPoint.subtract(scene.activeCamera.position); dir.normalize(); pickResult.pickedMesh.applyImpulse(dir.scale(1), pickResult.pickedPoint); } });
在您喜欢的浏览器中启动您的代码。您现在可以单击您的物理网格来玩它们。
显示边界框以更好地理解整个故事
最后,我们将创建一个调试场景,让您可以显示/隐藏碰撞器并为其激活/禁用物理属性。
我们将把 UI 注入这个 div。
<div id="lcContainer">
<ul id="listColliders">
</ul>
</div>
我们将使用此函数来处理 UI。
function CreateCollidersHTMLList() { var listColliders = document.getElementById("listColliders"); for (var j = 0; j < meshesColliderList.length; j++) { var newLi = document.createElement("li"); var chkVisibility = document.createElement('input'); chkVisibility.type = "checkbox"; chkVisibility.name = meshesColliderList[j].name; chkVisibility.id = "colvis" + j; var chkPhysics = document.createElement('input'); chkPhysics.type = "checkbox"; chkPhysics.name = meshesColliderList[j].name; chkPhysics.id = "colphysx" + j; (function (j) { chkVisibility.addEventListener( "click", function (event) { onChangeVisibility(j, event); }, false ); chkPhysics.addEventListener( "click", function (event) { onChangePhysics(j, event); }, false ); })(j) newLi.textContent = meshesColliderList[j].name + " visibility/physx "; newLi.appendChild(chkVisibility); newLi.appendChild(chkPhysics); listColliders.appendChild(newLi); } function onChangeVisibility(id, event) { if (!meshesColliderList[id].isVisible) { meshesColliderList[id].isVisible = true; meshesColliderList[id].material.alpha = 0.75; meshesColliderList[id].material.ambientColor.r = 1; } else { meshesColliderList[id].isVisible = false; } } function onChangePhysics(id, event) { if (!meshesColliderList[id].checkCollisions) { meshesColliderList[id].checkCollisions = true; meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0, friction: 0.5, restitution: 0.7 }); } else { meshesColliderList[id].checkCollisions = false; meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.NoImpostor); } } }
我知道,这会生成一个非常丑陋的 UI,但我太懒了,不想花更多时间在上面。欢迎您改进它!:-P
调用这个新函数并启动 Web 项目。例如,现在显示碰撞器 12 和 17。
您还可以通过第二个复选框来启用/禁用物理属性。例如,如果您禁用了碰撞器 12 的物理属性并发射了球体,它们现在将穿过这堵墙!如下面的屏幕截图所示,球体被红色方框包围。
您可以在此处直接在浏览器中玩此调试示例: babylon.js Espilit 物理调试演示。
也请看看 Samuel Girardin 构建的这个很棒的演示,它也在一些有趣的角色的 Oimo.js 上使用。
希望您喜欢本教程!如果您有任何评论,请随时在 Twitter 上@ http://twitter.com/davrous 上给我发消息。
David
本文是 Microsoft Web 开发技术系列的一部分。我们很高兴与您分享 Microsoft Edge 及其 新的渲染引擎。在此处获取免费虚拟机或在您的 Mac、iOS、Android 或 Windows 设备上远程测试 modern.IE.