使用 Babylon.js 构建跨平台 WebGL 游戏

已发表: 2022-03-10
快速总结 ↬你面临一个挑战:在周末构建一个 3D 游戏怎么样? Babylon.js 是一个 JavaScript 框架,用于使用 HTML5、WebGL 和 Web Audio 构建 3D 游戏,由您和 Babylon.js 团队构建。 为了庆祝库的新版本 2.3,我们决定构建一个名为“Sponza”的新演示,以突出在当今构建出色游戏时可以使用 WebGL 引擎和 HTML5 做什么。 这个想法是在所有支持 WebGL 的平台上创建一致、相似(如果不相同)的体验,并尝试达到原生应用程序的功能。 在本文中,我将解释这一切如何协同工作,以及我们面临的各种挑战以及我们在构建它时学到的教训。

这对您来说是一个挑战:周末制作一个 3D 游戏怎么样? Babylon.js 是一个 JavaScript 框架,用于使用 HTML5、WebGL 和 Web Audio 构建 3D 游戏,由您和 Babylon.js 团队构建。 为了庆祝库的新版本 2.3,我们决定构建一个名为“Sponza”的新演示,以突出在当今构建出色游戏时可以使用 WebGL 引擎和 HTML5 做什么。

这个想法是在所有支持 WebGL 的平台上创建一致、相似(如果不相同)的体验,并尝试达到原生应用程序的功能。 在本文中,我将解释这一切如何协同工作,以及我们面临的各种挑战以及我们在构建它时学到的教训。

关于 SmashingMag 的进一步阅读

  • 使用 Babylon.js 构建着色器
  • 在 Web 游戏中使用 Gamepad API
  • 多边形建模和 Three.js 简介
  • 如何创建响应式 8 位鼓机

为了实现这一目标,Sponza 使用了许多 HTML5 功能,如 WebGL、Web 音频以及指针事件(现在由于 jQuery PEP polyfill 而得到广泛支持)、Gamepad API、IndexedDB、HTML5 AppCache、CSS3 过渡/动画、flexbox 和全屏API。 您可以在台式机、移动设备或 Xbox One 上测试 Sponza 演示。

跳跃后更多! 继续往下看↓
Sponza 游戏中的大厅
交互式 Sponza 演示中的主厅。

发现演示

首先,您将从自动动画序列开始,将功劳归于构建场景的人。 团队的大部分成员都来自演示现场。 您会发现这是 3D 开发人员文化的重要组成部分。 在我这边,我在 Atari 上,而 David Catuhe 在 Amiga 上,这仍然是我们之间经常发生冲突的根源,信不信由你。 我正在编写一些代码,但主要是在我的演示组中创作音乐。 我是 Future Crew 的忠实粉丝,尤其是 Purple Motion,我最喜欢的演示场景作曲家。 但是,我们不要偏离主题。

对于 Sponza,以下是贡献者:

  • Michel Rousseau aka “Mitch”作为 3D 艺术家完成了出色的视觉动画和渲染优化。 它采用了 Crytek 在其网站上免费提供的 Sponza 模型,并使用 3DS Max 导出器来生成您所看到的。
  • David Catuhe又名“deltakosh”,我已经完成了 Babylon.js 引擎的核心部分以及演示的所有代码(自定义加载器、使用淡入黑后期处理的演示模式的特殊效果等)以及一种名为“ UniversalCamera ”的新型相机以通用方式处理所有类型的输入。
  • 我使用 Renoise 和 EastWest Symphonic Orchestra 音色库创作了音乐。 如果您有兴趣,我已经在使用 Renoise 跟踪器和东西方 VST 插件为 World Monger Windows 8 游戏创作音乐的文章中分享了我的工作流程和过程
  • Julien Moreau-Mathis帮助我们构建了一个新工具来帮助 3D 艺术家完成建模工具(3DS Max、Blender)和最终结果之间的工作。 例如,米歇尔用它来测试和调整各种动画摄像机,并将粒子注入场景。

如果您等到自动序列结束直到“史诗般的完成”,您将自动切换到交互模式。 如果您想绕过演示模式,只需单击相机图标或按游戏手柄上的A

在交互模式下,如果您使用的是 Mac 或 PC,您将能够像 FPS 游戏一样使用键盘/鼠标在场景中移动。 如果您使用的是智能手机,您将能够通过一次触摸(以及2旋转相机)来移动。 最后,在 Xbox One 上,您可以使用游戏手柄(或桌面,如果您将游戏手柄插入其中)。 有趣的事实:在 Windows 触控 PC 上,您可以同时使用 3 种类型的输入。

