CPU与GPU瓶颈的详述
参考文章:https://www.wepc.com/tips/cpu-gpu-bottleneck/
在实时渲染领域,或者说我们常玩的游戏中,如果CPU和GPU性能达到瓶颈,最直观的体现就是FPS很低,毫无游戏体验感。
瓶颈的定义
瓶颈,就和其定义一样,发生的原因是同时送往处理的数据大小或者同时可以处理的数据大小存在一个限制。换句话说,这意味着返回一个处理过的数据的性能相比正送往处理的数据的大小来说不大足够。
在瓶颈处理中的两个组件是CPU和GPU。如果二者处理速度差异过大,会出现瓶颈。
CPU瓶颈
CPU瓶颈发生的原因是处理器处理和传输数据的速度不够快。如果用了很好的显卡,但CPU跟不上GPU的处理速度的话,就会出现CPU的瓶颈。
CPU在游戏中负责动作、物理、UI、音乐和其它复杂CPU相关的处理,如果传输数据的速度有上限的话,就会出现瓶颈。一种解决方案是通过调节方案平衡处理速度。
GPU瓶颈
和CPU类似,瓶颈发生的情况在于GPU跟不上CPU的处理速度。
CPU和GPU出现瓶颈的原因
除了硬件,游戏本身也会导致瓶颈的出现。游戏的设计不尽相同,有依赖CPU的游戏和依赖GPU的游戏。
依赖CPU的游戏通常FPS高,但图形分辨率很低,例如《城市:地平线》、《我的世界》、《文明五》、《刺客信条:黑旗》。
那些更高帧率的游戏通常拥有高端显卡,这些是依赖GPU的游戏,如:《巫师3》、《地铁:最后的束光》、《无主之地2》、《消逝的光芒》。
总体来说,CPU和GPU的瓶颈并不仅仅来自于组件本身。
解决CPU和GPU瓶颈的方法
并没有一个方法判断是CPU还是GPU导致了瓶颈。我们可以使用监控软件,类似于MSI Afterburner。
监控CPU和GPU进程
使用类似的监控软件,可以在游戏时日志输出CPU和GPU的内存占用。如果CPU占用高,GPU占用第,那么就是CPU瓶颈,反之就是GPU瓶颈。
解决瓶颈问题
解决瓶颈问题的关键是平衡加载相同组件的主力过程,来达到平衡。
提升游戏的分辨率
如果有CPU瓶颈,提升GPU的处理占用可以平衡这一加载过程,可以提高游戏图像的分辨率,那么GPU就需要更多的时间来渲染待处理的数据。让CPU和GPU以相同的速度处理数据的话,出现瓶颈的机会会更少。
关闭不必要的后台进程
一种减少CPU瓶颈问题的方法是关闭后台进程,可以显著的提升FPS数量。
内存超频
内存几乎直接决定CPU处理数据的速度,通过超频内存,就会显著提升FPS。
CPU超频
超频CPU可以为处理器提供更多的空间。
使用更少的CPU密集性游戏设置
降低游戏内CPU密集性计算的设置的数量可以显著解决一些瓶颈问题,包括但不限于绘制距离(如远处的可能直接关闭)、植被、密集人口等。
其它
对于GPU瓶颈的话,没有太多可以直接调整显卡的操作,不过往往可以从下面的方面去思考,即优化游戏本身。
在开始前,我们只考虑显示,从游戏开始到进行,往往可以将游戏的资源数据直观分为(以开放世界游戏为例):网格数据、材质数据和纹理数据。
对于网格数据,不考虑过多的话,最基本的单位就是顶点,那么从这一方面入手的话,有几个优化的方法,即尽量减少每帧使用顶点的数量:
- 在游戏加载场景时,首先根据摄像机视锥的可视范围,进行选择性加载。
- 之后,对于预先无法看见的物体,选择性关闭。
- 对于有遮挡关系的物体,可以有选择性的关闭。
- 接着,对于远处的资源,如果经测试后发现关闭不关闭对于视觉影响不大,可以选择性关闭。
- 然后,对于网格数据,按单个模型为单位的话,可以为其准备多套细分级别的模型,按距离远近有选择地切换。
对于材质数据,其基本单位在底层是着色器,可以据此产生几个优化方案:
- 直观来说,多个网格可能使用同一个着色器或者材质,我们可以考虑这些网格只使用一种材质,不设置实例副本,如果有需要甚至可以将这些使用相同材质的网格合并起来。不过有局限性,即限制了自由度,且合并后的网格很难去局部修改。这一操作与Draw Call的数量直接挂钩,批处理的优势就是可以降低Draw Call的数量,只是后续想要去修改的话会非常困难,所以需要前期做好规划,如将一些静态的当作背景的模型合并在一起。
- 如果是对于着色器本身的话,可以按照着色器本身地类型进行优化。对于顶点着色器,它目前的主要功能是进行相关空间变换,但由于它是逐顶点的计算,相比逐像素计算来说着色器的调用次数可以显著降低,所以可以提前规划好一些变量(插值后不会有太大的影响的话),在顶点着色器中计算好在送往接下来的阶段。对于细分曲面着色器,该阶段就是用来帮助降低顶点数据大小的,我们可以不必提前准备大量的顶点,可以在细分曲面着色器中帮助生成额外的三角形来优化模型。对于几何着色器,它的主要用途就是生成额外的基本体,可操作性很高,处理生成粒子这种VFX领域的应用外,也可以减少顶点着色器中的一些计算,同时生成额外的基本体,也可以帮助进行一些辅助运算。对于像素着色器,就像之前所说的,尽量减少相关矢量和矩阵的计算,同时对于光源(这里讲解三种常见光),平行光的话,一般没有太大的问题,对于点光源,一般会考虑衰减范围,我们可以直接定义一个距离(很难察觉出衰减瞬间消失的距离),让之后的片段无法受到点光源的影响,对于聚光灯(这里一般指第一人称角色的手电筒),可以让过远的物体不受影响,此外,还可以对光源进行类型切换,如果光源(如点光源)距离较远,可以将其切换为逐顶点光照的光源,降低计算光照次数,甚至于不对周围的片段进行光照,只是渲染出灯光的颜色,甚至关闭光源。此外,还可以对光照模型进行切换,可以按照距离以及游玩性来对模型划分主次,比如主要的模型可以使用计算量高的PBR光照,变得次要的话可以使用Phong或Blinn-Phong代替,甚至关闭高光,使用Lambert。另外,在像素着色器中还会进行一些采样操作,可以根据距离有选择地使用直接采样或邻域采样优化(即滤波操作)。还有,可以有选择地根据主次选择后处理效果的应用,比如发光,可以与光源的主次适配,同时,能够使用屏幕空间实现的效果就不要使用其它的空间。最后,对于光照变化不大的物体,如静态背景物体,可以提前渲染好对应光照纹理以及阴影纹理等,只需要采样去进行直接的颜色计算即可,不必实时计算。
- 除此之外,由于GPU架构本身的原因,条件分支和流控制存在性能限制,所以说尽量避免使用条件分支和流控制。
对于纹理数据,一些API对其的操作一般包括,采样以及滤波方式,还有mipmaps等。可以从纹理本身到游戏中的相关设置来优化:
- 对于纹理本身,其图像格式就很重要,按通道算的话,不需要alpha通道就不要用png等格式存储,只需要单通道的话同理。按图片分辨率来算的话,对于一些要求不高的模型,或者说按照距离和游玩性等划分主次后,那些级别较低的模型就不需要使用分辨率高的纹理。按图片质量来算的话,同上,级别较低的模型就不需要使用质量较高的图片格式。按图片存储的颜色空间算的话,常使用的是sRGB和普通的RGB,前者不在线性空间,后者在线性空间,这其实往往不会带来太大的性能差异,只不过对于艺术家来说,大部分软件的色彩空间设置为sRGB,这是对人眼友好的色彩空间,只是计算光照的时候要转换到线性空间计算,二者之间的区别主要在是否要相互转换以及伽马矫正上。
- 对于API的一些操作,采样的滤波一般使用两种方式,最邻近和四邻域按权重,我们往往在放大纹理显示时设置为四邻域,缩小纹理显示时使用最邻近。对于纹理映射这里大概找不大出能够优化的地方。对于mipmaps,它本身就是一些API为减少资源消耗的一种方式,往往是在进行缩小操作是开启mipmaps。
这些是对处理数据的相关的优化的个人总结,除此之外,还可以对各个光照模型的实现进行优化,还可以对中间渲染数据存储进行优化,有些pass可以选择使用纹理或者缓冲来存储渲染的数据,按照是否适配以及存储快慢和读取快慢来选择。对于渲染路径,主流的是前向渲染和延迟渲染,可以根据各自的优点结合两种渲染路径使用。前向渲染可以实现几乎所有效果,只是性能会随着规模增长大大增加,延迟渲染很适合处理大规模数据,只是因为本身将数据存储在纹理中的限制,许多效果实现不了,如果要结合使用的话,还是可以让主要的物体使用前向渲染,其它使用延迟渲染(如背景)。
到此为止,总结了一下渲染管线中各个可编程着色器阶段以及应用程序阶段的数据提前处理方面的优化,接下来还有渲染管线中的其它阶段。在几何着色器和像素着色器间,还有光栅化阶段,主要操作是将顶点连为三角形,判断三角形覆盖的像素,并为它们插值计算和顶点一样的相关属性用于像素着色器的光照计算,这一阶段可使用大量的采样和滤波操作来反走样,使用一个最佳的反走样方法可以降低性能消耗,同时得到不错的结果,还可以根据物体的观察距离、本身的大小等来选择部分开启反走样。在像素着色器后,还会进行结合阶段,会进行一些测试和混合操作,提前深度测试可以减少深度测试比较操作的数量,模板测试的优化不大清楚,对于透明度测试,目前也不大清除可以怎么优化,对于透明度混合,使用良好的透明物体的绘制顺序的方法也可以在降低消耗的同时获得不错的效果,可以根据当前场景的情况进行绘制顺序方法的选择,如透明物体不相互交叉,可以直接按照摄像机的距离进行排序,如果相互交叉,可能就需要进行片段级别的顺序判断了。
总结一下就是,根据是否有必要来有选择地进行剔除以及在开销高和低的方法间转换,同时尽量减少数据存储的内存大小。