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

时间:2019-05-21 12:10:16

标签: javascript image performance webgl

我正在编写一个简单的webgl应用程序,该应用程序可以相互绘制多个图像(纹理)。根据滚动位置,更改图像的比例和不透明度,以创建3D多层视差效果。您可以在这里看到效果: http://gsstest.gluecksschmiede.io/ct2/

我目前正在努力改善性能,因为该效果在较旧的设备(低fps)上效果不佳。我缺乏对webgl(和webgl调试)的深入了解,以了解导致性能下降的原因是什么,因此我需要帮助。此问题仅与台式机设备有关。

我已经尝试过/目前正在:

  • 始终使用相同的程序和着色器对
  • 图像尺寸为2000x1067,并且已经压缩。由于透明度,我需要png。我可以压缩更多一点,但压缩不了多少。分辨率必须是这种方式。
  • 已经使用requestAnimationFrame和非阻塞滚动侦听器

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

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

基本上,我使用了此教程/代码,并做了一些更改: https://webglfundamentals.org/webgl/lessons/webgl-2d-drawimage.html

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

在我的应用程序中,每帧大约绘制15张2000x1067尺寸的图像。我希望它的性能比实际好。我不知道是什么导致了瓶颈。 您如何为我提供帮助:

  • 提供提示或想法什么代码/图像压缩/进行任何更改都可以提高渲染性能
  • 提供有关如何调试性能的帮助。为什么还要用console.log和performance.now打印时间呢?
  • 提供有关如何在旧设备上更好地降低性能或提供后备性能的想法。

2 个答案:

答案 0 :(得分:1)

几件事:

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

在webgl中图像压缩毫无用处,因为webgl不在乎png,jpg或webp。纹理在gpu上始终只是字节数组,因此您的每个图层都是2000 * 1067 * 4字节= 8.536兆字节。

永远不要在动画循环中构造数据,您一定要了解如何使用正在使用的数学库。

答案 1 :(得分:1)

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

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

您正在绘制15张图像。这些图像被绘制到画布中。然后将该画布绘制到浏览器的窗口中,然后将浏览器的窗口绘制在桌面上。这样至少有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%或某种程度(仅是一个猜测)就可以得到现实中的填充率。

您可以轻松地进行测试,只需缩小浏览器窗口即可。如果将浏览器窗口的大小设为屏幕大小的1/4,并且浏览器窗口运行平稳,那么问题就在于填充率(假设您正在调整画布的绘图缓冲区的大小以匹配其显示大小)。这是因为一旦执行此操作,绘制的像素就会减少(减少75%),但其他所有工作保持不变(所有javascript,webgl等)

假设显示您的问题是填充率,那么您可以做的事

  1. 不绘制所有15层。

    如果某些图层淡出到100%透明,则不要绘制这些图层。如果您可以设计该站点,以便一次只能看到4到7层,那么您将有一段很长的路要走以保持在填充率限制之内

  2. 不要绘制透明区域

    您说了15层,但其中一些层似乎大部分是透明的。您可以将它们分成9个以上的部分(如相框),而不画中间的部分。无论是9件还是50件,它都可能比80%的像素100%透明更好。

    许多游戏引擎如果为它们提供图像,它们会自动生成一个网格,该网格仅使用不透明度大于0%的纹理部分。例如,我在photoshop中制作了这个框架

    enter image description here

    然后将其加载为一个整体,您可以看到Unity制作了一个仅覆盖非100%透明部分的网格

    enter image description here

    您可以通过编写工具,手动操作或使用某些3D网格编辑器(例如Blender)来生成适合您图像的网格,从而离线进行操作,这样您就不会浪费时间尝试渲染100像素%透明。

  3. 尝试丢弃透明像素

    您必须测试一下。在您的片段着色器中,您可以放置​​

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

    其中alphaThreashold为0.0或更大。是否节省时间可能取决于GPU,因为使用丢弃比不慢。原因是如果您不使用discard,则GPU可以尽早进行某些检查。就您而言,我认为这可能是一场胜利。请注意,上述选项2在每个仅覆盖非透明零件的平面上使用网格要好于此选项。

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

    此函数过于复杂,但是您可以创建一个drawMultiImages函数,该函数需要多个纹理和多个纹理矩阵,并一次绘制N个纹理。它们都具有相同的目标矩形,但是通过为每个纹理调整源矩形,您将获得相同的效果。

    N可能为8或更小,因为在一次绘制调用中可以使用的纹理数量受到限制,具体取决于GPU。 IIRC的最低限制是8,这意味着某些GPU将支持超过8个,但如果您希望事物在任何地方都可以运行,则需要处理最低要求。

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

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

    选项4最快,但我不建议这样做。对于我来说,如此简单的效果似乎需要太多工作。尽管如此,我只想指出,至少一眼就能使用N个<div>并设置它们的CSS transformopacity并获得相同的效果。您仍然会遇到相同的问题,15个全屏图层太多了,您应该隐藏不透明度为0%的<div>(浏览器可能会为您做到这一点,但最好不要假设)。您还可以使用2D canvas API,并且应该看到类似的性能。当然,如果您正在执行某种特殊效果(不看代码),那么可以随意使用WebGL,只是一眼就不清楚。