一、减少需要处理的顶点数目
顶点数目同样有可能成为GPU的性能瓶颈,下面给出3个常用的顶点优化策略。
1.1 优化几何体
建模时尽可能减少模型中三角面片的数目,一些对于模型没有影响、或是肉眼非常难察觉到区别的顶点都要尽可能去掉。美术人员往往需要优化网格结构。很多三维软件都有相应的优化选项,可以自动优化网格结构。
在unity渲染统计窗口中可以查看到渲染当前帧需要的三角面片数目和顶点数目。需要注意的是,unity中显示的数目往往要多于建模软件里显示的顶点数,通常unity中显示的数目要大很多,其实这是因为在不同角度上计算的,都有各自的道理,但我们真正应该关心的是unity里显示的数目。
简单解释下造成这种不同的原因,三维软件更多地是站在我们人类的角度理解顶点的,即组成几何体的每一个点就是一个单独的点。而unity是站在GPU的角度上去计算顶点数的。在GPU看来,有时需要把一个顶点拆分成两个或更多的顶点。这种顶点一分为多的原因主要有两个:一是为了分离纹理坐标,另一个是为了产生平滑的边界。它们的本质,其实都是因为对于GPU来说,顶点每一个属性和顶点之间必须是一对一的关系。而分离纹理坐标,是因为建模时一个顶点的纹理坐标有多个。例如对于一个立方体,它的6个面之间虽然使用了一些相同的点,但在不同面上,同一个顶点的纹理坐标可能并不相同。对于GPU来说,这是不可理解的,因此他必须把这个顶点拆分成多个具有不同纹理坐标的顶点。而平滑边界也是类似,不同的是,此时一个顶点可能会对应多个法线信息或切线信息,这通常是因为我们要决定一个边是一条硬边还是一条平滑边。
对于GPU来说,它本质上只关心有多少个顶点,因此尽可能减少顶点数目其实才是我们真正需要管线的事情,最后一条几何体优化建议:移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。
1.2 模型的LOD技术
这种技术的原理是,当一个物体离摄像机很远时,模型上的很多细节是无法被察觉到的,因此LOD允许当对象逐渐远离摄像机时,减少模型上的面片数量,从而提高性能。
在unity中可以使用LOD Group组件来为一个物体构建一个LOD,我们需要为同一个对象准备多个包含不同细节程度的模型,然后把他们赋值给LOD Group组件中不同等级,unity就会自动判断当前位置上需要使用哪个等级的模型。
1.3 遮挡剔除技术
遮挡剔除可以用来消除那些在其他物件后面看不到的物件,这意味着资源不会浪费在计算那些看不到的顶点上,进而提升性能。
我们需要把遮挡剔除和摄像机的视椎体剔除(Frustum Culling)区分开来。视椎体剔除只会剔除掉那些不在摄像机的视野范围内的对象,但不会判断视野中是否有物体被其他物体挡住。而遮挡剔除会使用一个虚拟的摄像机来遍历场景,从而构建一个潜在可见的对象几何的层级结构。在运行时刻,每个摄像机将会使用这个数据来识别哪些物体是可见的,而哪些物体被其他物体挡住不可见,使用遮挡剔除技术,不仅可以减少处理的顶点数目,还可以减少overdraw,提高游戏性能。
模型的LOD技术和遮挡剔除技术可以同时减少CPU和GPU的负荷。CPU可以提交更少的draw call,而GPU需要处理的顶点和片元数目也减少了。
二、减少需要处理的片元数目
另一个造成GPU瓶颈的是需要处理过多的片元,这部分优化的重点在于减少overdraw,简单来说,overdraw指的就是同一个像素被绘制了多次。
unity还提供了查看overdraw的视图,我们可以在Scene视图左上方下拉菜单选中overdraw即可:
实际上,这里的视图只是提供了查看物体相同遮挡的层数,并不是真正的最终屏幕绘制的overdraw。也就是说,可以理解为它显示的是,如果没有使用任何深度测试和其他优化策略时的overdraw。这种视图是通过把所有对象都渲染成一个透明的轮廓,通过查看透明颜色的累计程度,来判断物体之间的遮挡。当然我们可以使用一些错误来防止这种最坏情况的出现。
2.1 控制绘制顺序
为了最大限度地避免overdraw,一个重要的优化策略就是控制绘制顺序。由于深度测试的存在,如果我们可以保证物体都是从前往后绘制的,那么就可以很大程度上减少overdraw,这是因为在后面绘制的物体由于无法通过深度测试,因此就不会在进行后面的渲染处理。
在unity中,那些渲染队列数目小于2500(如“Background” “Geometry”和“AlphaTest”)的对象都被认为是不透明(opaque)的物体,这些物体总体上是从前往后绘制的,而使用其他的队列(如“Transparent” “Overlay”等)的物体,则是从后往前绘制的。这意味着我们可以尽可能地把物体的队列设置为不透明物体的渲染队列,而尽量避免使用半透明队列。
而且我们还可以充分利用unity的渲染队列来控制绘制顺序。例如在第一人称射击游戏中,对于游戏中的主要人物角色来说,他们使用的shader往往比较复杂,但是由于他们通常会挡住屏幕的很大一部分区域,因此我们可以先绘制它们(使用更小的渲染队列)。而对于一些地方角色,它们通常会出现在各种掩体后面,因此我们在所有常规的不透明物体后面渲染它们(使用更大的渲染队列)。而对于天空盒子来说,它几乎覆盖了所有的像素,而且我们知道它永远会出现在所有物体的后面,因此它的队列可以设置为“Geometry+1”。这样就可以保证不会因为它而造成overdraw。
这些排序的思想往往可以节省许多渲染时间。
2.2 时刻警惕透明物体
对于半透明对象来说,由于没有开启深度写入,因此想要得到正确的渲染效果,就必须从后往前渲染。这意味着,半透明物体几乎一定会造成overdraw。如果如果不注意,在一些机器上可能会造成严重的性能下降。例如对于GUI对象来说,他们大多被设置成了半透明,如果屏幕中GUI占据的比例太多,而主相机又没有进行调整而是投影整个屏幕,那么GUI就会造成大量overdraw。
因此如果场景中包含了大面积的半透明对象,或者很多层相互覆盖的半透明对象(即便他们每个的面积可能都不大),或者是透明的粒子效果,在移动设备上也会造成大量的overdraw,这应该是避免的。
对于上述GUI这种情况,我们可以尽量减少窗口中GUI所占的面积,如果实在无能为力,我们可以把GUI的绘制和三维场景的绘制交给不同的摄像机,而其中负责三维场景的摄像机的视角范围尽量不要和GUI的相互重叠。当然这样对游戏的美观度产生一定影响,因此我们可以在代码中对机器的性能进行判断,例如首先关闭一些耗费性能的功能,如果发现这个机器表现非常良好,再尝试开启一些特效功能。
在移动平台上,透明度测试也会影响游戏性能,虽然透明度测试没有关闭深度写入,但由于它的实现使用了discard或clip操作,而这些操作会导致一些硬件化的优化策略失效。例如PowerVR使用基于瓦片的延迟渲染技术,为了减少overdraw它会在调用片元着色器前就判断哪些瓦片被真正渲染的。但是由于透明度测试在片元着色器中使用了discard函数改变了片元是否被渲染的结果,因此就无法使用上述优化策略了。也就是说只有在执行了所有的片元着色器后,GPU才知道哪些片元被真正渲染到屏幕上了。这样原先可以减少overdraw的优化就都无效了。这种时候使用透明度混合的性能往往比使用透明度测试更好。
2.3 减少实时光照和阴影
实时光照对于移动平台是一种非常昂贵的操作。如果场景中包含了过多的点光源,并且使用了多个Pass的shader,那么很可能会造成性能下降。例如一个场景如果包含了3个逐像素的点光源,而且使用了逐像素的shader,那么很有可能将draw call数目(CPU的瓶颈)提高3倍,同时也会增加overdraw(GPU的瓶颈)。这是因为对于逐像素的光源来说,被这些光源照亮的物体需要被再渲染一次。更糟的是,无论是静态批处理还是动态批处理,对于这种额外的处理逐像素光源的Pass都无法进行批处理,也就是说,他们会中断批处理。
当然,游戏场景还是需要光照才能得到出色的画面效果。许多移动平台的游戏往往使用了烘焙技术,把光照提前烘焙到一张光照纹理(lightmap)中,然后在运行时刻只需要根据纹理采样得到光照结果即可。另一个模拟光源的方法是使用God Ray。场景中很多小型光源的效果都是靠这种方法模拟的,他们一般并不是真正的光源,很多情况是通过透明纹理模拟得到的。在移动平台上,一个物体使用的逐像素光源数目应该小于1(不包括平行光),如果一定要使用更多实时光,可以选择逐顶点光照来代替。
有些游戏角色看起来使用了非常复杂高级的光照计算,但这实际上是优化后的结果,开发者把复杂的光照计算存储到一张查找纹理(lookup texture,查找表,LUT)中,然后在运行时刻,我们只需要使用光源方向、视角方向、法线方向等参数,对LUT采样得到光照结果即可。使用这样的查找纹理,不仅可以让我们使用更出色的光照模型,例如更复杂的BRDF模型,还可以利用查找纹理的大小来进一步优化性能,例如主要角色可以使用更大分辨率的LUT,而一些NPC就使用较小的LUT。他们往往会开发一个LUT烘焙工具,来帮助美术人员快速调整光照模型,并把结果存储到LUT中。
实时阴影同样是一个非常消耗性能的效果,不仅是CPU需要提交更多的draw call,GPU也需要进行更多的处理。因此我们应该尽量减少实时阴影,例如使用烘焙把静态物体的阴影信息存储到光照纹理中,而只对场景中的动态物体使用适当的实时阴影。
版权声明:本文为CSDN博主「小橙子0」的原创文章,
遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cgy56191948/article/details/102960196