当新节点变得可见时,SceneKit 应用程序会卡顿

SceneKit app stutters when new nodes become visible

当新节点出现在屏幕上时,我遇到了一个 SceneKit 应用程序(使用 Metal)卡顿的问题,尽管该应用程序前后以 60 fps 的速度流畅地 运行。

想象一个东西被摧毁的游戏,有时能量提升会出现在东西被摧毁的地方。我很确定断断续续与出现的能量提升有关,因为它不会在东西刚刚被摧毁(并因此从场景中移除)时发生。

到目前为止我为解决口吃问题所做的工作: 我通过 SceneKit 视图的预加载方法预加载节点,并仅在其完成处理程序中将它们添加到场景中。 我在需要显示它们之前很久就将它们添加到相机上方,并且在需要时将它们移动到正确的位置。 我已经实施了一种排队机制,以确保每帧只进行一次更改(删除已销毁项目的节点,将电源移动到它的位置)。

但是有时(并非总是)出现加电时卡顿仍然会发生。我想知道 SceneKit 是否只在节点第一次出现时才做某事(即使它们已被预加载)。无论发生什么似乎都足以导致卡顿,但对于 XCode 性能表来说太短了无法显示它。每帧都有大量空闲时间,CPU 并且 GPU 从未接近用尽。

我不认为这个问题与复杂的几何形状或巨大的纹理有关,因为当我使用具有统一颜色的简单立方体时它仍然会发生。

知道这里发生了什么或者我如何追踪它吗?

我自己找到了原因,我想和大家分享一下,以防你们运行遇到同样的问题。

SceneKit 在幕后做的神秘任务是重新编译各个节点的着色器。尽管 Apple 没有证实这一点,但我很确定 SceneKit 有一项政策,即始终使用最高效的着色器,这些着色器的复杂程度足以按预期渲染相应的节点。这意味着它会在您添加效果、material 属性或光源时编译更复杂的着色器。当您消除导致光照计算复杂性增加的原因时,它将再次用更简单的着色器替换它。

虽然这在始终获得尽可能高的性能方面很棒,但它也有一个缺点,这就是我所经历的。重新编译着色器需要一些时间,导致 CPU 加载并强制 GPU 等待新版本的着色器。最后,它会导致应用卡顿,即使它们 运行 大多数时候都非常流畅。

解决此问题的最简单方法是用您自己的代码(使用 SCNProgram)替换 SceneKit 着色器,但这也会使您失去 SceneKit 提供的大部分舒适性。 由于这不是我想要的,我最终采用了以下方法:

我强制 SceneKit 最初编译所有节点的所有着色器,这些着色器稍后可能会变得可见,方法是在开始时用黑色覆盖层覆盖场景,将所有节点添加到相机前面,然后将它们移动到正确的位置在淡出覆盖层之前。 我还通过从不完全关闭不需要的效果来避免重新编译(例如,通过将灯的强度更改为 0.1 而不是 0 来关闭灯)。这使得 SceneKit 始终保持相同的着色器,从而避免卡顿。

再说一次:这还没有得到 Apple 的证实,但到目前为止它是有效的,所以我认为我的假设是正确的;-)。