如何提高在 webgl 中绘制的多个图像的性能?
How can I improve performance for multiple images drawn in 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/
基本上,我已经使用了这个 tutorial/code 并做了一些改动:
https://webglfundamentals.org/webgl/lessons/webgl-2d-drawimage.html
然后我使用此设置代码根据此文件中显示的当前滚动位置绘制图像(请参阅 "update" 方法):
http://gsstest.gluecksschmiede.io/ct2/js/para.js
在我的应用程序中,每帧大约有 15 张 2000x1067 大小的图像相互绘制。我预计这会比实际表现得更好。我不知道是什么导致了瓶颈。
你可以如何帮助我:
- 提供提示或想法什么代码/图像压缩/任何更改可以提高渲染性能
- 提供有关如何调试性能的帮助。有没有更聪明的为什么然后用 console.log 和 performance.now 打印出时间?
- 提供有关如何优雅地降级或提供在旧设备上性能更好的回退的想法。
几件事:
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 等)
假设这表明您的问题是填充率,那么您可以做的事情
不要绘制所有 15 个图层。
如果某些图层淡出到 100% 透明,则不要绘制这些图层。如果您可以将站点设计为一次只能看到 4 到 7 个层,那么您将大大低于填充率限制
不绘制透明区域
你说的是 15 层,但看起来其中一些层大部分是透明的。你可以把它们分成 9 个以上的部分(比如一个相框)而不是画中间的部分。不管是9块还是50块,大概都比80%的像素100%透明好。
许多游戏引擎如果您给它们一张图像,它们将自动生成一个网格,该网格仅使用 > 0% 不透明的纹理部分。例如,我在 photoshop
中制作了这个框架
然后加载到unity中你可以看到Unity制作了一个只覆盖非100%透明部分的网格
这是你可以通过编写工具或手动完成或使用一些 3D 网格编辑器(如 blender)来离线完成的事情来生成适合你的图像的网格,这样你就不会浪费时间尝试渲染像素100% 透明。
尝试丢弃透明像素
这个你得测试一下。在您的片段着色器中,您可以放置类似
的内容
if (color.a <= alphaThreshold) {
discard; // don't draw this pixel
}
其中 alphaThreashold
为 0.0 或更大。这是否节省时间可能取决于 GPU,因为使用丢弃比不使用要慢。原因是如果你不使用 discard
那么 GPU 可以提前进行某些检查。就您而言,尽管我认为这可能是一场胜利。请注意上面的选项 #2,为每个仅覆盖 non-transparent 部分的平面使用网格比此选项要好得多。
将更多纹理传递给单个着色器
这个过于复杂,但您可以制作一个 drawMultiImages
函数,它采用多个纹理和多个纹理矩阵并一次绘制 N 个纹理。它们都有相同的目标矩形,但通过调整每个纹理的源矩形,您将获得相同的效果。
N 可能为 8 或更少,因为根据 GPU,一次绘制调用中的纹理数量有限制。 8 是 IIRC 的最小限制,这意味着一些 GPU 将支持超过 8 个,但如果你想要 运行 到处都需要处理最小情况。
像大多数处理器一样,GPU 的读取速度比写入速度快,因此读取多个纹理并在着色器中混合它们比单独处理每个纹理要快。
最后,您不清楚为什么在此示例中使用 WebGL。
选项 4 最快,但我不推荐它。对于这样一个简单的效果,我觉得工作量太大了。不过,我只想指出,至少乍一看,您可以只使用 N <div>
并设置它们的 css transform
和 opacity
并获得相同的效果。你仍然会遇到同样的问题,15 个全屏图层太多了,你应该隐藏 <div>
不透明度为 0% 的人(浏览器可能会为你这样做,但不要假设)。您也可以使用 2D canvas API,您应该会看到类似的性能。当然如果你是做什么特效的(没看代码)那就随便用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/
基本上,我已经使用了这个 tutorial/code 并做了一些改动: https://webglfundamentals.org/webgl/lessons/webgl-2d-drawimage.html
然后我使用此设置代码根据此文件中显示的当前滚动位置绘制图像(请参阅 "update" 方法): http://gsstest.gluecksschmiede.io/ct2/js/para.js
在我的应用程序中,每帧大约有 15 张 2000x1067 大小的图像相互绘制。我预计这会比实际表现得更好。我不知道是什么导致了瓶颈。 你可以如何帮助我:
- 提供提示或想法什么代码/图像压缩/任何更改可以提高渲染性能
- 提供有关如何调试性能的帮助。有没有更聪明的为什么然后用 console.log 和 performance.now 打印出时间?
- 提供有关如何优雅地降级或提供在旧设备上性能更好的回退的想法。
几件事:
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 等)
假设这表明您的问题是填充率,那么您可以做的事情
不要绘制所有 15 个图层。
如果某些图层淡出到 100% 透明,则不要绘制这些图层。如果您可以将站点设计为一次只能看到 4 到 7 个层,那么您将大大低于填充率限制
不绘制透明区域
你说的是 15 层,但看起来其中一些层大部分是透明的。你可以把它们分成 9 个以上的部分(比如一个相框)而不是画中间的部分。不管是9块还是50块,大概都比80%的像素100%透明好。
许多游戏引擎如果您给它们一张图像,它们将自动生成一个网格,该网格仅使用 > 0% 不透明的纹理部分。例如,我在 photoshop
中制作了这个框架然后加载到unity中你可以看到Unity制作了一个只覆盖非100%透明部分的网格
这是你可以通过编写工具或手动完成或使用一些 3D 网格编辑器(如 blender)来离线完成的事情来生成适合你的图像的网格,这样你就不会浪费时间尝试渲染像素100% 透明。
尝试丢弃透明像素
这个你得测试一下。在您的片段着色器中,您可以放置类似
的内容if (color.a <= alphaThreshold) { discard; // don't draw this pixel }
其中
alphaThreashold
为 0.0 或更大。这是否节省时间可能取决于 GPU,因为使用丢弃比不使用要慢。原因是如果你不使用discard
那么 GPU 可以提前进行某些检查。就您而言,尽管我认为这可能是一场胜利。请注意上面的选项 #2,为每个仅覆盖 non-transparent 部分的平面使用网格比此选项要好得多。将更多纹理传递给单个着色器
这个过于复杂,但您可以制作一个
drawMultiImages
函数,它采用多个纹理和多个纹理矩阵并一次绘制 N 个纹理。它们都有相同的目标矩形,但通过调整每个纹理的源矩形,您将获得相同的效果。N 可能为 8 或更少,因为根据 GPU,一次绘制调用中的纹理数量有限制。 8 是 IIRC 的最小限制,这意味着一些 GPU 将支持超过 8 个,但如果你想要 运行 到处都需要处理最小情况。
像大多数处理器一样,GPU 的读取速度比写入速度快,因此读取多个纹理并在着色器中混合它们比单独处理每个纹理要快。
最后,您不清楚为什么在此示例中使用 WebGL。
选项 4 最快,但我不推荐它。对于这样一个简单的效果,我觉得工作量太大了。不过,我只想指出,至少乍一看,您可以只使用 N
<div>
并设置它们的 csstransform
和opacity
并获得相同的效果。你仍然会遇到同样的问题,15 个全屏图层太多了,你应该隐藏<div>
不透明度为 0% 的人(浏览器可能会为你这样做,但不要假设)。您也可以使用 2D canvas API,您应该会看到类似的性能。当然如果你是做什么特效的(没看代码)那就随便用WebGL吧,一看就不清楚