仅仅计算多边形来优化3D模型是不够的

图片

了解网格渲染过程的基础后,您可以应用各种技术来优化渲染速度。

引言


我可以使用多少个多边形? 这是艺术家在创建实时渲染模型时会问的一个非常普遍的问题。 这个问题很难回答,因为它不仅是数字问题。

我在第一个PlayStation时代开始了3D画家的职业生涯,后来成为图形程序员。 在开始为游戏创建3D模型之前,我想阅读这篇文章。 其中考虑的基本基础对许多艺术家有用。 尽管本文中的大多数信息不会显着影响日常工作的效率,但是它可以使您对图形处理单元(GPU)如何渲染创建的网格有基本的了解。

渲染速度通常取决于网格中多边形的数量。 但是,尽管多边形的数量通常与每秒帧速率(FPS)相关,但是您可能会发现,即使减少了多边形的数量,网格仍会缓慢渲染。 但是,通过了解一般如何渲染网格,可以应用一组技术来提高渲染速度。

如何显示面数据


要了解GPU如何绘制多边形,您首先需要考虑用于描述多边形的数据结构。 多边形由一组称为顶点和链接的点组成。 顶点通常存储为值数组,例如,如图1所示。


图1.简单多边形值的数组。

在这种情况下,三个维度(x,y和z)中的四个顶点给出12个值。 为了创建多边形,第二个值数组描述了顶点本身,如图2所示。


图2.到顶点的链接数组。

这些连接在一起的顶点形成两个多边形。 请注意,每个三角形具有三个角度的两个三角形可以由四个顶点描述,因为两个三角形都使用了顶点1和2。 为了让GPU处理此数据,假定每个多边形都是三角形。 GPU希望您使用三角形,因为它们是专门为绘制三角形而设计的。 如果需要绘制具有不同数量顶点的多边形,则需要一个应用程序将其划分为三角形,然后再渲染到GPU。 例如,如果创建一个包含六个多边形的立方体,每个立方体有四个边,那么这比创建一个由三个面组成的12个多边形的立方体更有效。 GPU将绘制这些三角形。 记住规则:您需要计算的不是多边形,而是三角形。

上一个示例中使用的顶点数据是三维的,但这不是必需的。 二维可能足以满足您的需求,但是通常您需要存储其他数据,例如,用于纹理的UV坐标和用于照明的法线。

多边形绘图


渲染多边形时,GPU首先确定在何处绘制多边形。 为此,他计算了三个顶点在屏幕上的位置。 此操作称为变换。 GPU中的这些计算由称为顶点着色器的小程序执行。

顶点着色器通常执行其他类型的操作,例如处理动画。 计算完多边形的所有三个顶点的位置之后,GPU将计算出该三角形中的哪些像素,然后开始使用另一个名为“片段着色器”(fragment shader)的小程序填充这些像素。 片段着色器通常每个像素执行一次。 但是,在极少数情况下,例如,每个像素可以执行几次,以改善抗锯齿效果。 片段着色器通常称为像素着色器,因为在大多数情况下,片段与像素相对应(请参见图3)。


图3.屏幕上绘制了一个多边形。

图4显示了渲染多边形时GPU执行的动作序列。


图4. GPU渲染多边形的顺序。

如果将三角形分成两个并绘制两个三角形(请参见图5),则该过程将对应于图6。


图5.将多边形划分为两个。


图6. GPU绘制两个多边形的过程。

在这种情况下,需要进行两倍的转换和准备,但是由于像素数保持不变,因此该操作无需光栅化其他像素。 这表明将多边形数量加倍并不一定会使渲染时间加倍。

使用顶点缓存


如果查看上一个示例中的两个多边形,则可以看到它们具有两个公共顶点。 可以假定这些顶点必须计算两次,但是称为顶点缓存的机制使您可以重用计算结果。 可以重新使用的顶点着色器计算结果存储在缓存中-包含最后几个顶点的一小部分内存。 使用顶点缓存绘制两个多边形的过程如图7所示。


图7.使用顶点缓存绘制两个多边形。

多亏了顶点缓存,如果两个多边形具有相同的顶点,则它们绘制速度几乎可以与一个多边形一样快。

我们处理顶点的参数


为了使顶点可重复使用,每次使用时顶点必须保持不变。 当然,位置应保持不变,但其他参数也不应更改。 传递到顶部的参数取决于所使用的引擎。 这是两个常用参数:

  • 纹理坐标
  • 正常的

将UV应用于3D对象时,任何创建的接缝都将意味着无法共享沿接缝的顶点。 因此,在一般情况下,应避免接缝(见图8)。


图8.紫外线缝合线纹理。