互动模式的气氛不同。 您在 3D 环境中随机放置了三个风暴音频源,每个角落都有风和火裂纹。 在支持的浏览器(Chrome、Firefox、Opera 和 Safari)上,您甚至可以通过单击专用图标在普通扬声器模式和耳机模式之间切换。 然后,它将使用 Web Audio 的双耳音频渲染来进行更逼真的音频模拟 - 如果您通过耳机收听。

为了获得完整的类似应用程序的体验,我们为所有平台生成了图标和磁贴。 这意味着,例如,在 Windows 810上,您可以将 Web 应用程序固定到“开始”菜单中。 我们甚至有多种尺寸可供选择:

您可以将 Web 应用固定到“开始”菜单
生成图标和磁贴后,您可以将 Web 应用程序固定到“开始”菜单中。
您可以将 Web 应用程序固定到 iOS、Android 或 Windows Mobile 上的“开始”菜单
它也适用于您的 iPhone、Android 设备或 Windows Mobile。

先下线!

演示完全加载后,您可以将手机切换到飞行模式以切断连接并单击 Sponza 图标。 该网络应用程序仍将提供完整的 WebGL 渲染、3D 网络音频和触控支持体验。 将其切换到全屏,您实际上将无法感受到演示和原生应用体验之间的区别。

为此,我们在 Babylon.js 中使用本机可用的 IndexedDB 层。 场景(JSON 格式)和资源(JPG/PNG 纹理以及用于音乐和声音的 MP3)存储在 IDB 中。 IDB 层与 HTML5 应用程序缓存相结合,然后提供离线体验。 要了解有关这部分的更多信息以及如何配置您的游戏以获得类似的结果,您可以阅读有关使用 IndexedDB 处理您的 3D WebGL 资产的文章:分享 Babylon.JS 的反馈和提示以及在 Babylon.js 中的 IndexedDB 中的缓存资源

Xbox One 很喜欢这个节目

最后但并非最不重要的一点是,同样的演示在您的 Xbox One 上的 MS Edge 中完美运行:

Xbox One 上的 MS Edge

A切换到交互模式。 Xbox One 将通知您现在可以使用游戏手柄在 3D 场景中移动:

Xbox One 通知:您现在可以使用游戏手柄在 3D 场景中移动

所以,让我们简要回顾一下。

相同的代码库适用于 Mac、Linux、MS Edge 上的 Windows、Chrome、Firefox、Opera 和 Safari、iPhone/iPad、带有 Chrome 或 Firefox 的 Android 设备、Firefox OS 和 Xbox One! 这不是很酷吗? 能够直接从您的 Web 服务器定位到如此多的具有完全成熟的原生体验的设备吗?

我已经在前一篇关于网络的文章中分享了我对该技术潜力的兴奋:下一个游戏前沿?

使用调试层破解场景

如果您想了解 Michel 如何掌握 3D 建模的魔力,您可以使用 Babylon.js调试层工具破解场景。 要在带有键盘的机器上启用它,请按CMD/CTRL + SHIFT + D ,如果您在 PC 或 Xbox 上使用游戏手柄,请按Y 请注意,由于渲染引擎需要执行合成作业,显示调试层会消耗一些性能。 所以显示的 FPS 比没有显示调试层的真实 FPS 重要一点。

例如,让我们在 PC 上对其进行测试。

靠近狮子的头部,从我们的着色器管道中切掉凹凸通道:

狮子头

您应该看到头部现在不那么逼真了。 与其他频道一起播放以检查发生了什么。

您还可以切断动态闪电引擎或禁用碰撞引擎以飞行或穿过墙壁。 例如,禁用“碰撞”复选框并飞到一楼。 将相机放在红旗前。 你可以看到它们在轻微移动。 Michel 使用 Babylon.js 的骨骼/骨架支持来移动它们。 现在,禁用“骨架”选项,它们应该停止移动:

骷髅选项

最后,您可以在右上角显示网格树。 您可以启用或禁用它们以完全破坏 Michel 所做的工作:

移除几何图形、着色器通道或引擎的某些选项可以帮助您对特定设备的性能进行故障排除,以了解当前成本过高的情况。 您还可以检查您是否受 CPU 限制或 GPU 限制,尽管在大多数情况下,由于 JavaScript 的单线程特性,您在 WebGL 中会受到 CPU 限制。 最后,该工具对于帮助您了解 3D 艺术家如何构建场景也非常有用。

