我正在开发一个Three.js WebGL场景,当我缩小时注意到60 FPS,以便所有观察结果(约20,000个三角形)都在视野中,但是当我&#39时FPS非常低; m放大,以便只查看一小部分三角形。
我想知道造成这种差异的原因。我的直觉是相反的情况:我假设当用户在近和远剪裁平面中放大时会从场景中移除许多会增加FPS的三角形。我想弄清楚为什么这种直觉在这个场景中是错误的。
如何识别three.js程序中使用的完整堆栈调用?理想情况下,我想识别所有函数/方法调用以及执行该函数所需的时间,以便我可以尝试找出我正在处理的着色器的哪一部分在用户时杀死FPS放大了。
答案 0 :(得分:3)
GPU有几个基本的地方,他们花费计算能力。应该很明显。一个是每个顶点运行一次顶点着色器。另一个是每个像素/片段运行一次片段着色器。
几乎总是比顶点多一个像素。单个1920x1080屏幕近200万像素,但可以覆盖3个顶点三角形或4或6个顶点四边形(2个三角形)。这意味着要覆盖整个屏幕,顶点着色器运行3到6次,但片段着色器运行了200万次!
向片段着色器发送过多的工作称为“填充绑定”。你最大化了填充率(用像素填充三角形),这就是你所看到的。在我2014年的MacBook Pro上更糟糕的情况下,在达到每秒60帧更新屏幕的填充率限制之前,我可能只能绘制大约6个像素的屏幕。
有各种解决方案。
第一个是z缓冲区。 GPU将首先测试深度缓冲区,看它是否需要运行片段着色器。如果深度测试失败,则GPU不需要运行片段着色器。因此,如果您对不透明对象进行排序和绘制,最近的对象首先是最后一个对象,那么当渲染三角形的像素时,距离中的大多数对象将无法通过深度测试。请注意,仅当您的片段着色器未写入gl_FragDepth
且未使用discard
关键字时,才可以执行此操作。
这是一种“避免透支”的方法。透支是指不止一次绘制的任何像素。如果你在距离上绘制一个立方体,然后向上绘制一个球体,使其覆盖立方体,那么对于立方体渲染的每个像素,它都被球体像素“透支”。那是浪费时间。
如果您的片段着色器非常复杂,因此运行缓慢,某些3D引擎将绘制“Z缓冲区预传”。他们将使用最简单的顶点和片段着色器绘制所有不透明的几何体。顶点着色器只需要位置。片段着色器只发出一个常量值。如果硬件支持,它们甚至会关闭绘图到颜色缓冲区gl.colorMask(false, false, false, false)
或者可能只创建一个深度帧缓冲区。然后他们使用它来填充深度缓冲区。完成后,他们使用昂贵的着色器和深度测试设置为LEQUAL
(或任何适用于其引擎的工具)再次渲染所有内容。这样每个像素只会渲染一次。当然它不是免费的,它仍需要GPU时间来尝试光栅化三角形并测试每个像素,但如果着色器价格昂贵,它仍然比过度绘制更快。
另一种方法是尝试找出哪些对象将被更近的对象遮挡,甚至不将它们提交给GPU。 There are tons of ways to do this,通常涉及边界球和/或边界框。一些potentially visible sets技术也可以帮助进行遮挡剔除。您甚至可以要求GPU使用occlusion queries来计算其中的一部分,尽管这只能在WebGL2中使用
查看是否填充限制的最简单方法是使画布变小,例如2x1像素(或者只是将浏览器窗口调整得非常小)。如果您的应用程序开始快速运行,则可能是填充限制。如果它仍然运行缓慢,它可能是几何绑定(顶点着色器做了太多的工作)或它的CPU绑定(无论你只是调用WebGL命令或计算动画或冲突,你在CPU上做的任何工作都花费太长时间或物理等等。
在你的情况下,你很可能是填充边界,因为你看到所有三角形都很小,它运行得很快(因为绘制的像素非常少),当你放大并且很多三角形覆盖屏幕然后它运行缓慢(因为绘制了太多像素)。
没有真正“简单”的解决方案。我真的只是取决于你想要做什么。显然你正在使用three.js,我知道它可以为透明对象排序。我不知道它是否对不透明物体进行排序。我认为列出的其他技术超出了three.js的范围,更多的是你的应用程序将事物带入和离开场景或将其可见性设置为假等等......
注意:here is a simple demo to show how little overdraw your GPU can handle。它只是绘制了一堆全屏四边形。默认情况下,它可能无法绘制那么多,特别是在全屏大小时,它不能再达到60fps。打开前后排序,它将能够绘制更多,仍然可以达到60fps。
另请注意,启用混合比禁用混合更慢。这应该是清楚的,因为没有混合GPU只是写像素。通过混合,GPU必须首先读取目标像素,以便它可以进行混合,因此速度较慢。