永远相信美好的事情即将发生 😊!

Unity Shader 入门精要(冯乐乐著)学习笔记(15)——Unity中的渲染优化技术

学习 Mavis 151℃ 0评论

移动平台的特点

为了尽可能移除那些隐藏的表面,减少overdraw(即一个像素被绘制多次),powerVR芯片(通常用于ios设备和某些Android设备)使用了基于瓦片的延迟渲染(Tiled-based Deferred Rendering ,TBDR)架构,把所有的渲染图象装入一个个瓦片(tile)中,再由硬件找到可见的片元,而只有可见的片元才会执行片元着色器,另一些基于瓦片的GPU架构,如Adreno和Mali芯片则会使用Early-Z或相似的技术进行一个低精度的深度检测,还有一些GPU,类似Tegra芯片,则使用了传统的架构设计,因此在这些设备上,overdraw可能造成性能瓶颈。

一些游戏往往需要针对不同的芯片发布不同的版本,一边对每个芯片进行更有针对性的优化。

影响性能的因素

两种计算资源:CPU\GPU,让游戏可以在预期的帧率和分辨率下工作,CPU主要负责保证帧率,GPU主要负责分辨率相关的一些处理。

造成游戏性能瓶颈的主要原因:

  1. CPU:过多的draw call、复杂的脚本或者物理模拟。(每一帧中drawcall的数目限制以及其他很多东西)
  2. GPU:顶点处理——过多的顶点+过多的逐顶点计算、片元处理——过多的片元(可能是分辨率造成也可能是overdraw)+过多的逐片元计算。
  3. 带宽:使用了尺寸很大且未压缩的纹理、分辨率过高的帧缓存。

对应的优化技术:

  1. CPU:使用批处理技术减少drawcall数目
  2. GPU:减少需要处理的顶点数目(优化几何体,使用模型LOD(Level of Detail)技术,使用遮挡剔除(Occlusion Culling)技术)、减少需要处理的片元数目(控制绘制顺序、警惕透明物体、减少实时光照)、减少计算复杂度(使用shader的LOD技术,代码方面的优化)。
  3. 带宽:减少纹理大小、利用分辨率缩放

Unity中的渲染分析工具

优化前需要知道哪个步骤造成了性能瓶颈:渲染统计窗口、性能分析器、帧调试器。

渲染统计窗口:

性能分析器:

帧调试器:

减少draw call数目

使用同一种材质的物体可以一起处理。

动态批处理:unity自动完成,物体可以移动,但存在很多限制。

限制:能够进行动态批处理的网格的顶点属性规模要小于一定的阈值。且一般来说所有对象都需要使用同一个缩放尺度(目前对缩放的限制已经不存在了)。使用光照纹理的物体需要小心处理,需要保证这些物体指向光照纹理中的同一个位置。多pass的shader会中断批处理。

当场景中只有一个平行光(关掉阴影),渲染数据如下图:

可以看出来使用了批处理,但是如果加入点光源(并且照到场景中的所有物体),则由于场景中的物体都使用了多个pass的shader,需要应用到多个光照的情况下,破坏了动态批处理的机制,,平行光和点光源需要对四个物体分别产生影响,所以需要多很多个批处理:

静态批处理:自由度很高,但会占用更多内存,且不可再移动。实现原理是只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中。由于静态批处理前一些物体共享了相同的网格,那么在内存中每个物体都会对应一个该网格的复制品,即一个网格会变成多个网格再发送给GPU,多使用更多的内存。这样可能造成性能瓶颈。因此需要选择合适的批处理方法或者自己写批处理方法。

上图三个teapot都是使用相同材质,但是与前面的相比,并没有使用动态批处理,因为模型顶点数为393,而使用的shader中使用了4个顶点属性,超过了阈值,这种情况下要再想减少drawcall就需要使用静态批处理。

开启静态批处理(也可只勾选Batching Static):

运行后再查看渲染信息(非运行状态下不启动静态批处理):

运行时可以查看到物体的网格变成了下图的效果:

查看该网格发现四个物体都在,unity会对于合并后的网格,判断其中使用一个材质的进行批处理:

加入其他光源后,在未运行的情况下,除了平行光以外的其他光源存在并在shader中额外定义了pass来处理,这些额外的pass不会被批处理:

但是在运行后,处理平行光部分的basepass仍然会被静态批处理:

共享材质:拥有同一种实体的材质(参数纹理相同)才可以进行批处理,纹理不同可以合并纹理,参数不同可以利用VBO中的数据,如果在脚本中需要访问共享材质,需要使用Renderer.sharedMaterial来保证。如果使用Renderer.Material来修改材质,unity会创建该材质的复制品,就破坏了批处理在该物体上的应用。

注意事项:尽可能选择静态批处理,但小心内存和不可移动。使用动态批处理尽可能包含少量的顶点属性和顶点数目。对于游戏中的小道具,例如可以捡拾的金币可以使用动态批处理。对于包含动画的物体,无法全部使用静态批处理,但其中如果有不动的部分,可以标识为static。

由于批处理需要把模型变换到世界空间下再合并他们,因此,如果shader中存在一些基于模型空间下的坐标的运算,那么往往会得到错误的结果:解决办法是在shader中使用disablebatching强制使用该shader的材质不会被批处理,对于半透明材质需要严格的绘制顺序。

减少需要处理的顶点数目

优化几何体:unity中显示数目更多的原因是顶点的每一个属性和顶点必须是一对一的,所以需要拆分顶点。或者一个顶点可能对应多个法线信息或切线信息,因此尽可能减少顶点的数目,移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。

模型的LOD(level of detail)技术:当一个物体离摄像机很远时,可以减少模型的面片数量从而提高性能。在unity中可以使用LOD Group组件来为物体构建一个LOD ,需要为同一个对象准备多个包含不同细节程度的模型然后赋给组件中的不同等级,unity就会自动判断当前位置上需要使用哪个等级的模型。

遮挡剔除技术(Occlusion culling):使用一个虚拟的摄像机来遍历场景,从而构建一个潜在可见的对象集合的层级结构,在运行的时候 ,每个相机都会使用这个数据来判断可见性。

模型的lod技术和遮挡剔除技术同时减少cpu和gpu的负荷。

减少需要处理的片元数目

重点在于减少overdraw

查看物体之间的遮挡情况:

控制绘制顺序:对于不透明物体从前往后绘制,对于透明物体从后往前。例如第一人称射击游戏中,主人公可以先绘制(较小的渲染队列),敌人一般在掩体后后绘制(较大的渲染队列),天空盒子永远出现在物体的后面,队列设置为Geometry+1,这些排序的思想往往会节省掉很多渲染时间。

警惕透明物体:半透明没有开启深度写入,所以必须从后往前渲染,几乎一定会造成overdraw,对于GUI对象(通常被设置为透明)来说,若屏幕中占据比例太多,应尽可能减少,或者把GUI的绘制和三维场景的绘制交给不同的摄像机,其中负责三维场景的摄像机的视角范围尽量不要和GUI的重叠。移动平台上透明度测试使用discard也会影响GPU的优化策略,导致可以减少的overdraw的优化都无效了,这个时候使用透明度混合的性能往往比透明度测试更好。

减少实时光照和阴影:避免过多的点光源,可以使用烘焙技术把光照提前烘焙到一张光照纹理中(lightmap),然后运行的时候根据纹理采样即可。另一个模拟光源的方法是使用God Ray,小型光源靠这种方法模拟,多是通过透明纹理模拟得到的,在移动平台上,一个物体使用的逐像素光源数目应该小于1(不包括平行光,如果一定要使用更多的实时光,可以选择逐顶点光源来代替)。实时阴影消耗也很大,可以把静态物体的阴影烘焙到光照纹理中,而只对场景中的动态物体使用适当的shi’shi

节省带宽

减少纹理大小(纹理图集、多级渐远纹理技术、纹理压缩)+利用分辨率缩放。

减少计算复杂度

shader的lod技术,可以控制使用的shader等级,只有shader的lod值小于某个设定的值,这个shader才会被使用,而使用那些超过设定值的shader的物体将不会被渲染。

代码方面的优化:对象数<顶点数<像素数,因此尽可能把计算放到对象或逐顶点上。尽可能使用低精度的浮点值进行运算,尽可能不要使用全屏的屏幕后处理技术,尽可能不要使用分支和循环语句,,尽量不要使用复杂的数学运算,尽量不要使用discard。


Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Unity Shader 入门精要(冯乐乐著)学习笔记(15)——Unity中的渲染优化技术
喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(2)个小伙伴在吐槽
  1. Great article, just what I needed.
    buy CBD2021-02-16 14:37 回复
  2. Superb, what a blog it is! This webpage gives valuable data to us, keep it up.