Graphics优化策略-官篇2

原文:http://www.cnblogs.com/zblade

英文:英文地址

  

如果游戏渲染问题来自CPU

  概括的来说,CPU在一帧的渲染中的工作可以分为三个部分:

  1)决定谁需要被渲染

  2)为GPU准备渲染指令

  3) 发送渲染指令给GPU

  在每个部分中又有许多单独的任务,这些任务主要通过多个线程来执行。多线程确保渲染任务的并发执行,单个线程执行单个渲染任务,从而大大提高渲染性能。如果渲染任务被分配到多个线程进行,这就是多线程渲染。

  在渲染中主要有三种线程:主线程,渲染线程和工作线程。主线程主要负责CPU中的大部分工作,也包括渲染任务。渲染线程特定用来给GPU发送渲染指令。对于工作线程来说,每个工作线程执行单独的任务,例如剔除或者网格的蒙皮。每个任务由哪个线程执行取决于游戏设置和运行时的硬件条件。例如,如果游戏运行硬件中CPU处理能力越强,就会有越多的工作线程产生。基于这个原因,我们的游戏在不同的硬件上会有不同的表现,我们可以基于目标硬件进行特定的游戏运行问题分析。

  由于多线程渲染非常复杂和硬件条件的原因,在进行渲染性能改进前需要定位CPU中的什么任务造成渲染问题。如果游戏运行较慢是由于某个线程中的剔除操作耗时较长,那么改进指令发送所在的线程对于性能的改进于事无补。

