GPU 有几个消耗计算能力的基本地方。这应该是很明显的。一种是每个顶点运行一次顶点着色器。另一种是每个像素/片段运行一次片段着色器。
像素几乎总是比顶点多很多。单个 1920x1080 屏幕有近 200 万像素,但可以用 3 顶点三角形或 4 或 6 顶点四边形(2 个三角形)覆盖。这意味着为了覆盖整个屏幕,顶点着色器运行了 3 到 6 次,但片段着色器运行了 200 万次!
向片段着色器发送过多的工作称为“填充绑定”。您最大化了填充率(用像素填充三角形),这就是您所看到的。在最糟糕的情况下,在我的 2014 年 MacBook Pro 上,我可能只能在 6 个左右的屏幕上绘制像素,然后才能达到每秒 60 帧更新屏幕的填充率限制。
对此有多种解决方案。
第一个是 z 缓冲区。 GPU 将首先测试深度缓冲区,看看是否需要运行片段着色器。如果深度测试失败,GPU 不需要运行片段着色器。因此,如果您对不透明对象进行排序和绘制,首先是最近的对象,最后是最远的对象,那么远处的大多数对象在渲染其三角形的像素时将无法通过深度测试。请注意,只有当您的片段着色器不写入时,这才有可能gl_FragDepth
并且不使用discard
关键词。
这是一种“避免透支”的方法。过度绘制是指多次绘制的任何像素。如果您在远处绘制一个立方体,然后在近距离绘制一个球体,使其覆盖立方体,那么对于为立方体渲染的每个像素,它都会被球体像素“透支”。那是浪费时间。
如果您的片段着色器确实很复杂,因此运行速度很慢,某些 3D 引擎将绘制“Z 缓冲区预通道”。他们将使用最简单的顶点和片段着色器绘制所有不透明几何体。顶点着色器只需要位置。片段着色器仅发出一个常量值。他们甚至会关闭对颜色缓冲区的绘制gl.colorMask(false, false, false, false)
或者如果硬件支持的话,可能会制作一个仅深度的帧缓冲区。然后他们用它来填充深度缓冲区。完成后,他们使用昂贵的着色器再次渲染所有内容,并将深度测试设置为LEQUAL
(或任何适合他们的引擎的东西)。这样每个像素只会被渲染一次。当然它不是免费的,它仍然需要 GPU 时间来尝试光栅化三角形并测试每个像素,但如果着色器很昂贵,它仍然比过度绘制更快。
另一种方法是尝试找出哪些对象将被较近的对象遮挡,甚至不将它们提交给 GPU。有很多方法可以做到这一点 https://www.gamasutra.com/view/feature/131801/occlusion_culling_algorithms.php,通常涉及边界球和/或边界框。一些潜在可见集 https://en.wikipedia.org/wiki/Potentially_visible_set技术还可以帮助进行遮挡剔除。您甚至可以使用 GPU 来计算其中的一些内容遮挡查询 http://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch29.html虽然这仅在 WebGL2 中可用
查看是否已填充的最简单方法是使画布变小,例如 2x1 像素(或者将浏览器窗口的大小设置得非常小)。如果您的应用程序开始快速运行,则很可能已满。如果它仍然运行缓慢,则可能是几何限制(顶点着色器做了太多工作)或者是 CPU 限制(无论您在 CPU 上执行的任何工作都花费了太长时间,无论是调用 WebGL 命令还是计算动画或碰撞)或物理或其他)。
在你的情况下,你可能会被填充,因为你看到当所有三角形都很小时,它运行得很快(因为绘制的像素很少),而当你放大并且大量三角形覆盖屏幕时,它运行得很慢(因为太正在绘制许多像素)。
不存在真正“简单”的解决方案。我真的只取决于你想做什么。显然你正在使用 Three.js,我知道它可以对透明对象进行排序。我不知道它是否适合不透明的物体。我认为列出的其他技术超出了 Three.js 的范围,更多取决于您的应用程序将事物带入和带出场景或将其可见性设置为 false 等...
Note: 这是一个简单的演示,展示您的 GPU 可以处理多少过度绘制 http://greggman.github.io/doodles/overdraw.html。它只是绘制一堆全屏四边形。默认情况下,在无法再达到 60 fps 之前,它可能无法绘制那么多,尤其是在全屏尺寸下。打开从前到后排序,它将能够绘制更多内容,并且仍能达到 60fps。
另请注意,启用混合比禁用混合要慢。这应该很清楚,因为在不混合的情况下,GPU 只是写入像素。通过混合,GPU 必须首先读取目标像素,以便它可以进行混合,因此速度较慢。