顺便说一句,它在 Xbox One 上也能很好地工作:

的Xbox One sponza

挑战

在此过程中,我们在构建演示时遇到了许多问题和挑战。 让我们详细研究其中的一些。

WebGL 性能和跨平台兼容性

编程方面可能是最容易解决的,因为它完全由 Babylon.js 引擎本身处理。 我们正在使用自定义着色器的架构,该架构通过尝试使用各种后备方法找到可用于当前浏览器/GPU 的最佳着色器来适应平台。 这个想法是降低渲染引擎的质量和复杂性,直到我们设法在屏幕上显示一些有意义的东西。

Babylon.js 主要基于 WebGL 1.0,以保证基于它构建的 3D 体验几乎可以在任何地方运行。 它的构建考虑了 Web 理念,因此我们正在逐步增强着色器编译过程。 这对于大部分时间不想处理这些复杂性的 3D 艺术家来说是完全透明的。

尽管如此,3D 美术师在性能优化方面仍扮演着非常重要的角色。 她必须了解她的目标平台、支持的功能和限制。 您不能获取来自为高端 GPU 和 DirectX 12 制作的 AAA 游戏的资产,然后将它们集成到在 WebGL 引擎上运行的游戏中。 我认为,今天以 WebGL 为目标与您为优化移动设备体验而必须做的工作非常相似,只是需要一些额外的 JavaScript,这些 JavaScript 必须是高度单线程的。

Mitch 非常擅长的就是:优化贴图,预先计算闪电将其烘焙到贴图中,尽可能减少绘制调用的次数等等。他身后有多年的经验,看到了各代的3D 硬件和引擎(从 PowerVR/3DFX 到今天的 GPU)确实有助于实现演示。

他已经在他关于实时 3D 的文章中分享了一些基础知识:为 WebGL 目的制作一个演示 - 基础知识,并且已经多次证明您可以在小型集成 GPU 上以高性能在 Web 上创建非常引人入胜的视觉体验,例如在Mansion、Hill Valley 或 Espilit 演示场景。 如果您有兴趣,请花时间观看他在 NGF2014 上的演讲——为移动世界和网络创建 3D 资产,一位 3D 设计师分享他的经验以及他如何设法优化 Hill Valley 场景的观点小于 1 fps 到 60 fps。

Sponza 的最初目标是构建两个场景。 一种用于台式机,另一种用于移动设备,具有较低的复杂性、较小的纹理和更简单的网格和几何形状。 但在我们的测试中,我们终于发现桌面版本在移动设备上运行也相当不错,因为它可以在 iPhone 6s 或 Android OnePlus 2 上运行高达 60fps。然后我们决定不再继续开发更简单的移动版本。

但同样,在 Sponza 上采用干净的移动优先方法可能会更好,以在许多移动设备上达到 30fps+,然后增强高端移动和桌面的场景。 尽管如此,到目前为止,我们在 Twitter 上收到的大部分反馈似乎都表明最终结果在大多数设备上运行良好。 诚然,Sponza 已在 HD4000 GPU(集成 Intel Core i5)上进行了优化,它或多或少等同于实际高端手机的 GPU。

我们对我们取得的成绩感到非常满意。 Sponza 使用我们的着色器,启用了环境漫反射凹凸镜面反射反射。 我们有一些粒子来模拟每个角落的小火、红旗的动画骨骼、3D 定位的声音和使用交互模式移动时的碰撞

从技术上讲,我们在场景中使用了 98 个网格,生成多达 377781 个顶点,16 个活动骨骼,60 多个粒子,可以生成多达 36 个绘制调用。 我们学到了什么? 可以肯定的一件事:减少绘制调用是优化性能的关键,在网络上更是如此。

装载机

对于 Sponza,我们想创建一个新的加载器,与我们在 BabylonJS 网站上使用的默认加载器不同,以拥有一个干净和优美的 web 应用程序。 然后我请 Michel 提出一些新的建议。

他首先将以下屏幕发送给我:

加载说明

确实,当您第一次看到它时,屏幕看起来非常漂亮。 但随后您可能开始想知道如何以真正响应的方式使其在所有设备上运行? 让我们弄清楚。

先说一下背景吧。 Michel 创建的模糊效果很好,但在所有窗口大小和分辨率下都不能很好地产生一些波纹。 然后,我将其替换为场景的“经典”屏幕截图。 但是,我希望背景完全填满屏幕,没有黑条,也不会拉伸图像来破坏比例。

