如何提高在 webgl 中绘制的多个图像的性能?

How can I improve performance for multiple images drawn in webgl?

我正在编写一个简单的 webgl 应用程序,它在彼此之上绘制多个图像(纹理)。根据滚动位置,图像的比例和不透明度会发生变化,以创建 3D 多层视差效果。你可以在这里看到效果: http://gsstest.gluecksschmiede.io/ct2/

我目前正在努力提高性能,因为效果在旧设备上表现不佳(低 fps)。我缺乏对 webgl(和 webgl 调试)的深入了解,无法了解性能不佳的原因,因此我需要帮助。此问题仅与桌面设备有关。

我已经尝试过/我目前是:

我用来绘制图像的 webgl 函数可以在这个文件中读取: http://gsstest.gluecksschmiede.io/ct2/js/setup.js

着色器代码可以在这里找到(只需右键单击 -> 显示源代码): http://gsstest.gluecksschmiede.io/ct2/

基本上,我已经使用了这个 tutorial/code 并做了一些改动: https://webglfundamentals.org/webgl/lessons/webgl-2d-drawimage.html

然后我使用此设置代码根据此文件中显示的当前滚动位置绘制图像(请参阅 "update" 方法): http://gsstest.gluecksschmiede.io/ct2/js/para.js

在我的应用程序中,每帧大约有 15 张 2000x1067 大小的图像相互绘制。我预计这会比实际表现得更好。我不知道是什么导致了瓶颈。 你可以如何帮助我:

几件事:

Performance profiling 表明您的问题不是 webgl,而是 内存泄漏.

图像压缩在 webgl 中毫无价值 因为 webgl 不关心 png、jpg 或 webp。纹理总是只是 gpu 上的字节数组,所以你的每一层都是 2000*1067*4 字节 = 8.536 兆字节。

永远不要在你的动画循环中构建数据,你这样做,学习如何使用你正在使用的数学库。

这只是一个猜测,但是...

在许多系统上绘制 15 张全屏图像会很慢。就是像素太多了。这不是图像的大小,而是它们绘制的大小。就像在我的 MacBook Air 上一样,屏幕的分辨率是 2560x1600

您正在绘制 15 张图像。那些图像被绘制成 canvas。然后 canvas 被绘制到浏览器的 window 中,然后浏览器的 window 被绘制在桌面上。所以这至少是 17 次平局或

 2560 * 1600 * 17 = 70meg pixels

为了获得流畅的帧率,我们通常希望 运行 每秒 60 帧。每秒 60 帧意味着

 60 frames a second * 70 meg pixels = 4.2gig pixels a second.

我的 GPU 额定为每秒 8gig 像素,所以看起来我们可以在这里获得 60fps

让我们与配备 Intel HD Graphics 6000 的 2015 Macbook Air 进行比较。它的屏幕分辨率为 1440x900,如果我们计算的话,每秒 60 帧时为 1.3gig 像素。它的 GPU 额定为每秒 1.2gig 像素,因此我们不会 在 2015 Macbook Air

上达到 60fps

请注意,与所有事物一样,GPU 的指定最大填充率是其中之一 理论最大值,由于其他开销,您可能永远不会看到它达到最高速率.换句话说,如果您查找 GPU 的填充率乘以 85% 或其他值(只是猜测)以获得您在现实中更有可能看到的填充率。

你可以很容易地测试这个,只需将浏览器 window 变小即可。如果您使浏览器 window 屏幕大小的 1/4 并且它 运行 平滑那么您的问题是填充率(假设您正在调整 canvas 的绘图缓冲区以匹配其显示屏尺寸)。这是因为一旦你这样做,绘制的像素就会减少(减少 75%),但所有其他工作都保持不变(所有 javascript、webgl 等)

假设这表明您的问题是填充率,那么您可以做的事情

  1. 不要绘制所有 15 个图层。

    如果某些图层淡出到 100% 透明,则不要绘制这些图层。如果您可以将站点设计为一次只能看到 4 到 7 个层,那么您将大大低于填充率限制

  2. 不绘制透明区域

    你说的是 15 层,但看起来其中一些层大部分是透明的。你可以把它们分成 9 个以上的部分(比如一个相框)而不是画中间的部分。不管是9块还是50块,大概都比80%的像素100%透明好。

    许多游戏引擎如果您给它们一张图像,它们将自动生成一个网格,该网格仅使用 > 0% 不透明的纹理部分。例如,我在 photoshop

    中制作了这个框架

    然后加载到unity中你可以看到Unity制作了一个只覆盖非100%透明部分的网格

    这是你可以通过编写工具或手动完成或使用一些 3D 网格编辑器(如 blender)来离线完成的事情来生成适合你的图像的网格,这样你就不会浪费时间尝试渲染像素100% 透明。

  3. 尝试丢弃透明像素

    这个你得测试一下。在您的片段着色器中,您可以放置​​类似

    的内容
    if (color.a <= alphaThreshold) {
      discard;  // don't draw this pixel
    }
    

    其中 alphaThreashold 为 0.0 或更大。这是否节省时间可能取决于 GPU,因为使用丢弃比不使用要慢。原因是如果你不使用 discard 那么 GPU 可以提前进行某些检查。就您而言,尽管我认为这可能是一场胜利。请注意上面的选项 #2,为每个仅覆盖 non-transparent 部分的平面使用网格比此选项要好得多。

  4. 将更多纹理传递给单个着色器

    这个过于复杂,但您可以制作一个 drawMultiImages 函数,它采用多个纹理和多个纹理矩阵并一次绘制 N 个纹理。它们都有相同的目标矩形,但通过调整每个纹理的源矩形,您将获得相同的效果。

    N 可能为 8 或更少,因为根据 GPU,一次绘制调用中的纹理数量有限制。 8 是 IIRC 的最小限制,这意味着一些 GPU 将支持超过 8 个,但如果你想要 运行 到处都需要处理最小情况。

    像大多数处理器一样,GPU 的读取速度比写入速度快,因此读取多个纹理并在着色器中混合它们比单独处理每个纹理要快。

  5. 最后,您不清楚为什么在此示例中使用 WebGL。

    选项 4 最快,但我不推荐它。对于这样一个简单的效果,我觉得工作量太大了。不过,我只想指出,至少乍一看,您可以只使用 N <div> 并设置它们的 css transformopacity 并获得相同的效果。你仍然会遇到同样的问题,15 个全屏图层太多了,你应该隐藏 <div> 不透明度为 0% 的人(浏览器可能会为你这样做,但不要假设)。您也可以使用 2D canvas API,您应该会看到类似的性能。当然如果你是做什么特效的(没看代码)那就随便用WebGL吧,一看就不清楚