在 Three.js 中调试低 FPS

Debugging low FPS in Three.js

我正在处理 Three.js WebGL 场景,当我缩小时我注意到 60 FPS,以便所有观察(~20,000 个三角形)都在视野中,但当我缩小时 FPS 非常低放大以便只有一小部分三角形在视图中。

我想弄清楚是什么导致了这种差异。我的直觉是,情况恰恰相反:我假设当用户在近处和远处的裁剪平面中放大时,会从场景中移除许多三角形,这会增加 FPS。我想搞清楚为什么在这个场景中这种直觉是错误的。

如何识别 three.js 程序中使用的完整调用堆栈?理想情况下,我想确定所有函数/方法调用以及执行该函数所需的时间,以便我可以尝试找出我正在处理的着色器的哪一部分在用户放大时杀死了 FPS。

GPU 有几个基本的计算能力用处。它应该很明显。一个是 运行 对每个顶点使用一次顶点着色器。另一个是 运行 每个 pixel/fragment.

一次片段着色器。

几乎总是有比顶点多一吨的像素。单个 1920x1080 屏幕有近 200 万像素,但可以覆盖在一个 3 顶点三角形或 4 或 6 顶点四边形(2 个三角形)中。这意味着覆盖整个屏幕顶点着色器 运行 3 到 6 次,但片段着色器 运行 200 万次!!!

向片段着色器发送过多的工作被称为"fill bound"。您最大化了填充率(用像素填充三角形),这就是您所看到的。在我的 2014 MacBook Pro 上更糟糕的情况下,在达到以每秒 60 帧更新屏幕的填充率限制之前,我可能只能绘制 6 个左右的屏幕像素。

对此有多种解决方案。

首先是 z 缓冲区。 GPU 将首先测试深度缓冲区,看看它是否需要 运行 片段着色器。如果深度测试失败,GPU 不需要 运行 片段着色器。因此,如果您对不透明对象进行排序和绘制,从最近的对象到最远的对象最后,那么在渲染其三角形像素时,远处的大多数对象都将无法通过深度测试。请注意,这只有在您的片段着色器不写入 gl_FragDepth 并且不使用 discard 关键字时才有可能。

这是"avoiding overdraw"的一个方法。过度绘制是多次绘制的任何像素。如果您在远处绘制一个立方体,然后在近处绘制一个球体,使其覆盖立方体,那么对于为立方体渲染的每个像素,它都是球体像素的 "overdrawn"。那是浪费时间。

如果您的片段着色器非常复杂,因此 运行 速度很慢,一些 3D 引擎会绘制 "Z buffer pre-pass"。他们将使用最简单的顶点和片段着色器绘制所有不透明几何体。顶点着色器只需要位置。片段着色器只发出一个常量值。如果硬件支持,他们甚至会关闭绘制到颜色缓冲区 gl.colorMask(false, false, false, false) 或者可能制作一个仅深度帧缓冲区。然后他们使用它来填充深度缓冲区。完成后,他们使用昂贵的着色器再次渲染所有内容,并将深度测试设置为 LEQUAL(或适用于他们的引擎的任何内容)。这样每个像素只会被渲染一次。当然它不是免费的,它仍然需要 GPU 时间来尝试光栅化三角形并测试每个像素,但如果着色器很昂贵,它仍然比透支更快。

另一种方法是尝试找出哪些对象将被更近的对象遮挡,甚至不将它们提交给 GPU。 There are tons of ways to do this, usually involving bounding spheres and or bounding boxes. Some potentially visible sets techniques can also help with occlusion culling. You can even ask the GPU to compute some of this using occlusion queries 尽管这仅适用于 WebGL2

查看您是否填充边界的最简单方法是将您的 canvas 变小,例如 2x1 像素(或者将您的浏览器 window 调整得非常小)。如果您的应用程序启动速度 运行ning 很快,它很可能已满。如果它仍然 运行ning 慢,它可能是几何限制(顶点着色器做了太多工作)或者它是 CPU 限制(无论你在 CPU 上做什么工作都需要太长了,无论是调用 WebGL 命令还是计算动画、碰撞、物理等等。

在你的情况下,你可能是填充边界,因为你看到当所有三角形都很小时它 运行s 很快(因为绘制的像素很少)与当你放大和很多三角形时覆盖屏幕然后它 运行 很慢(因为绘制了太多像素)。

没有真正的 "simple" 解决方案。我真的只是取决于你想做什么。显然你正在使用 three.js,我知道它可以对 t运行sparent 对象进行排序。我不知道它是否对不透明物体进行排序。我认为列出的其他技术有点超出 three.js 的范围,更多取决于您的应用程序将事物带入和带出场景或将它们的可见性设置为 false 等...

注:here is a simple demo to show how little overdraw your GPU can handle。它只是绘制了一堆全屏四边形。默认情况下,它可能无法绘制那么多,尤其是在全屏尺寸下,然后才能达到 60fps。打开从前到后排序,它就能画得更多,而且仍然能达到 60fps。

另请注意,启用混合比禁用混合慢。这应该很清楚,因为没有混合,GPU 只是写入像素。通过混合,GPU 必须首先读取目标像素才能进行混合,因此速度较慢。