解决方案主要来自CSS background-size: cover + centering image on X and Y轴。 结果,无论使用什么屏幕比例,我们都会获得我想要的体验:

Sponza 100%

其他部分正在使用良好的基于​​百分比的 CSS 定位。 好的,排序后,我们如何处理排版 - 字​​体大小应该基于视口的大小。 显然,我们可以为此使用视口单位。 vwvh (其中 1vw 是视口宽度的 1%,而 1vh 是视口高度的 1%)在浏览器中得到了很好的支持,尤其是在所有兼容 WebGL 的浏览器中。 (在 Smashing Magazine 上还有一篇关于 Viewport Sized Typography 的文章,我强烈建议您阅读。)

最后,我们正在使用背景图像的opacity属性,根据当前下载过程从 0 移动到 100%,将其从0移动到1

哦,顺便说一下,文本动画是简单地使用 CSS 过渡或动画结合 flexbox 布局来实现的,以一种简单但有效的方式显示在中心或每个角落。

以透明的方式处理所有输入

我们的 WebGL 引擎正在渲染端完成所有工作,以在所有平台上正确显示视觉效果。 但是,无论使用何种输入类型,我们如何保证用户能够在场景中移动呢?

在以前的 Babylon.js 版本中,我们支持所有类型的输入和用户交互:键盘/鼠标、触摸、虚拟触摸操纵杆、游戏手柄、设备方向 VR(用于 Card Board)和 WebVR,每个都通过专用的摄像头。 您可以阅读我们的文档以了解更多关于它们的信息。

通过 jQuery PEP polyfill(在需要时为应用程序生成触摸事件)传播到所有平台的指针事件规范对触摸进行了普遍管理。 要了解有关指针事件的更多信息,您可以阅读统一触摸和鼠标:指针事件如何使跨浏览器触摸支持变得容易

然后回到演示。 Sponza 的想法是拥有一个独特的相机,同时处理所有用户的场景:桌面、移动和控制台。

我们最终创建了UniversalCamera 。 老实说,创建​​它是如此明显和简单,以至于我仍然不知道为什么我们以前没有这样做。 UniversalCamera 或多或少是一个扩展了TouchCamera的游戏手柄相机,它扩展了FreeCamera

FreeCamera 提供键盘/鼠标逻辑; TouchCamera 提供触摸逻辑,最终扩展提供游戏手柄逻辑。

现在,Babylon.js 默认使用 UniversalCamera。 如果您正在浏览演示,您可以在所有场景中使用鼠标、触摸和游戏手柄在场景中移动。 同样,您可以研究代码以了解它是如何完成的。

将过渡与音乐同步

现在,这部分是我们问自己最多问题的地方。 您可能已经注意到介绍序列与音乐播放曲目的特定区域同步。 当一些鼓声响起时会显示第一行,最后的结束序列是在我们使用的喇叭乐器的每个音符上从一个摄像机快速切换到另一个摄像机。

将音频与 WebGL 渲染循环同步并不容易。 同样,这是 JavaScript 的单线程特性导致了这种复杂性。 关于 HTML5 Web Workers 简介:JavaScript 多线程方法的文章分享了这背后的一些见解。 了解问题对于了解我们面临的全球性问题非常重要,但此处详细介绍超出了本文的范围。

通常,在演示场景(和视频游戏)中,如果您想将视觉效果与声音/音乐同步,您将受到音频堆栈的驱动。 经常使用两种方法:

  1. 生成将注入音频文件的元数据,然后当您到达它的特定部分时可以“调用”一些事件,
  2. 通过 FFT 或类似技术对音频流进行实时分析,以检测有趣的峰值或 BPM 变化,这些变化将再次为视觉引擎生成事件。

这些方法在 C++ 等多线程环境中效果特别好。 但是在 JavaScript 中,使用 Web Audio,我们遇到了两个问题:

  1. JavaScript,它是单线程的,不幸的是,大多数时候网络工作者不会真正帮助我们,
  2. 即使浏览器在单独的线程上处理 Web 音频,Web 音频也没有任何可以发送回 UI 线程的事件

Web Audio 的计时器比 JavaScript 精确得多。 如果能够在单独的线程上使用这个单独的计时器将事件驱动回 UI 线程,那就太棒了。 但是今天,你不能这样做(还没有?)。

