为什么我的简单 webgl 演示这么慢

Why is my simple webgl demo so slow

我一直在尝试使用 these awesome tutorials 学习 Web GL。我的目标是制作一个非常简单的 2D 游戏框架来取代基于 canvas 的 jawsJS。

我基本上只是希望能够创建一堆 sprite 并将它们四处移动,然后再制作一些瓷砖。

我整理了一个执行此操作的基本演示,但遇到了一个我无法追踪的性能问题。一旦我在屏幕上看到大约 2000 个精灵,帧率就会下降,我无法弄清楚为什么。与这个 demo of the pixi.js webgl framework 相比,它在大约 30000 个兔子左右开始丢帧(在我的机器上),我有点失望。

My demo (framework source)有5002个精灵,其中两个在动,帧率马桶

我已经尝试通过 pixi.js 框架来尝试找出它们的不同之处,但它是 500kloc,而且比我的要多得多,我无法解决。

我发现 this answer 基本上证实了我所做的大致正确 - 我的算法与答案中的算法几乎相同,但肯定有更多。

到目前为止,我已经尝试了一些东西 - 只使用一个 'frame buffer' 和一个定义的单一形状,然后为每个精灵翻译 5000 次。这确实对帧速率有一点帮助,但与 pixi 演示没有任何区别(这意味着所有精灵必须具有相同的形状!)。我删除了所有不动的矩阵数学,所以它也不是。这一切似乎都归结为 drawArrays() 功能 - 它对我来说真的很慢,但仅限于我的演示!

我也试过删除所有基于纹理的东西,用简单的块颜色替换片段着色器。它几乎没有任何区别,所以我消除了作为罪魁祸首的狡猾纹理处理。

如果有人帮助我追查我做过的愚蠢之事,我将不胜感激!

编辑:我肯定误解了这里的一些关键内容。我将整个事情剥离回到基础,将顶点和片段着色器更改为超级简单:

attribute vec2 a_position;

void main() {
    gl_Position = vec4(a_position, 0, 1);
}

和:

void main() {
    gl_FragColor = vec4(0,1,0,1);  // green
}

然后将要绘制的精灵设置为 (0,0), (1,1)。

5000个精灵,绘制一帧大概需要5秒。这是怎么回事?

问题可能出在渲染中的这一行:glixl.context.uniformMatrix3fv(glixl.matrix, false, this.matrix);

根据我的经验,在 webGL 中为每个模型传递制服非常慢,在大约 1,000 个独特模型后我无法保持 60FPS。不幸的是,webgl 中没有统一的缓冲区来缓解这个问题。

我通过计算 CPU 上的所有顶点位置并使用一个 drawArray 调用将它们全部绘制出来解决了我的问题。如果顶点数不是太多,这应该可以工作。我可以以 60 FPS 的速度绘制 2k 个移动+旋转的立方体。我不记得你可以在 60 FPS 下绘制多少个立方体,但它比 2k 高很多。如果那还不够快,那么您必须查看 drawArrayInstanced。基本上,将所有矩阵存储在数组缓冲区中,并使用一个 drawArrayInstanced 具有正确偏移量等的调用来绘制所有模型。

编辑:还有 OP,如果您想了解 PIXI 如何进行顶点更新渲染(非统一实例化),请参阅 https://github.com/GoodBoyDigital/pixi.js/blob/master/src/pixi/renderers/webgl/utils/WebGLFastSpriteBatch.js

查看 chrome 中使用 WebGLInspector or the experimental canvas inspector 的帧调用揭示了一个完全未优化 渲染循环。

您可以而且应该使用同一个顶点缓冲区来渲染所有几何图形, 这样您就可以保存 bindBuffer 以及 vertexAttribPointer 调用。 您还可以节省 99% 的纹理绑定,因为您重复重新绑定一个和相同的纹理。只要您不将其他东西绑定到同一纹理单元,纹理就会保持绑定状态。

拥有状态缓存有助于避免绑定已经绑定的数据。

看看 my answer here 关于 gpu 作为状态机的内容。

优化渲染循环后,您可以继续考虑以下事项:

  • 使用ANGLE_instanced_arrays扩展
  • 避免在渲染循环中构建数据。
  • 使用交错顶点缓冲区。
  • 在某些情况下使用索引缓冲区也会增加 性能。
  • 检查您是否可以在着色器中削减几个 GPU 周期
  • 将您的对象分解成块,并在 CPU 侧进行视锥体剔除。