为了对表面进行适当的照明,每个顶点通常都存储法线-从表面指向的向量。 由于所有具有相同顶点的多边形均由一个法线定义,因此它们的形状看起来很平滑。 这称为平滑阴影。 如果每个三角形都有自己的法线,则多边形之间的边会变得明显,并且表面看起来很平坦。 因此,这称为平面阴影。 图9显示了两个相同的网格,一个具有平滑阴影,另一个具有平面阴影。


图9.平滑阴影与平面阴影的比较。

这种平滑的阴影几何图形由18个三角形组成,并具有16个公共顶点。 因为没有共享顶点,所以18个三角形的平面阴影需要54(18 x 3)个顶点。 即使两个网格具有相同数量的多边形,它们的渲染速度仍将不同。

形式的重要性


GPU之所以能够快速工作,主要是因为它们可以并行执行许多操作。 GPU营销材料通常集中在确定可同时执行多少个GPU的管线数量上。 GPU绘制多边形时,它会执行许多流水线任务以填充像素的正方形。 这通常是一个八乘八像素的正方形。 GPU继续执行此操作,直到所有像素都满为止。 显然,三角形不是正方形,因此正方形的某些像素将位于三角形内部,而其他像素将位于三角形外部。 设备使用正方形中的所有像素,即使三角形之外的像素也可以使用。 计算完正方形中的所有顶点后,设备将丢弃三角形外部的像素。

图10显示了一个三角形,需要绘制三个正方形(平铺)。 使用大多数计算的像素(青色),红色显示的像素超出三角形的边界,将被丢弃。


图10.三个用于绘制三角形的图块。

图11中具有完全相同像素数但被拉伸的多边形需要填充更多的图块; 每个图块(红色区域)中的大多数结果将被丢弃。


图11.在拉伸图像中填充图块。

渲染的像素数只是因素之一。 多边形的形状也很重要。 为了提高效率,请尽量避免使用狭长的多边形,并优先选择边长近似相等的三角形,其角度接近60度。 图12中的两个平面以两种不同的方式进行了三角剖分,但渲染时它们看起来相同。


图12。以两种不同方式对表面进行三角剖分。

它们具有完全相同数量的多边形和像素,但是由于左侧的表面比右侧的表面具有更长,更窄的多边形,因此其渲染速度会更慢。

重画


要绘制六点星形,可以创建一个由10个多边形组成的网格,或者仅从两个多边形中绘制相同的形状,如图13所示。


图13.渲染六点星形的两种不同方式。

您可以确定绘制两个多边形比绘制10个多边形更快。但是,在这种情况下,这很可能是不正确的,因为星形中心的像素将被绘制两次。 这种现象称为透支。 从本质上讲,这意味着多次重绘像素。 重新绘制自然会在整个渲染过程中发生。 例如,如果某个字符被某个列部分隐藏,则尽管该列与该字符的一部分重叠,也会完整地绘制该字符。 一些引擎使用复杂的算法来避免渲染最终图像中不可见的对象,但这是一项艰巨的任务。 与绘制GPU相比,CPU通常更难找出不需要渲染的内容。

作为一名艺术家,您必须接受一个事实,那就是您无法摆脱重涂油漆的问题,但是最好删除掉看不见的表面。 如果您正在与开发团队合作,则要求向游戏引擎添加调试模式,使所有内容变得透明。 这将使查找可删除的隐藏多边形更加容易。

在地板上安装抽屉


图14显示了一个简单的场景:一个盒子站在地板上。 地板仅包含两个三角形,盒子包含10个三角形。 此场景中的重绘以红色显示。


图14.站在地板上的抽屉。

在这种情况下,GPU将用抽屉将地板的一部分绘制到地板上,尽管这是不可见的。 相反,如果我们在框下面的地板上创建了一个孔,那么我们将收到更多的多边形,而绘制的多边形要少得多,如图15所示。


图15.抽屉下方的一个孔,以避免重新绘制。

在这种情况下,这完全取决于您的选择。 有时值得减少多边形的数量,以得到重新绘制。 在其他情况下,值得添加多边形以避免重绘。 另一个示例:下面显示的两个图形是外观相同的表面网格,其中突出了一些点。 在第一个网格(图16)中,尖端位于表面上。


图16.尖端位于表面上。

在图17中的第二个网格中,在尖端下方的表面上切出了孔,以减少重绘次数。


图17.在尖端下方切出了孔。

在这种情况下,为切孔添加了许多多边形,其中一些形状较窄。 另外,我们摆脱的重绘表面不是很大,因此在这种情况下该技术无效。

想象一下,您正在模拟一栋站在地面上的房屋。 要创建它,您可以使地球保持不变,也可以在房屋下方的地面上挖一个洞。 如果未在房屋下方开孔,则重画的次数更多。 但是,选择取决于玩家从中看到房屋的几何形状和视角。 如果您在房屋底部下方绘制泥土,则如果您走进房屋并向下看,将产生大量重绘。 但是,如果您从飞机上看房子,差别不会特别大。 在这种情况下,最好在游戏引擎中设置调试模式以使表面透明,以便您可以看到玩家可见的表面下绘制的内容。