另一方面,我们使用 WebGL 和requestAnimationFrame方法渲染场景。 这意味着,在“最佳情况”下,我们有 16 毫秒的窗口时间范围。 如果您错过了一个,您将不得不等待最多 16 毫秒才能对下一帧采取行动以反映声音同步(例如启动“淡入淡出”效果)。

然后我正在考虑将同步逻辑注入到requestAnimationFrame循环中。 我研究了自序列开始以来所花费的时间,并研究了调整视觉以对音频事件做出反应的选项。 好消息是,无论主 UI 线程中发生什么,Web 音频都会呈现声音。 例如,您可以确定音乐的 12 秒时间戳将在音乐开始播放后的 12 秒后准确到达,即使 GPU 很难渲染场景。

最后,我们最终选择了可能是有史以来最简单的解决方案:使用setTimeout()调用! 是的,我知道。 如果您查看那里的大多数文章,包括我在上面链接的那篇,您会发现它非常不可靠。 但在我们的例子中,一旦准备好渲染场景,我们就知道我们已经下载了所有资源(纹理和声音)并编译了着色器。 我们不应该对使 UI 线程饱和的意外事件感到太恼火。 GC 可能是个问题,但我们也花了很多时间在引擎中与它作斗争:通过使用 Internet Explorer 11 的 F12 开发人员栏来减少垃圾收集器的压力。

尽管如此,我们知道这个解决方案远非理想。 切换到另一个选项卡或锁定手机并在几秒钟后解锁可能会在演示的同步部分产生一些问题。 我们可以通过使用 Page Visibility API 来解决这些问题,例如暂停渲染循环、各种声音并重新计算setTimeout()调用的下一个时间帧。

但也许我们错过了一些东西; 也许,甚至可能,有更好的方法来处理这个问题。 如果您认为有更好的方法来解决相同的问题,我们很乐意在评论部分听到您的想法和建议。

在 iOS 上处理网络音频

我想与您分享的最后一个挑战是 iOS 在 iPhone 和 iPad 上处理 Web Audio 的方式。 如果您正在寻找有关“网络音频 + iOS”的文章,您会发现很多人很难在 iOS 上播放声音。 现在,那里发生了什么事?

iOS 对 Web Audio 的支持非常出色——甚至是双耳音频模式。 但是苹果已经决定,如果没有特定用户的交互,网页默认不能播放任何声音。 做出这个决定可能是为了避免广告或其他任何通过播放不请自来的声音来打扰用户的东西。

这对 Web 开发人员意味着什么? 好吧,您首先需要在用户触摸后解锁 iOS 的网络音频上下文——在尝试播放任何声音之前。 否则,您的 Web 应用程序将保持极度静音。

不幸的是,我发现这样做的唯一方法是通过用户平台嗅探方法来检查,因为我没有找到一种特征检测方法来做到这一点。 这简直就是一种可怕的,而不是防弹的技术,但我找不到任何其他可以解决这个问题的解决方案。 代码? 干得好!

如果您不在 iPad/iPhone/iPod 上,则可以立即使用音频上下文。 否则,我们将通过在touchend事件上播放代码生成的空声音来解锁 iOS 的音频上下文。 如果您想在启动游戏之前等待它,您可以注册onAudioUnlocked事件。 因此,如果您在 iPhone/iPad 上启动 Sponza,您将在加载序列结束时看到这个最终屏幕:

触摸开始

触摸屏幕上的任意位置将解锁 iOS 的音频堆栈并启动“显示”。

所以我们开始吧! 我希望您对演示开发背后的一些见解有所了解。 要了解更多信息,请在我们的 GitHub 上阅读此演示的完整源代码。 显然,一切都是开源的,你可以在 GitHub 上找到主要文件:index.js 和 babylon.demo.ts。

最后,我真的希望您现在更加确信网络绝对是一个很棒的游戏平台! 请继续关注,因为我们目前正在开发新的演示,希望它们也能令人印象深刻。

本文是 Microsoft 技术布道者和工程师关于实用 JavaScript 学习、开源项目和互操作性最佳实践(包括 Microsoft Edge 浏览器)的 Web 开发系列的一部分。

我们鼓励您使用 dev.microsoftedge.com 上的免费工具(包括 F12 开发人员工具)跨浏览器和设备进行测试,包括 Microsoft Edge(Windows 10 的默认浏览器)——七种不同的、完整记录的工具,可帮助您调试、测试和加快您的网页速度。 此外,请访问 Edge 博客以随时了解 Microsoft 开发人员和专家的最新信息。