注意:并不是所有的平台都支持多线程渲染。在写这篇文章的时候,WebGL并不支持这个特性。在不支持多线程渲染的平台上,所有的渲染任务都在同一个线程执行。在这样的运行平台上,任何线程的优化操作都会提升渲染性能。如果渲染性能问题来自于CPU,那么我们需要详细阅读下面的部分并选择合适的渲染优化方法改进游戏的渲染性能。

  1、Graphics jobs

  Player Settings中的graphics jobs决定Unity是利用主线程,渲染线程还是工作线程进行渲染任务。在可以设置的平台上,graphics jobs可以带来较为可观的性能改进。

  2、查找造成渲染问题的原因

  可以通过profiler window来定位造成渲染问题的具体任务,让我们分析一下常见的性能问题及其解决办法:

  1)给GPU发送渲染指令

  常见的CPU问题来自于指令发送时间过长,在大多数平台上,该任务处于渲染线程中,某些平台(PS4)处于工作线程中。

  在指令发送中,最耗时的操作是SetPass Call操作。如果游戏限制因素来自于指令发送,则需要减少SetPass的发送数量来优化游戏渲染性能。

  通过unity中的profiler window我们可以查看有多少SetPass被调用和多少批处理被渲染调用,SetPass可以被发送的数量受到目标硬件的限制,高配置的PC相对于移动设备可以发送更多数量的SetPass。SetPass和其相关的批处理的数量主要受到多个因素的影响,通常分为:

  (1)大多数情况下减少批处理的数量或者让更多的object共享同一渲染状态可以减少SetPass的调用次数;

  (2)通常情况下减少SetPass的调用次数可以提高CPU的性能。

   即使减少批处理的数量并没有减少SetPass的调用次数,对于性能也是有一定的提升的。因为相比于多个批处理,CPU对于单个批处理的执行效率更高,即使两者之间包含的数据量一样。概括地说,有三种基本方法可以减少批处理和SetPass的数量:

  (1)减少渲染的objects的数量可以有效地减少批处理和SetPass的数量;

  (2) 减少每个object的渲染次数通常可以减少SetPass的数量;

  (3) 将必须渲染的object的数据进行合并可以减少批处理的数量。

  对于不同的游戏有不同的优化方法,所以我们需要全局考虑来选择最适合我们游戏的方法。

  2)减少渲染的object的数量

  减少渲染的object的数量通常是减少批处理和SetPass数量最直接的方式,常用的方法有:

  (1)减少场景中可见的object的数量是最直接的方式。如果我们在场景中需要渲染大量的角色,通过减少场景中的角色的数量可以测试效果,如果其效果依然很好并且渲染性能提升,那么这就是最直接的提升性能的方法;

  (2) 可以通过设置相机的Far Clip Plane属性来减小相机的渲染距离。深度值超过该属性值的objects就不会被相机渲染。如果我们期望远距离的物体不在可见,我们可以试着使用雾化效果来隐藏远处物体的消失,可以参考此处:Fog to hide the lack of distant objects

  (3) 我们可以使用相机的LayerCullDistance属性来根据物体的深度值来隐藏物体,在物体的前面有许多装饰细节的时候,可以通过更小的距离来隐藏这些细节。

  (4) 我们可以采用Occlusion Culling来剔除那些被隐藏的物体,例如场景中如果有大量的建筑,通过剔除技术可以让被遮挡的物体不被渲染。unity中cull并不是适用任何场景,cull会造成额外的cpu消耗和增加设置的难度,但在某些场景中也可以极大的提高渲染性能。这儿详细的介绍了culling技术。除了cull之外,我们也可以手动设置物体的可见与否。例如,如果我们的场景中包含大量的前置或者后置的不可见物体,则我们可以将其隐藏,根据我们游戏中的具体实际情况来优化相比于依赖于Unity的动态优化更可靠。

  3)减少必须被渲染物体的渲染次数

  实时光照,阴影和反射等措施会给游戏带来更加逼真的效果,但是CPU的消耗也是极大。这些措施都会让一个object被多次渲染,这对于游戏的性能有极大的影响。

  这些光照特性对于游戏的影响取决于游戏的渲染路径设置。光照渲染路径决定场景中的物体的之间的渲染计算关系,不同渲染路径之间的差别主要体现在它们如何处理实时光照,阴影以及反射。通常来说,如果游戏运行在高端设备上,并且采用了较多的实时光照,阴影以及反射,延迟渲染路径是一个较好的选择。如果游戏运行在低端手机上而且不采用较多的光照特性,前向渲染路径是更为合适的选择。如果游戏希望采用实时光照,阴影和反射等特性,最好对于具体的游戏项目进行特定的设置。对于不同渲染路径的设置,这儿有更多详尽的阐述。对于unity中的光照和渲染,这儿有一些入门知识介绍。

  无论选择何种渲染路径,实时光照,阴影和反射对于游戏的运行性能都有较大的影响,所以对于他们的优化非常重要。

  (1) unity中的动态光照渲染十分复杂,深入的讨论不在本文的范围内。但是这儿有一些基本入门知识,这儿有一些对于游戏中的灯光的优化。

  (2)动态的光照渲染对于游戏性能的消耗较大,如果场景中包含较多的静态的物体,我们可以采用baking(烘培)技术提前将光照的计算烘培到场景中,这样就不需要进行实时的光照计算,这儿有对烘培技术的介绍,,这儿有对于烘培中光照的详尽介绍。

  (3) 如果我们希望在游戏中添加阴影,那么可以对其进行较大的性能提升。这儿对于如何在Unity中设置阴影的属性及其影响有较为详细的介绍。比如我们可以设置shadow distance(阴影距离)来确保只有较近的物体才会被纳入光照计算。

  (4) 反射属性可以产生真实的光照反射效果,但是这对于批处理来说耗费较大。最好将反射的使用减小到最低,如果使用的时候尽可能优化。这儿介绍了如何优化游戏中反射的使用。

  4)将objects合并进更少的批处理中

  在条件满足的情况下,一个批处理操作可以包含很多的objects。在以下情况下,可以将不同物体合并进一个批处理操作中:

  (1)共享相同的材质;

  (2) 材质的设置相同(比如:贴图,shader,shader参数等)

  将满足条件的物体进行批处理可以提高游戏的性能,然而与此同时我们必须仔细地分析来确保批处理的消耗不会超过优化带来的提升(不然得不偿失)。

  对于合适的物体的批处理,有一些技术可以采用:

  (1) 静态批处理  在unity中可以将不移动的物体合并在一个静态批处理中,例如可以将场景中不移动的石头都合并进一个批处理中来提升游戏性能。这儿对于如何在游戏中设置静态批处理有一些介绍。

  (2) 动态批处理 在unity中可以应用动态批处理来对游戏中的无论移动与否的物体进行处理,应用动态批处理对于物体有一些限制,这儿对其限制有详细的列表解释。动态批处理对于CPU的处理时间有更大的消耗,在进行动态批处理的测试的时候最好注意这一点而小心使用动态批处理技术。

  (3) UI中的批处理由于受到层级的设置影响而显得十分复杂,UI批处理视频介绍了一些优化方法,这儿介绍了一些方法来确保UI中的批处理优化达到我们期望的效果。

  (4) GPU实例化 unity中可以采用GPU实例化来将物体有效的批处理,尽管有一些限制而且不是所有的硬件都支持,但是如果我们的游戏中同屏物体较多,可以尝试采用此方法。这儿对于在unity中如何使用GPU实例化方法有详尽的解释,而且对各个平台都有较为详尽的阐述。

  (5) 图集 图集主要是将多个贴图打包进一个,在2D游戏和UI中应用较多,3D游戏中也可以采用此方法。在设计制作游戏中的美术时可以采用图集方法来讲那些共享相同贴图的物体合并在一起进行批处理操作。unity中提供了图集的制作工具Sprite Packer

  (6)可以在unity的编辑器模式下或者实时运行的时候将共享相同材质和贴图的网格合并在一起。在进行合并操作的时候,我们需要明确阴影,光照,和剔除等操作任然在每个物体上运行。这会加大游戏性能的消耗,从而中和通过剔除操作带来的性能优化。如果我们研究该方法,我们需要检查Mesh.CombineMeshes方法。

  (7) 在操作脚本中的Renderer.material方法时需要及其小心,这会复制生成当前的贴图从而返回一个指向该新的拷贝的指针。由于当前渲染不再指向同一个材质的实例化对象,如果当前渲染处于批处理中,这会打断批处理操作。如果我们想要获取批处理中objects的材质,最好采用Renderer.sharedMaterial.

  5)剔除,排序和批处理

  将需要渲染的objects的数据进行剔除和批处理排序操作,然后生成对应的GPU指令都会对CPU的性能瓶颈有影响。这些任务要么在主进程上运行,要么在单独的工作进程上运行,这取决于游戏的设置和目标硬件。

  (1) 剔除操作本身并不耗费性能,但是减少不必要的剔除操作可以提升游戏的性能。对于场景中的各个激活的物体都有一个单独的相机,即使那些不被渲染的物体。为了提升性能,可以关闭那些当前不被使用的相机或者将其渲染不使能。

  (2) 批处理操作可以提升发送给GPU的指令的速度,但是有时这会造成不必要的消耗。如果批处理操作造成了CPU的瓶颈,最好减少人工的或者自动的批处理操作数量。

  6)网格皮肤

  SkinnedMeshRenders在网格的骨骼动画中常常被使用,特别是在角色的动画中。网格皮肤的渲染任务常常在主进程或者单独的工作进程中进行,这取决于游戏的设置和目标硬件。

  网格皮肤的渲染极其消耗性能,如果在profiler window中查看到网格皮肤的渲染造成CPU瓶颈,下面有一些方法可以用来提升性能:

  (1) 对于游戏中的每个物体我们需要考虑是否需要采用SkinnedMeshRender组件,也许游戏中的模型并不是需要该组件,这时候可以用MeshRender组件来代替从而提升性能。如果在将模型导入unity的时候如果不选择导入动画,模型就会用MeshRender组件来代替SkinnedMeshRender组件。

  (2) 如果我们的模型只是偶尔会有一些动画(比如开场或者距离较远时),则可以用MeshRender组件来替代SkinnedMeshRender组件。SkinnedMeshRender组件有一个BakeMesh函数用来创造合适的网格,这样可以在不改变视觉效果的情况下交换不同的网格或者渲染。

  (3)对于采用网格皮肤的角色动画,这儿有一些优化方法来提升性。除此之外,每个顶点都会加大网格皮肤的渲染,所以减少模型的顶点数量会极大的提升游戏的性能,模型顶点数量越少越好。

  (4)在一些平台伤,网格皮肤的渲染可以放在GPU伤进行,如果GPU中包含较大的容量,可以测试将其移植到GPU中进行渲染,可以在Player Settings中进行相关的设置。

       7)主进程中与渲染无关的操作

  CPU中的主进程会同时进行多个任务,这意味着减少与渲染无关的任务的时间可能会提高CPU的性能。比如CPU主进程同时进行高消耗的渲染工作和脚本操作,如果已经将渲染操作尽可能的优化了,那么我们可以尝试优化脚本的性能。

posted @ 2018-05-14 20:48  何文西  阅读(...)  评论(...编辑  收藏