当Z缓冲区发生Z冲突时


GPU绘制两个重叠的多边形时,如何确定哪个在另一个之上? 第一批计算机图形学研究人员花费了大量时间来研究此问题。 Ed Catmell(后来成为Pixar和Walt Disney Animation Studios的总裁)写了一篇文章,概述了完成此任务的十种不同方法。 在文章的一部分中,他指出,如果计算机具有足够的内存来为每个像素存储一个深度值,则解决该问题的方法将很简单。 在1970年代和1980年代,它的存储量非常大。 但是,如今大多数GPU都是这样工作的:这样的系统称为Z缓冲区。

Z缓冲区(也称为深度缓冲区)的工作方式如下:每个像素的深度值都与之关联。 设备绘制对象时,它会计算从相机绘制像素的距离。 然后,它检查现有像素的深度值。 如果距摄像机比新像素更远,则将绘制新像素。 如果现有像素比新像素更接近相机,则不会绘制新像素。 即使多边形相交,这种方法也解决了许多问题并且可以工作。


图18.深度缓冲区处理后的相交多边形。

但是,Z缓冲区没有无限的准确性。 如果两个表面与相机的距离几乎相等,则这会使GPU感到困惑,并且可以随机选择其中一个表面,如图19所示。


图19.具有相同深度的表面存在显示问题。

这称为Z格斗,看起来非常越野车。 距离相机表面越远,Z冲突通常会变得越严重。 引擎开发人员可以将改正合并到其中,以解决该问题,但是,如果美术师创建的多边形足够接近且彼此重叠,则仍然可能会出现问题。 另一个示例是一面墙上贴有海报的墙壁。 海报与摄像机的距离与后面的墙壁几乎位于同一深度,因此Z冲突的风险非常高。 解决方法是在海报下方的墙上切一个洞。 这也将减少重画数量。


图20.重叠多边形的Z冲突示例。

在极端情况下,即使对象彼此接触,也可能发生Z冲突。 图20显示了地板上的抽屉,由于我们没有在抽屉下面的地板上开孔,因此z缓冲区可能会在地板与抽屉相交的边缘附近混淆。

使用绘图调用


GPU已经变得非常快-如此之快以至于CPU可能跟不上它们。 由于GPU本质上是为执行一项任务而设计的,因此使它们更容易快速开始工作。 图形本质上与多个像素的计算有关,因此您可以创建可并行计算多个像素的设备。 但是,GPU仅渲染其绘制CPU的命令。 如果CPU无法快速将数据“馈入” GPU,则视频卡将处于空闲状态。 每次CPU命令GPU绘图时,这称为绘图调用。 最简单的绘制调用包括渲染一个网格,包括一个着色器和一组纹理。

想象一下,一个慢处理器可以每帧传输100个绘制调用,而一个快速GPU可以每帧绘制一百万个多边形。 在这种情况下,理想的绘制调用可以绘制10,000个多边形。 如果您的网格仅包含100个多边形,则GPU每帧只能绘制10,000个多边形。 也就是说,GPU有99%的时间处于空闲状态。 在这种情况下,我们可以轻松增加网格中的多边形数量而不会丢失任何内容。

抽奖的内容及其成本在很大程度上取决于特定的引擎和体系结构。一些引擎可以将多个网格合并到一个绘制调用中(执行批处理,批处理),但是所有网格必须具有相同的着色器,否则可能会有其他限制。Vulkan和DirectX 12等新API专门用于解决此问题,方法是优化程序与图形驱动程序的通信方式,从而增加了可在单个帧中传输的绘制调用的数量。

如果您的团队正在编写自己的引擎,请询问引擎的开发人员有哪些局限性。如果您使用虚幻引擎或Unity等现成的引擎,请运行性能基准测试以确定引擎功能的极限。您可能会发现可以增加多边形的数量而不会降低速度。

结论


我希望本文能作为一个很好的介绍,以帮助您了解渲染性能的各个方面。在不同制造商的GPU中,一切都是以自己的方式实现的。对于特定的引擎和硬件平台,有很多保留和特殊条件。始终与渲染程序员保持公开对话,以在项目中使用他们的建议。

关于作者


埃斯基尔·斯坦伯格(Eskil Steenberg)是游戏和工具的独立开发商,他是顾问和独立项目的作者。所有屏幕截图都是使用Esquil开发的工具在活动项目中拍摄的。您可以在Quel Solaar网站和他的@quelsolaar Twitter帐户上了解有关他的工作的更多信息

Source: https://habr.com/ru/post/zh-CN433186/


All Articles