游戏中的图形渲染优化
- 介绍:
在这篇文章中我们将学习当Unity渲染一帧时,在场景背后到底发生了什么,会出现什么样的问题以及怎么去解决关于渲染的相关问题。
在读这篇文章之前,应该要清楚的认识到:提高渲染性能的方案不是唯一的。因为影响渲染性能的因素有很多,包括游戏自身,硬件和运行平台。我们应该从调查、经验以及严格的分析这些案列。
文章包含了很多普遍的渲染性能问题的解决方案建议以及拓展阅读,很可能我们的游戏就存在这些问题或者其他文章没有提到的问题,但是这篇文章仍然可以帮助我们理解这些问题并且给出了有效的知识点和词汇帮助我们快速找到解决方案。
- 渲染简介:
开始之前,让我们简单快速的了解一下Unity渲染时发生的事情,了解这个渲染流程将会帮助我们理解、研究并且解决性能问题
NB:文章中将用“object”来表示在游戏中被渲染的物体,任何带有renderer组件的物体就会被叫做object
基本上,渲染可被描述为以下几点:
(1)CPU决定什么会被绘制以及如何绘制
(2)CPU发送指令给GPU
(3)GPU根据CPU发送的指令进行绘制
现在,让我们更进一步的看看到底发生了什么,在文章的后面将会详细的介绍这些步骤,但现在先熟悉一下这些词汇以及了解一下CPU和GPU在渲染中扮演的角色。
渲染管线是经常被用来描述渲染的,这是个非常有用的一张脑海图,有效的渲染应该遵循以下几点:
(1)CPU会检查场景中的每一个物体,决定他们是否会被渲染。只有符合条件(certain criteria)的物体才会被渲染;比如,摄像机的视椎体(view frustum https://docs.unity3d.com/Manual/UnderstandingFrustum.html?_ga=2.87818000.925366423.1538040429-1156207016.1532919959)可见范围。物体不会被渲染的情况叫做裁剪(culled),想知道更多关于视椎体和视椎体裁剪知识请移步到该网页(
(2)CPU将会收集所有会被渲染的物体的信息并且对这些数据进行排序然后加进到命令集合中,也就是所谓的draw calls。一次drawcall包含的信息包括:单一网格数据和这个网格将会怎么被渲染;比如,哪一张图片会被使用。在某些情况下,那些共享设置的物体会被包含进相同的drawcall中,归并不同物体的数据到相同的drawcall中的操作叫做合批(batching)
(3)CPU为每一个drawcall创建一个被叫做batch的数据包,合批有时候包含的是数据而不是drawcalls,但这些情况不考虑。
对于每个包含drawcall的合批(batch),CPU必须做以下操作:
(1)CPU会发送一个改变一系列变量(这些变量统称为渲染状态(render state))的指令给GPU。这个指令也叫做 SetPass call,一个SetPass call指令将会告诉GPU哪些设置将会被用来渲染下一个网格(mesh)。SetPass call只有在下一个要被渲染的网格跟上一个网格的渲染状态不同时才会被发送。(A SetPass call is sent only if the next mesh to be rendered requires a change in render state from the previous mesh)
(2)CPU发送drawcall指令给GPU,这个drawcall指引GPU去渲染用了 最近一次SetPass call设置的网格
(3)在某些情况下,合批需要多个pass,一个pass是shader中的一部分,一个新的pass需要一个渲染状态的改变,对于合批的每一个pass,CPU都必须发送一个SetPass call并且重新发送一次drawcall指令。
与此同时,GPU需要做以下操作:
(1)GPU根据CPU按顺序发送的指令去处理相应的操作。
(2)如果当前的任务(task)是SetPass call的话,GPU就会更新渲染状态
(3)如果当前的任务(task)是drawcall的话,GPU就会渲染网格。这个是分阶段发生的,是定义在shader代码中的(separate sections)。这部分比较复杂,暂且不详细讨论,但能让我们了解到vertex shader(顶点着色器:它告诉GPU怎么处理网格的顶点)代码块和fragment shader(片元着色器:它告诉GPU怎么去绘制每一个像素)代码块。
(4)这个处理过程将会一直重复执行,直到GPU处理完所有从CPU发送的指令操作
到此,我们了解了当Unity渲染一帧时到底发送了什么,现在让我们考虑一下渲染中会发生的一些问题
渲染问题的种类:
(1)要了解渲染就必须明白这一点:为了渲染一帧,CPU和GPU都必须完成各自的任务,如果任意一个任务的执行时间太长,当前帧的渲染都会被延迟。
(2)渲染问题有两个基本成因,第一类是:低效的管线,这类问题的产生是因为在渲染管线中有至少一个任务的执行时间过长了,中断了数据流。这类问题也被叫做:bottlenecks(瓶颈)。第二类问题是:塞入太多的数据给管线。即便是再有效的管线,在一阵帧内,也有处理数据量的限制。
(3)如果是因为CPU的任务的执行时间过长,这种问题叫做:CPU bound(限制),如果是因为GPU,那就叫做:GPU bound
理解渲染问题:
(1)使用性能分析工具能让我们在做出决策前了解到渲染性能问题的成因。不同的问题需要不同的解决方案。衡量方案的有效性也是非常重要的;处理渲染性能问题其实是一个权衡问题,也就是说,优化一个可能会给另外一个造成负面影响。
(2)Unity内置性能分析工具:Profiler window , Frame Debugger
The Profiler Window:可以观察到实时的渲染数据。包括:内存使用率,渲染管线和用户脚本性能表现。关于The Profiler Window学习文章可以参考下面两个网址:
https://unity3d.com/cn/learn/tutorials/temas/performance-optimization/profiler-window?playlist=44069
The Frame Debugger:可以观察到每帧的渲染情况,step by step。通过The Frame Debugger,我们可以查看很多详细信息:比如在每一次drawcall中什么被绘制了,涉及到的shader属性和从CPU发送过来的指令顺序。这些信息能够帮助我们理解游戏是怎么被渲染的以及如何优化渲染性能。
关于The Frame Debugger的学习文章:
https://unity3d.com/cn/learn/tutorials/topics/graphics/frame-debugger
查找渲染性能问题的原因:
(1)在我们尝试为游戏提高渲染性能前,我们必须确定由于渲染问题,游戏会运行缓慢。如果是因为过于复杂的代码造成的,很抱歉,在这里没有这个解决方案。如果你不确定当前的性能问题是渲染方面的,那么可以查看这篇文章:
(2)一旦我们确定了性能问题是渲染方面的,我们应该弄清楚是不是CPU bound或者是GPU bound。不同的问题需要不同的解决方案,所以在修复问题之前应该先搞清楚问题的成因,如果你还不能确定是否是因为GPU bound或CPU bound,你可以查看这篇文章:
如果确定了性能问题是渲染方面的,并且也弄清楚了是否是GPU bound或CPU bound,那继续下面的阅读:
如果是CPU bound:
一般来说,CPU为渲染一帧所做的事情分为以下三种:
(1)决定什么会被绘制
(2)指令准备
(3)发送指令
这些广义范畴包含了很多独立的任务,并且这些任务会在多线程中完成。线程允许分离的任务同时执行,当一个线程在执行一个任务时,另外一个线程也能够同时执行另外一个任务。这意味着可以更快的完成操作。当渲染任务被分到不同的线程中时,也叫做多线程渲染(multithreaded rendering)
在Unity的渲染流程中,有三种不同的线程类型:主线程,渲染线程,工作线程(或者叫辅助线程)。主线程是负责游戏的主要任务的,也包括一些渲染任务。渲染线程是专门发送指令给GPU的。工作线程就是单独负责各自任务的:比如,裁剪(culling)或网格蒙皮。线程会执行哪个任务取决于游戏中的设置和游戏运行的硬件设备。举个例子:目标设备有更多核,那么就会生成更多的工作线程。基于此,目标硬件设备的性能分析也是很重要的,不同的设备会有不同的表现。
因为多线程渲染是复杂的并且依赖于硬件设备,所以在进行性能优化之前必须要弄清楚哪些任务导致了CPU bound,如果游戏是因为线程中的裁剪操作(culling operations)的执行时间过长导致运行缓慢的,那么,在其他线程中发送指令给GPU也不会减少这个执行时间。
NB:并不是所有的平台都支持多线程渲染,至少目前WebGL是不支持的。在那些不支持多线程渲染的平台上,所有的CPU任务都会在相同的线程中完成。如果在这样一个平台上,优化CPU的操作就可以提高CPU的性能表现。如果游戏中出现这样的情况,那么我们应该阅读接下来的所有部分并且考虑哪一个优化方法是最适合我们的游戏的。
Graphics Jobs(Experimental)
在Unity中的Player Setting中的Graphics Jobs选项是决定Unity是否使用工作线程完成渲染任务的(不勾选的话就会在主线程完成,在某些情况下,是在渲染线程中完成)。当这个选项被勾选时,能够极大地提高性能。如果要用到这个特征属性,那么我们就应该去分析勾选和不勾选状态下的游戏性能。
--找到导致渲染性能问题的元凶
--使用Profiler Window能够帮助我们找到是哪个任务导致了CPU bound,
发送指令给GPU
(1)发送指令给GPU所耗费的时间是造成CPU bound的最常见的原因。在大多数平台上,这个任务的执行会在渲染线程中,在某些平台上(ps4)会在工作线程中执行。
(2)最耗时的发送指令操作是SetPass call,如果我们游戏是因为发送指令给GPU造成CPU bound的话,那么减少SetPass call的数量是提高性能的一剂良方。
(3)我们可以通过Unity的Profiler Window性能分析工具观察到有多少SetPass call和batches。有多少SetPass call会被发送很大程度上取决于目标硬件,相比移动设备,高端的PC机上会发送更多的SetPass call
(4)SetPass call和与之相关的batches的数量取决于几个因素,接下来的文章中也会深入的探讨这个话题,然而,通常情况下是这样的:
- 减少batches的数量并且/或者让更多的物体使用相同的渲染状态,在多数情况下,是能减少SetPass call数量的
- 减少SetPass call的数量,在多数情况下,会提高CPU性能
如果减少了batches却没有减少SetPass call,也会使性能提高的。这是因为相比多个batches,CPU能够更加有效的处理单个batch,即使他们包含了相同大小的网格数据。
通常来讲,有三种方法可以减少batches和SetPass call。我们将更加深入的探讨这几点:
(1)减少需要被渲染的物体的数量可以同时减少batches和SetPass call
(2)减少需要被渲染物体的渲染次数能够减少SetPass call数量
(3)合并需要被渲染物体的的数据将减少batches
不同的技术会适应不同的游戏,所以我们应该考虑所有的可能,以此决定使用哪一些技术。
减少需要被渲染的物体的数量:
减少需要被渲染的物体的数量是减少batches和SetPass call的最简单的方式,下面给出几个可以减少需要被渲染的物体的数量的技术
- 简单地减少场景中可见物体的数量是一个非常有效的方法。比如,如果我们渲染大量的角色模型,那么可以试验一下少量角色模型的情况。如果场景表现有所提升,这将是更便捷的方案,而不是复杂的技术了。
- 通过摄像机组件的Far Clip Plane属性降低摄像机的绘制距离。摄像机的可视距离范围。如果希望远处的物体看不见,可以尝试使用这个:
fog to hide the lack of distant objects:
- 基于距离,为了更多细粒度的隐藏物体的方式,可以使用摄像机的Layer Cull Distances属性,为不同层级的物体提供自定义的裁剪距离,如果有很多前景细节装饰,那么这个方式是很有效的;我们能够在更加短的距离上隐藏这些细节。
- occlusion culling(遮挡裁剪,遮挡剔除)是使那些被其他物体遮挡的物体不被渲染出来的技术。比如场景中有一个比较大的物体,这个物体后面有其他的物体,那么就可以使用occlusion culling(遮挡剔除)。Unity的遮挡剔除技术(occlusion culling)并不是适合所有的场景,它会造成一定的CPU开销并且设置起来也比较复杂,但在某些场景中它是可以提高性能的。这有一篇学习occlusion culling的博客:
除了使用Unity的occlusion culling,我们也可以使用自己的occlusion culling:手动地将一些玩家看不到的物体隐藏。就比如一些用来显示剧情动画的物体,在播放之前或之后是看不见的,就可以隐藏掉这些物体。这也告诉我们,有时候不一定要在Unity官方找答案,根据具体的游戏找到适合的解决方式。
减少每个需要被渲染的物体的渲染次数:
(1)实时光照技术、阴影和反射等技术提高了游戏的真实性,但也是非常消耗性能的。使用这些技术将会使物体被渲染多次,这很影响性能。
(2)使用这些技术产生的具体影响取决于光照路径(rendering path),渲染路径是绘制场景时,计算的执行顺序,渲染路径之间的主要不同点它们怎么去处理实时光照、阴影和反射。一般情况下,如果游戏运行在高端的硬件设备上并且使用了大量的实时光照、阴影和反射,那么Deferred Rendering是个绝佳的选择。而Forward Rendering是在相反情况下的更合适的选择。然而,这是非常复杂的任务并且如果我们希望使用实时光照、阴影和反射这些技术,那么,研究相关课题和案例是非常有必要的。这个网址:
https://docs.unity3d.com/Manual/RenderingPaths.html?_ga=2.159199410.925366423.1538040429-1156207016.1532919959 可以学习到这些技术知识,这里提供了很多关于Unity可供选择的渲染路径之间的不同点的相关知识。而这篇文章:
https://unity3d.com/cn/learn/tutorials/topics/graphics/introduction-lighting-and-rendering 提供了非常有用的光照技术知识
(3)不管选择哪种渲染路径,使用实时光照、阴影和反射这些技术都是会对游戏造成一定影响的,所以,我们应该了解如何去优化它们
- Unity中的动态光照是非常复杂的课题,所以深入探讨这个话题已经超出了本篇文章的范畴。这里提供两篇学习文章(https://unity3d.com/cn/learn/tutorials/topics/graphics/introduction-lighting-and-rendering(动态光照介绍),https://docs.unity3d.com/Manual/LightPerformance.html?_ga=2.162255028.925366423.1538040429-1156207016.1532919959 (详解普通光照优化))
- 动态光照是非常消耗性能的,当游戏场景存在一些不动的物体:比如,风景,我们可以使用一个叫做baking(烘焙)的技术去预先计算光照,这样在实时光照时就不会再计算了,学习这个技术请移步:https://unity3d.com/cn/learn/tutorials/topics/graphics/lighting-overview?playlist=17102 而这篇文章:https://docs.unity3d.com/Manual/GIIntro.html?_ga=2.69613336.925366423.1538040429-1156207016.1532919959 详细介绍了baked lighting (烘焙光源)
- 如果想要使用实时阴影,这是能提高性能的好去处 。这篇文章 https://docs.unity3d.com/Manual/DirLightShadows.html?_ga=2.167572022.925366423.1538040429-1156207016.1532919959 指引我们在Quality Settings如何调整shadow属性并且告诉我们这些是如何影响性能的。比如,使用Shadow Distance属性来确定只有近处物体会计算阴影。
- Reflection propes(反射探头)能够生成真实反射但这也是非常消耗性能的(增加batches)。应该尽量少的使用的反射,并且使用时尽可能的去优化。这篇文章https://docs.unity3d.com/Manual/RefProbePerformance.html?_ga=2.131305607.925366423.1538040429-1156207016.1532919959 (反射探头优化)
合并物体数据,减少batches
(1)在条件确定好时,一个batch会包含很多物体数据。为了能够符合batching的条件,物体应该是:
- 相同的材质的相同的实例共享
- 材质设置相同(比如texture,shader,shader系数)
(2)批处理符合条件的物体可以提高性能, 虽然我们分析这些确定batching开销的优化技术并不能很好的提高性能。(这句可能翻译的不是很准确,原文是这样的:Batching eligible objects can improve performance,although as with all optimization techniques we must profile carefully to ensure that the cost of batching does not exceed the performance gains.)
以下有几点关于批处理符合条件的物体的不同技术
- Static batching(静态批处理技术)能使Unity批处理近处且不动的符合条件的物体。比如,一堆相同的物体(比如岩石)就可以使用静态批处理技术进行优化。这篇文章:https://docs.unity3d.com/Manual/DrawCallBatching.html?_ga=2.102928520.925366423.1538040429-1156207016.1532919959 介绍了静态批处理设置的用法说明。静态批处理会造成较大的内存使用,所以在分析游戏性能时不能忘了分析这个
- 动态批处理是另一个能批处理那些符合条件的物体的技术,不论这些物体是否会动。使用这个技术会有一些限制条件。这些限制会被列举出来,但与用法说明分开。这篇文章https://docs.unity3d.com/Manual/DrawCallBatching.html?_ga=2.94609172.925366423.1538040429-1156207016.1532919959。 动态批处理会影响到CPU使用量,并且它能节省的时间比开销的时间更多。所以谨慎使用
- 批处理Unity UI元素会有些小困难,因为会被UI布局所影响。文章
(3)GPU instancing()技术能够很有效的批处理相同的物体,但有一些限制并且不是所有的硬件都支持。但如果游戏中存在大量的相同的物体,使用这个技术会是个绝佳选择。这篇文章:https://docs.unity3d.com/Manual/GPUInstancing.html?_ga=2.158048050.925366423.1538040429-1156207016.1532919959 (详解GPU instancing)
(4)Texture atlasing(图集打包)技术可以合并大量纹理,在2D游戏和UI系统比较常见,但也可以用在3D游戏上。游戏中制作美术资源时就可以用到这个技术,这样我们就能确定物体共享纹理并且物体也是符合批处理条件的。Unity内置图集打包工具(Sprite Packer)
(5)手动合并共享相同材质和纹理的网格也是有可以的,要么是在Unity Editor模式下要么是在运行时通过代码完成。当使用这种方式合并网格时,我们应该要知道阴影,光照和裁剪仍然会逐对象级运行;这意味着通过合并网格实现的性能提升会被那些当不被渲染时就不再裁剪的物体抵消掉(this means that a performance increase from combining meshes could be counteracted by no longer being able to cull those objects when they would otherwise not have been rendered ),如果我们想研究这个方法,那就去测试Mesh.CombineMeshes这个方法
(5)在代码中使用Renderer.material需要注意。它会复制材质并且返回一个复制对象的引用。当这个renderer是合批中的一部分时,这样做的话就会打破合批,因为这个renderer(渲染器)不再持有当前材质实例的引用了。如果想要在代码中使用合批好的物体的材质,那就用Renderer.sharedMaterial.
裁剪,分类,批处理
裁剪,收集将会被绘制的物体的数据,整合这些数据到批处理中并且生成GPU指令都将会促成CPU bound。这些任务要么在主线程要么在工作线程中执行,取决于游戏设置和目标硬件。
- 就裁剪本身而言是不太消耗性能的,但是删减掉不必要的裁剪也是会有助于性能的提升的。对于场景中所有激活的物体,包括那些不被渲染的层上的物体,都会有逐对象逐相机的日常开销(per-object-per-camera overhead).我们可以通过disable相机并且deactivate或disable那些当前没有使用的渲染器来减少开销。
- 批处理能够很大程度上提升发送指令的速度(CPU发送给GPU的指令的速度),但有时候会在别的地方增加一些多余的开销。如果批处理操作促成了游戏的CPU bound,那么我们就应该限制手动的或自动的批处理操作了。
蒙皮网格
(1)当我们通过网格变形生成骨骼动画时就会使用到蒙皮网格渲染器,它通常会在动画角色中使用。渲染蒙皮网格的任务通常是在主线程或独立的工作线程中执行,取决于游戏设置和目标硬件设备。
(2)渲染蒙皮网格是一个很消耗性能的操作。如果因为渲染蒙皮网格促成了游戏的CPU bound,我们可以尝试以下几个方法提升性能:
- 我们应该考虑是否每一个物体(当前已经用了SkinnedMeshRenderer组件的)都需要使用SkinnedMeshRenderer组件,有个情况就是,我们导入了一个使用了SkinnedMeshRenderer组件模型,但我们没有给它动画,像这样的话,把SkinnedMeshRenderer组件替换成MeshRenderer组件是可以提升性能的。当我们导入一个模型时,如果在“the model‘s import Settings”中选择了不导入动画的话,这个模型就会使用MeshRenderer组件,而不是SkinnedMeshRenderer组件。
- 如果只是在某些时候需要使用动画(比如,在游戏启动时或仅仅在离摄像机一定距离时),我们可以换成网格细节更少的版本或者使用MeshRenderer。SkinnedMeshRenderer组件有一个BakeMesh功能,这个功能能生成匹配姿势的网格, 能使不同网格或不同渲染器之间进行交换但却不会造成任何视觉上的物体改变
- https://docs.unity3d.com/Manual/ModelingOptimizedCharacters.html?_ga=2.87438096.925366423.1538040429-1156207016.1532919959 这篇文章给出了一些优化那些使用了蒙皮网格的动画角色的建议。
- https://docs.unity3d.com/Manual/class-SkinnedMeshRenderer.html?_ga=2.160772661.925366423.1538040429-1156207016.1532919959 这篇文章介绍SkinnedMeshRenderer,包括了怎么去调整组件属性等来提升性能。另外还有一些使用建议,需要记住,蒙皮网格的开销是逐顶点增加的;减少模型顶点才能减少工作量。
- 在某些平台上,蒙皮会被GPU处理,而不是CPU,如果GPU有足够的能力,这样的选择是具有实验研究价值的。我们可以在Player Setting中为当前平台和目标设备启用GPU skinning。
主线程中跟渲染无关的一些操作:
(1)许多跟渲染无关的操作会在主线程中执行,这意味着如果在主线程中遇到CPU bound了,我们是可以通过减少CPU花费在这些与渲染无关的任务上的时间来提升性能。
(2)举个栗子,在游戏中,某些时候主线程可能会执行一些很消耗性能的渲染操作和代码操作,促成了CPU bound。如果在没有失真的前提下我们能尽可能优化这些渲染操作,那么就有可能在代码侧减少CPU的开销以提升性能。
如果我们的游戏是GPU bound的情况:
如果是GPU bound,那么我们首先要做的就是找出是什么造成了GPU的瓶颈,GPU性能通常会因为fill rate(填充率)而受限,尤其是在移动设备上,但跟内存带宽和顶点处理也有关系。下面就测试一下这些问题,了解是什么原因造成的,如何诊断以及如何修补。
Fill rate
所谓fill rate就是GPU每秒能够渲染的像素数量。如果游戏因为fill rate受限,这意味着游戏每帧想尝试绘制的像素超过了GPU能处理的上限。
检查是否是fill rate导致GPU bound是很简单的:
- 进行性能分析并纪录好GPU时间
- 在Player Setting中降低显示分辨率
- 再次分析游戏性能,如果性能有所提升,那么就可以认为是fill rate造成GPU bound 的
因为fill rate造成GPU bound的解决方法
- 片元着色器是shader定义的一段代码,它告诉GPU应该如何绘制每一个像素,GPU会为每一个需要被绘制的像素执行这部分代码,所以如果这段代码是低效的就会很容易增加性能开销。复杂的片元着色器是造成fill rate问题的非常常见的原因
-- 如果游戏中用了Unity内置的着色器,尽量去使用那些最简单的最优化的着色器来实现我们想要的视觉效果。比如,
https://docs.unity3d.com/Manual/shader-Performance.html?_ga=2.102053897.925366423.1538040429-1156207016.1532919959 the mobile shaders that ship with Unity 是高度优化的;尝试去使用这个着色器看看是否不影响视觉效果并且提升了性能。这些着色器专门为移动平台设计的,但其实也适用其他任何项目。在保证项目视觉效果的基础山,在非移动平台上使用“mobile”着色器是可以提升性能的。
--如果游戏中的物体使用了Unity的标准着色器,应该要知道Unity是基于当前material设置去编译当前着色器的。只有那些当前使用的特征才会被编译。这就意味着,去掉那些,比如细节贴图,能够使得片元着色器代码简单些,进而提升性能。还是一样,去调整设置看看能不能提升性能。
--如果项目中使用了定制的着色器,要尽量去优化。优化着色器是非常复杂的,这有两篇优化shader代码的文章:
https://docs.unity3d.com/Manual/SL-ShaderPerformance.html?_ga=2.154442288.925366423.1538040429-1156207016.1532919959 https://docs.unity3d.com/Manual/MobileOptimisation.html?_ga=2.154442288.925366423.1538040429-1156207016.1532919959
- 过渡绘制是指相同的像素被绘制了多次。这种情况是在一些物体在另一些物体上面绘制时发生的,并且这个很容易造成fill rate问题。为了搞明白过渡绘制,就要弄清楚Unity绘制场景物体的顺序。物体的shader决定了它的绘制顺序,通常是通过指定的渲染队列。Unity利用这些信息严谨绘制物体,文章:https://docs.unity3d.com/Manual/SL-SubShaderTags.html?_ga=2.160756277.925366423.1538040429-1156207016.1532919959 另外,在绘制前,不同渲染队的物体会使用不同的排序方式。比如,Geometry队列下,Unity从前往后对物体进行排序,以减少过渡绘制。Transparent队列下,是从后往前来实现想要的视觉效果的。Transparent队列下的从后往前的排序是会增加过渡绘制的。过渡绘制是非常复杂的研究课题,没有唯一的方法解决这个问题,但减少重叠物体的数量是关键点,因为Unity不能自动对这些物体排序。在Unity Scene窗口可以很好的研究这个问题,Draw Mode(绘制模式)能让我们看到场景中的过渡绘制,进而确定怎么去减少(过渡绘制),造成过渡绘制最常见的罪魁祸首是透明材质(transparent material) 、没有优化过的粒子,重叠的UI元素,所以优化或减少这些是可行的。https://unity3d.com/cn/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input?playlist=30089 (关于UI和overdraw指导)
- 图片效果也是很容易造成fill rate问题的,尤其是当我们使用多种图片效果时。如果游戏使用了图片效果并且造成了fill rate问题的话,我们应该去调整设置或使用更加优化的图片效果(比如高光(优化过的)),如果在相同的相机中使用多种图片效果,这将造成多次shader pass。在这种情况下,合并这些图片效果的shader代码成一个独立的pass块是有益的。比如Unity的延迟处理堆栈:https://github.com/Unity-Technologies/PostProcessing/wiki 如果经过优化后的图片效果还是会有fill rate问题,那么就要考虑不用图片效果了,尤其是在低端的设备上。
内存带宽
(1)内存带宽是指GPU从显存中存取的速率。如果我们游戏受到内存带宽的限制,这意味着我们使用了太大的图片,超过了GPU的快速处理能力
(2)检查一下是否是内存带宽的问题:
- 对游戏进行性能分析并且纪录GPU消耗时间
- 在Quality Setting中设置好目标平台的目标质量,降低纹理质量
- 重复第一个步骤,如果性能提升,那么就能确定是内存带宽的问题
如果内存带宽造成性能问题,那么就要降低纹理的内存使用率了,下面有几个方法能够优化纹理
- 纹理压缩技术:能够纹理的磁盘存储大小和内存大小。如果内存带宽在我们游戏中会造成性能问题,使用纹理压缩技术是可以提升性能的。在Unity中有多种纹理压缩技术,每一种都有单独的设置界面。通常来说,某些纹理压缩是无论何时都是会被用到的,然而,一个试验和错误能够让我们找到每一个纹理性能最好的最佳设置。请看这篇文章:https://docs.unity3d.com/Manual/class-TextureImporter.html?_ga=2.191165986.925366423.1538040429-1156207016.1532919959
- 纹理映射(MipMaps)是优化方案中效果没那么好的技术,在Unity中可以用在远处物体上。如果场景中有跟摄像机距离比较远的物体,使用mipmaps能够缓解内存带宽带来的问题。The MipMaps Draw Mode(https://docs.unity3d.com/Manual/ViewModes.html?_ga=2.103665800.925366423.1538040429-1156207016.1532919959)(Scene Mode下),能够看到使用MipMaps后的好处,还有这篇文章:https://docs.unity3d.com/Manual/class-TextureImporter.html?_ga=2.199507494.925366423.1538040429-1156207016.1532919959
顶点处理
(1)顶点处理是指GPU必须渲染网格顶点的过程。顶点处理的性能开销受到以下两个方面的影响:会被渲染的顶点的数量,在每一个顶点上执行的操作的次数
(2)如果游戏是GPU bound的话并且我们确定不是fill rate或内存带宽的问题,那么很可能就是顶点处理的问题。在这种情况下,尝试减少GPU要执行的顶点处理的次数是可以提升性能的。
(3)下面介绍几个可以减少渲染的顶点的数量或在每一个顶点上执行的操作的次数的方法
- 首先,要减少那些不必要的复杂网格。如果使用了那些在游戏中看不见的网格或因为生成时错误的有大量顶点的无效网格,这会让GPU做很多无用功。降低顶点处理的开销的最简单的方法就是在我们的3D项目中生成更少顶点的网格
- normal mapping(法线映射)技术是指纹理用在了可以生成庞大的复杂几何结构幻觉的网格上的技术。一些GPU的性能开销使用这个技术是可以提升性能的。https://docs.unity3d.com/Manual/StandardShaderMaterialParameterNormalMap.html?_ga=2.123024131.925366423.1538040429-1156207016.1532919959, 这篇文章指导我们使用normal mapping(纹理映射)来在网格上模仿复杂的几何结构。
- 如果网格中没有使用纹理映射技术,一般会在网格的import settings中将vertex tangents(顶点切线)禁用掉。这种数据上的削减会设置到每一个顶点上
- LOD(Level Of Detail 细节层次渲染)是一种可以降低远离摄像机的网格的复杂度的优化技术。降低需要渲染的顶点数量且保证不失真。https://docs.unity3d.com/Manual/class-LODGroup.html?_ga=2.200163238.925366423.1538040429-1156207016.1532919959
- 顶点着色器(shader代码块,告诉GPU如何绘制每一个顶点),如果游戏因为顶点处理受限,那么降低顶点着色器的复杂度是可以提升性能的
结论:
我们已经学习了在Unity中是如何渲染的,产生的问题的分类还有如何去提升渲染性能。利用这些知识加上性能分析工具,我们可以解决渲染性能问题并且使游戏在一个高效流畅的渲染管线中构建。
扩展阅读链接
原文地址:https://www.cnblogs.com/DevMi/p/9742551.html