65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (2投票s)

2015 年 4 月 23 日

CPOL

10分钟阅读

viewsIcon

14232

今天,我想通过使用 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.jsoimo.jsOimo 现在是首选的默认物理引擎。

如果您选择了“选项 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.

© . All rights reserved.