NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包
在本文中,我们将重点关注用于优化 Babylon.js 港口场景的优化和架构技术。
我们的场景总共有超过 600 个网格和 1,000,000 个顶点。 在我们的 2018 Macbook Pro 上的 Google Chrome 中,它始终以 45+ FPS 的速度运行。 我们发现 Firefox 的帧速率约为 40 FPS,Safari 的帧速率要低得多,但仍可用,为 25 FPS,主要是因为它不支持 WebGL 2.0。
这里讨论的优化技术确实涵盖了 Babylon.js,但也侧重于改进我们使用的底层模型、材质和照明的方法,这对性能有巨大影响。 我们所有的模型都是使用 Blender 构建的,因此,此处包含的示例通常引用 Blender 解决方案,但也可以使用任何其他 3D 计算机图形软件。
注意:还有比这里描述的更多的优化技术,有些可能不适用于你自己的环境(例如,我们的一些技术要求我们的模型在场景中是静态的)。 有关推荐技术的更多列表,请查看 Babylon 自己关于此主题的文章。
1、单个模型与多个模型导入
为场景设计资源时需要进行权衡。 以我们的港口为例。
![](https://i-blog.csdnimg.cn/blog_migrate/de72aaa0442a92ab6bf20fe6112954bd.png)
五月花号数字体验中使用的 3D“港口”模型的鸟瞰图
我们可以在单个 .blend 文件中对整个场景进行建模,并通过单个 .gltf 文件将其传输到 Babylon。 或者,我们可以在独立的 .gltf 文件中设计每个建筑物、船只、树木和岩石,然后在 Babylon.js 脚本中导入和定位资产。
更少的模型和网格意味着 Babylon 渲染循环的循环次数更少。 在 3D 软件中安排场景也更容易,并且可以由不熟练的 JavaScript 程序员的团队成员来完成。 但是,如果你有一个特别大的模型,加载时间可能会很长(就上下文而言,我们的最终 .glb 文件为 8MB)。
或者,我们可以在自己的 .gltf 文件中构建单独的资产(建筑物、树木等),然后导入这些文件并使用 JavaScript 来定位它们。 与自己的 3D 软件相比,这对于设计布局和空间来说不太实用,但由于可以并行加载模型,因此可以缩短加载时间。 它还可以轻松利用 Babylon 自己的实例功能,从而进一步优化树木的放置等。
在 MAS 数字体验中,我们选择了混合方案。 大多数情况下,我们在 Babylon.js 之外即 Blender 中构建的资产。 我们发现让非开发人员编辑场景的自由非常有用,不容牺牲。 我们对加载时间感到满意,因为体验总是预先加载介绍性动画,因此在后台进行的场景加载不太明显。 此外,我们知道我们希望在 Blender 中对场景进行高质量渲染。 在那里构建完整的场景意味着静态渲染可以完美地反映基于浏览器的世界。
2、Babylon.js 性能监控
为了实现优化,我们需要在整个开发过程中意识到我们的表现。 虽然 Babylon.js 有自己出色的调试检查器,但我们发现在开发中有用的最简单的资源是创建我们自己的 FPS 计数器,该计数器始终留在角落里。 Babylon 引擎可以揭示其价值。
MAS 港口场景视图,显示左上角的小型定制 FPS 计数器
当我们向场景中添加新资源时,当我们做一些有损性能的事情时,情况就变得非常明显。 此外,当有意识地优化我们的场景时,这提供了一个非常易于阅读的改进指标。
不过,执行此操作时请务必与你的场景进行交互。 当相机静止时,场景以 60 FPS 运行,并不意味着它会保持这种状态。 四处走动,点击东西,做你的用户会做的事情!
如果你正在努力找出性能影响发生的地方,我们在检查器中发现了这些非常有用的 Babylon 功能,它们可以帮助你:
2.1 线框和点渲染
在Babylon.js的检查器中,我们可以选择更改渲染模式。 可以通过在场景代码中包含以下行来查看检查器:
scene.debugLayer.show()
从现在显示的菜单中,单击左侧“场景资源管理器”中的“场景”对象。 在右侧的“渲染模式”下,你可以在“点”、“线框”和“实体”之间切换。 我们发现的这些替代视图给出了非常清晰的多边形密度图像。
![](https://i-blog.csdnimg.cn/blog_migrate/dd75e7756d127db6f3357f15274ba043.png)
Babylon.js 的检查器视图的屏幕截图,其中可以在“点”、“线框”和“实体”渲染模式之间切换
在本例中,我们可以看到,该图像中心的建筑物(代表 IBM 的运营决策管理器)的优化效果仍然不佳,因为考虑到它包含多少条边以及因此包含的多边形。
2.2 显示/隐藏网格和 isEnabled 切换
检查器的另一个最有用的功能是能够打开/关闭场景中的每个网格。 这提供了一种非常简单的方法来监控特定资源对场景性能的影响,这可能与多边形计数无关。
关于如何执行此操作,有两种选择:
- “场景资源管理器”中任何网格体右侧的“眼睛”图标。
- Babylon TransformNode 的“IsEnabled”切换。 在场景资源管理器中单击变换节点后,此选项显示在右侧检查器的“常规”选项卡中。
在 Blender 中,我们发现将对象组的父级设置为单个空创建的变换节点。 这些在这里特别有用,因为当导入到 Babylon 时,可以打开/关闭每个节点的“isEnabled”选项,从而一次点击所有子节点,从而使寻找潜在的性能改进变得更加容易。 Blender 集合在导出到 gltf 时丢失。
启用全场景后,我们达到了 48 FPS。
关闭产品构建后,获得了 1 FPS 的增益
当在与产品相关的建筑物的检查器中关闭“isEnabled”选项时,对 FPS 的影响很小 - 现在为 49 FPS。
![](https://i-blog.csdnimg.cn/blog_migrate/f1721bc484a02b87ab67295de30b55ec.png)
关闭填充建筑物可提高 11 FPS,最高可达 59 FPS
不过,当关闭“填充”建筑物时,我们注意到显着的改进,帧速率跃升至 59/60 FPS。 显然,这些网格是我们需要集中注意力并进一步优化的地方。
3、标准 3D 优化技术
我们应该遵循一些标准的良好实践,这些实践适用于任何实时渲染的场景,无论是 Babylon.js 还是基于浏览器。
3.1 多边形计数
最简单的衡量指标是多边形数量。 渲染 4 个顶点比渲染 4,000,000 个顶点要快得多。 在可能的情况下,巧妙地处理你的模型,只在重要的地方添加细节。 如果你的相机拉得太远以至于你永远看不到它,那么在角色模型上雕刻高清脸部就没有意义了。
Ian Hubert 的 1 分钟“懒惰”Blender 教程是一个很好的例子,说明即使使用超低多边形网格也可以获得多少“细节”。 为 4k 渲染和高预算电影构建高清模型固然很棒,但如果你希望构建基于浏览器的体验,则需要灵活选择模型在何处以及如何呈现细节。
岩石模型重新拓扑的“之前和之后”视图,取自 SketchFab 的 3D 扫描资产重新拓扑指南
如果你有一个高多边形模型并希望对其进行优化,那么重新拓扑就是你的朋友。 这可能是一个乏味的过程,但性能的改进是显着的。
如果你的建筑物具有门窗等细节,那么你还可以烘焙法线贴图以提供细节外观,而不会减少顶点数。 法线贴图在不牺牲多边形计数的情况下添加高细节方面特别有效。
左:应用了法线贴图的模型; 中间:没有法线贴图的基础网格; 右:法线贴图
值得注意的是,.gltf 网格具有三角面,因此当你将模型移动到 Babylon 时看到“面数”大幅增加时,请不要感到惊讶。 如果可能,请在导入之前对面进行三角测量,以确保您可以控制多边形和面数。
3.2 实例化
如上所述,渲染 4 个顶点比渲染 4,000,000 个顶点要快。 我们可以“欺骗”渲染器,让其认为我们使用的顶点较少的一种方法是通过实例化。 这减少了 Babylon 在每次渲染中必须执行的绘制调用总数。
在我们完成的港口场景中,我们有 631 个活动网格,以及 73 个绘制调用。 如果没有实例化,我们预计会出现 631 次绘制调用,并且在我们的示例中渲染速度会慢 5-10 倍。
实例化:Blender 中单个建筑物的突出显示实例
实例是使用硬件加速渲染绘制大量相同网格的绝佳方法(让我们想象一下森林或军队)。 这可以在你选择的 3D 软件中完成(我们使用 Blender,可以使用链接的副本来实现),也可以直接在 Babylon.js中用JavaScript完成(如果你的场景适合从内部定位对象的用例) 。
在场景中多次实例化和重复使用同一模型的一个负面后果是,通常很明显你已经一遍又一遍地冲洗和重复相同的网格。
在这里,我们分享了 Blender 中的一些渲染示例,展示了如何利用实例化,同时保持模型的规模感和可变性。
左:16 个顶点(带实例); 16k 个顶点(无)— 中间:48 个顶点(有); 48k 个顶点(无)— 右:228 个顶点(有); 228k(无)
在我们的第一张图片中,我们有一个建筑物实例化了 1,000 次。 在第二次渲染中,我们使用建筑物的 3 种材质变体,将其平均分配到 1,000 个实例中。 最后,在第三张图像中,我们修改建筑物,使其不对称,并引入绕垂直轴的随机旋转。 这些微小的变化带来了变化感,同时保持了优化的几何形状。
4、WebGL 的额外考虑 — 材质整合
使用 Babylon 时,文件通常使用 .gltf 格式导入。 但你可能会注意到,当你的模型加载到 Babylon 中时,它已将你的模型拆分为多个单独的对象。
这发生在逐个材质的基础上,意味着在渲染循环中,Babylon 每次都会循环遍历每个新创建的对象,而不仅仅是单个原始网格 - 昂贵!
这里的理想解决方案是使用尽可能少的材质,虽然你可能希望每个对象都具有漂亮的 4K 纹理和 PBR 材质,但如果能够并且它符合你想要的美感,可以使用调色板 。
左:从 IBM Carbon Palette 创建的多样化调色板。 右:我们用于港口场景的调色板,其中包括渐变段
使用调色板,你可以为所有对象定义单一材质。 将该材质的基础/反照率颜色设置为调色板纹理,并确保模型的 UV 面与你想要的颜色一致。
为此,在 3D 软件中,UV 展开对象,将对象的 UV 缩放至零,然后将 UV 移动到与你希望该面的颜色相匹配的彩色方块中。 如果你使用 Blender,Imphenzia 在他的 10 分钟 Blender 挑战中经常这样做,他在这里详细介绍了这项技术。
每个调色板只需为 3x3 像素(9 种不同颜色)或 8x8 像素(64 种颜色)。 你还可以选择更大的调色板,这样你也可以选择将渐变添加到调色板中。 我们在五月花号体验中将这种技术用于港口的调色板,如上所示。
5、Babylon.js 技巧与技术
如前所述,Babylon 的文档包含丰富的解决内存使用和性能问题的方法。 我们发现两个特别有用的资源是:
具体来说,我们在适当的场景和网格导入中使用了以下方法:
5.1 场景:禁用对象交互
如果在你的场景中,不需要直接与 3D 网格交互,那么禁用鼠标事件我们发现非常有效。
scene.pointerMovePredicate = () => false;
scene.pointerDownPredicate = () => false;
scene.pointerUpPredicate = () => false;
我们在《Harbor》和《Challenge 3》场景中使用了这种方法。 鉴于我们需要检测用户点击环境对象(岩石、浮标和船只),我们无法在《Challenge 1》 中使用此功能。
5.2 场景:删除缓存的顶点数据
所有顶点缓冲区都在 CPU 内存上保留其数据的副本,以支持碰撞、拾取、几何编辑或物理。 如果不需要使用这些功能,可以调用此函数来释放相关内存:
scene.clearCachedVertexData();
5.3 网格导入:静态资源
如果你的网格体不会改变位置、旋转或大小,那么通过调用以下命令“冻结”网格体会非常有效:
mesh.freezeWorldMatrix();
即使它们确实发生了变化,但是是间歇性的,那么你可以通过调用以下命令重新打开世界矩阵计算:
mesh.unfreezeWorldMatrix();
5.4 网格导入:无交互
如果不要求用户单击或选取网格体,则可以在导入网格体时添加以下行以进一步增强性能:
mesh.isPickable = false;
mesh.doNotSyncBoundingInfo = true;
5.5 VueJS 和BabylonJS
在我们的项目完成后,我们实际上发现了使用 BabylonJS 和 VueJS 的一个陷阱。 虽然我们对这里的 FPS/性能非常满意,但事实证明,我们通过将 BabylonJS 引擎和场景绑定为 VueJS 中的反应变量而导致了巨大的 FPS 赤字。
我不会在这里讨论太多细节,但在这篇论坛帖子中对此进行了非常详细的介绍。
原文链接:url