|
本章内容参考陆泽西的《Unity3D高级编程》用户界面章节的UI优化部分,这本书以通俗易懂的表达方式讲解晦涩的知识点,感兴趣的可以看看哦~(尊重正版!!!!) 1、UI动静分离
”动“指的是移动、缩放、旋转、换图等频率较高的UI;
”静“指的是静止不动的UI;
UGUI与NGUI一样都是用网格模型来构建UI画面,构建后都执行了合并网格的操作,否则会增加很多的drawcall,进而导致渲染队列阻塞,使得游戏性能下降。但是只要其中有一个UI元素会动,那就需要重新合并网格,导致那些静态的、不需要构建的内容也一并重新构建。因此需要将这些动、静UI分离开来,将会动的元素合并在一起,因为它们重绘的频率比较高,这样节省了CPU的开销。
如何分离呢?UGUI和NGUI系统都有自己的重绘合并节点,称为画板,UGUI中是Canvas,NGUI中是UIPanel。
以画板为节点拆分动与静,将会动的UI元素放入专门的画板中,将静止的UI元素留在原来的画板上,这样在重构动态UI的时候就不会影响到静态UI,减少了CPU重绘和合并的消耗。
2、拆分过重的UI
UI系统会随着项目的增大而不断扩大,一个Prefeb里可能会装着多个界面。比如,在打开一个界面时隐藏另一个界面,这样导致一个UI中元素过多,在实例和初始化时CPU消耗过大,因此需要拆分这些过重的UI界面。
可以将需要隐藏的UI拆分相互来,成为一个单独的界面,在需要展示的时候才调用实例化。如果依旧很大,可以进行二次拆分,将二次显示的内容进一步分离,比如打开一个界面会显示的动画、点击才能显示的动效等,可以考虑将其拆出来设置为一个预制体,需要的时候再加载。
加载和实例化本身无无法避免CPU消耗的,但我们可以将这些消耗分散开来,需要注意权衡加载速度和内存。但如果小个体被频繁的加载和销毁,同样会消耗过多CPU,所以拆分法更适用于冗余比较大的界面上,对于这些难以拆分的小UI,优化办法下面介绍。
3、UI预加载
为什么要预加载?
当UI实例化时,需要将Prefeb实例化到场景中,期间会有网格的合并、组件初始化、渲染初始化、图片加载、界面逻辑初始化等,会消耗大量的CPU,可能导致我们在打开某个界面时频繁出现卡顿现象。上面我们介绍了拆分UI法,此方法适合大冗余、益拆分的界面,对于容量小、难拆分的UI即使再拆分后可能仍会消耗大量CPU,这种情况可以使用UI预加载的方法,在游戏开始前/进入某个场景之前预先加载部分UI,使得实例化和初始化平摊到等待的时间线上。
如何进行UI预加载?
最简单直接的方法就是在游戏开始前加载UI资源但不实例化,这样做只是提前把资源加载到内存中,在显示UI时CPU只需要实例化和初始化。
如果使用了上面的方法后打开界面时CPU还是消耗严重,可以将实例化和初始化也提前到游戏开始前,只是会对UI进行隐藏,需要显示时再显示出来,同样,关闭时也是隐藏不是直接销毁。
目前,大部分项目都是使用AssetBundle来加载资源,也有部分是使用Unity3D本地打包机制(Resources这个API接口)来加载资源。对于这种使用Resources加载的预制体,Unity3D中有Preload可以直接使用,在进入程序时unity会将这些预制体进行预加载。在程序初始化时,已加载了指定的预制体,CPU更多的消耗是在启动页面上。虽然现在不鼓励使用Resourses方式,但部分界面必须使用他们。
不过所有的预加载都会引起一个问题,那就是CPU集中消耗带来的卡顿现象。预加载并没有减少CPU的消耗,加载的图片、实例化的元素都没有变化,CPU消耗的总量是不变的,我们是指把这部分消耗分离或者提前了。如果将全部集中到某个位置,同样会有强烈的卡顿,所以应当尽量分散这些消耗。
4、UI图集Alpha分离
为什么要分离Alpha?
压缩UI图集可以减少App包的大小,是减少内存使用量的有效方法。需要I/O的消耗少了,内存的申请少了,可以减少CPU的消耗。但对图集压缩后会降低图片效果,出现模糊、锯齿、线条等问题,这是因为使用压缩模式ECT或PVRTC时将透明通道一并压缩进去了,导致渲染的扭曲,因此需要将透明通道Alpha分离出来单独压缩。这样既可以缩小内存,又不会使图像太是真。
如何分离?
由于UGUI是内部集成的,alpha的分离在Unity3D中已经完成了,这里就不介绍。因此主要讲一下NGUI中的方案。
在NGUI中,Alpha分离的原理是将原本以ETC方式压缩的一张图改为压缩一张没有Alpha的图和一张有Alpha的图。RGB888的PNG图没有Alpha,所有的Alpha通道都在Alpha8的PNG图里。也可以使用程序分离的方式,把原图中的颜色提取出来 放入一张新的图片中,而Alpha部分提取出来放入另一张图中。
然后,修改NGUI的原始着色器,将原来只绑定一张主图的着色器改为需要绑定一张主图和一张Alpha图的着色器。这里需要修改4个着色器:
Unity - Transparent Colored.shader
Unity - Transparent Colored 1.shader
Unity - Transparent Colored 2.shader
Unity - Transparent Colored 3.shader修改的内容就是加入 _AlphaTex("Alpha(A)",2D)="black"{} 变量,用来绑定Alpha的图。
接着,在frag()函数中,将Alpha与主图Alpha操作的内容替换成Alpha图中的Alpha值。用Alpha图中的Alpha值替代原来主图Alpha部分,而主图仍然承担主要色彩内容。源码如下:
//原图用一张图承担颜色和透明通道的情况
fixed4 col = tex2D(_MainTex, i.texcoord) * i.color;
return col
//用两张图的情况
fixed4 col = tex2D(_MainTex, i.texcoord);
fixed4 result = texcol;
result.a = tex2D(_AlphaTex,i.texcoord).r*i.color.a;
做好上述操作后,选中一个创建好的图集prefeb,会发现Inspector窗口下的预览窗口以及Sprite选择窗口看到的Sprite都没有Alpha通道,因为在Editor下展示模式仍然使用的是原图,即使使用的是两个通道,因此需要修改这些编辑器上的NGUI工具。解决方案是在编辑器模式下动态生成一个rgb32的texture将其替换。需要修改NGUI编辑类下几个文件:
UIAltas.cs
UIAtlasInspector.cs
SpriteSelector.cs
NGUITools.cs
UISpriteInspector.cs修改以上类后,绘图时,启用的是用RGB888和Alpha合成的临时图。
总结:1、将原图生成两张图,一张只带颜色,一张只带Alpha通道。2、将着色器的Alpha来源修改为新的Alpha图。3、对于着色器修改导致的编辑器显示问题,需要在编辑器部分生成临时图来提替换原来的图。
5、UI字体拆分
项目中字体通常会占很大的空间,如果多个不同的 字体一同展示在屏幕上会消耗较大的内存。我们需要更高的性能效率,拆分字体可以让字体的加载速度加快,使得场景加载速度加快。
例如,在登入场景我们只需要数字和字母,可以从字体中提取数字和26个字母成立一个新的字体应用在场景中;在用户取名场景,我们只需要保留3000个常用字体。这样可以省下来不少的内存。
还可以针对不同的语言进行拆分,将不同种语言拆分成独立的字体包,每个语种版本只加载的自己的字体。
6、ScrollView优化
ScrollView中经常会有大量的的元素在窗口中不停滚动,每一次生成和滑动UI元素时,会进行大量的合批(及时不可见)和渲染裁剪,消耗大量的CPU来重构网格。若ScrollView中的滚动元素过多,每一帧进行重构会直接导致画面缓慢、卡顿严重的现象。
而大部分滚动元素是不可见的,造成了CPU的大量浪费。对于这种情况的优化策略是:实时判断是否有UI元素被移出画面,再将这些被移出并看不到的元素进行重复利用,将它们填补到需要显示的位置上去,再对该单位元素的属性重新设置,让重复利用的元素更新为在该位置需要显示的元素的样子。
示例
如上图所示举例,对于一个只能显示4个完整滚动元素的ScrollView来说,无论用户实际需要多少个元素,我们都可以只使用5个元素来实现,当整体元素向上滚动期间,只要最上面一个元素超出了可视范围就初始化并移动到最下面,代替新的一个元素,在用户视角很顺畅了看到了若干元素,但实际上我们只对5个元素进行了重复利用。
我之前有这样一个疑问:每次渲染前相机会进行裁剪,在相机视锥体之外的元素是不会被重构的,下面第8点优化利用的也是这个原理,那么我们知道相机的可视范围是有限的,即使ScrollView中有大量的元素也会被相机裁剪,最终需要渲重构的也只有一部分而已,为什么还会造成大量的CPU浪费呢?其实这是两套逻辑,ScrollView中有个mask遮罩来控制超出部分的不可见,此时裁剪由mask来控制而不是相机,所以重构时依旧会重构所有元素而不仅是相机中的可视范围。
7、网格重构优化
UGUI系统的合并机制是:将拥有相同材质的球的网格合并在一起,这样才能达到最佳效果。一个材质球对应一个图集,只有相同图集内的图片才需要合并在一起。所以UGUI系统中的元素在改变颜色或者Alpha后会导致网格重构。
在UGUI系统中,当元素需要改变颜色时,是通过改变当前元素的顶点颜色来实现的,然后将其重新合并到整块网格里去。并不能直接从原来合并好的网格上找到目标元素的顶点位置,所以需要一次性合并重构网络。改变Alpha也同样的道理,Alpha本身就是Color里的一个浮点数,附在顶点上并成为顶点的一个属性,所以改变Alpha也就是改变顶点的Color属性。
在UI动画中,每一帧都会改变UGUI的颜色和Alpha,所以每一帧都会对网格进行一次重构。这样消耗了大量的CPU运算,上述方法无法解决这个问题,那么该如何优化呢?
我们不希望在UI颜色改变时网格重构,对此可以单独建一个材质球,提前告诉UGUI:我们使用自己的特殊材质球进行渲染。当颜色动画对颜色和Alpha进行更改时,我们自己与自定义的材质球进行更改,把渲染工作交给了新的材质球,就不需要重构网格了。而新的材质球的颜色和Alpha上的变化都是通过改变材质球属性来实现的,并不是通过UGUI设置顶点颜色来达到效果,减少了UGUI重构网格的消耗。
8、UI展示与关闭的优化
在打开界面时会需要实例化和初始化,关闭界面需要销毁GameObject,所以会消耗一定的CPU,实际项目中消耗量很大。
对于这类优化,除了上面提到过的利用碎片时间进行预加载,还可以在关闭时选择隐藏节点而不是销毁,打开时重新激活节点而不用实例化。
在一个项目中,我们经常需要连续打开多个界面,比如游戏大厅为一级界面,用户可能会打开背包、邮箱等模块,若此时大厅界面activity为true,那么将同时合并渲染两个界面,增加了drawcalll。所以我们一般在打开全屏界面的时候需要将上层界面关闭,置为不可见。
关闭和激活时,内存虽然没有变化,但依旧会有网格重构和组件激活而大量消耗CPU,所以移出屏蔽会比隐藏的效果更好(使其在相机渲染之外,仍在Canvas节点下),但移出并不会让CPU的消耗全部消失。为了减少更多的消耗,在移出屏幕后还需要关闭一些脚本上的更新内容,即使这样,也会有不少组件是无法停止的,但比销毁或者隐藏好很多。当需要显示时再移入屏幕,这时候可以做些初始化操作回到原本状态。
在元素移出屏幕后,相机会对其进行裁剪判断,只渲染视锥体内的界面,但裁剪仍会消耗部分CPU,因此我们还需要设置UI为不可见层级的Layout,使其排除在相机渲染之外,这样就节省了相机的裁剪消耗,当需要展示时再设置回UI层级。
9、对象池的运用
对象池通俗来讲就是寄存了一些废弃对象的池子,当程序需要某对象时,可以向对象池申请,让我们对废弃对象再利用,实质就是内存重复利用的性能优化方案。
我们对废弃对象再利用不仅可以节省实例化对CPU的消耗,还能省去很多内存碎片和GC问题。其中实例化消耗包括模型文件读取、贴图文件读取、GameObject实例化、程序逻辑初始化、内存分配消耗等。同时,重复利用能减少内存脆片,使得内存更加连续,增加CPU缓存命中率。
对象池是程序设计中的重要部分,这里不多赘叙,不了解的可以参考其他详解文章。
总结几条运用对象池的经验:
- 当程序中有重复实例化并不断销毁的对象时可以使用对象池进行优化;
- 每个需要使用对象池的对象都需要继承对象池的基类对象,这样在初始化时可以针对不同对象重载;
- 销毁对象时使用对象池提供的回收接口,切记不要重复回收对象,也不要错误的放弃回收;
- 场景结束时要及时销毁整个对象池,避免无意义的内存驻留,此时对象池已不适合新的场景了。
10、UI贴图设置的优化
要知道,Unity3D会重置所有的贴图格式,也就是说无论.jpg、.png格式,还是.psd格式,只要在Unity3D中,Unity3D就会读取图片内容生成一个自己格式的图,因此在Unity3D中使用图片不用关心什么格式的图。但是我们需要关系图形或者颜色上的东西,比如.jpg是没有Alpha通道的,通常透明贴图都是使用.png,其他的交给Unity3D就可以。
但图片的设置中很多讲究,会影响到重新生成的图片的格式,最终决定载入引擎的图片的格式。我们在unity中选中一张导入的图片,在Inspector面板中可以看到Advance属性,我们需要注意几个问题:
- 是否需要Alpha通道。需要则打开,不需要则关闭。
- 是否需要进行2次方大小修正。对于UI贴图来头像这类Icon基本上都是2次方大小。
- 去除读、写权限。这里通勾选的话,会使贴图在内存中存储两份方便脚本读写,导致比不勾选的时候内存增加一倍。
- 去除Mipmap。Mipmap是对3D远近视觉的优化,通过生成不同大小的图,在摄像头远离物体时因为不需要高清的图片而使用Mipmap生成的小而模糊的贴图,从而减轻GPU的负担。但2D中没有远近之分,不需要这个功能,使用的话反而会导致内存和磁盘空间加大,UI看起来模糊。
- 选择压缩方式。压缩主要时为了降低内存的消耗、加载时的消耗,降低CPU与GPU之间的带宽消耗,在保证清晰度的前提下,应尽可能的选择一个压缩方式来优化内存和包体。下面这些压缩方式压缩力度逐级增大:
- 最高的色彩度是无压缩的
- 其次是RGBA16,色彩少了部分,且有Alpha通道
- 再次是RBG24,没有Alpha的全彩色
- 之后是RGB16,色彩少了一半,且无Alpha通道
- 再后是RGBA ETC2 8位和RGBA PVRTC 4位的带Alpha通道的压缩算法
- 醉胡是RGBA ETC2 4和RGB PVRTC 4位的不带Alpha通道的压缩算法
除此之外,我们还可以通过写脚本的方式来设置UI贴图,将放入UI的贴图自动设置我们规定的图片选项,还能节省不少二次检查的时间。下面是用Unity3D的Editor API来自动设置UGUI的精灵图片:
void Apply_UI_Sprite()
{
if(!UIAssetPost.IsInPath(assetImporter.assetPath,UI_Sprite_path))、
{
return;
}
TextureImporter tex_importer = assetImporter asTextureImporter;
if(tex_importer == null) return;
tex_importer.textureType = TextureImporterType.Sprite;
FileInfo file_info = newFileInfo(assetImporter.assetPath);string dir_name = file_info.Directory. Name;
tex_importer.spritePackingTag = dir_name;
tex_importer.alphaIsTransparency = true;
tex_importer.mipmapEnabled = false;
tex_importer.wrapMode = TextureWzapMoce.Clamp;
tex_importer.isReadable = false;
SetCompress(tex_importer);
}
11、UI图集拼接优化
UI图集想必从事Unity3D开发的都不陌生,它的大小、个数在一定程度上决定了项目打包后的大小和运行效率。对UI图集优化可以减少很多浪费的空间,增加CPU工作效率,下面介绍几种UI方法:
1)充分利用图集空间。将图片拼在一起时,尽量减少碎片空间,比如把大图分开来拼接,大图穿插小图。
2)图集大小控制。如果不控制大小,容易出现20482048甚至40964096的像素的图,导致加载UI时异常卡顿。我们需要规范图集大小,例如1024*1024。
3)图片的拼接归类。在没有归类的情况下,加载UI会加载一些不必要的图集,导致加载速度变慢,消耗过多内存。例如可以分为常用图集(各个界面都可能用到的)、功能图集(只在某个特定界面才用到的)等。
12、针对高低端机型的优化
我们知道,游戏的画质和流畅度依赖于设备的性能,所以就需要区分不同机型的设备区别使用画质,来保证不同的机型都能流畅的运行游戏。
13、内存泄漏
什么是内存泄漏?
内存泄漏,简单来说就是由程序向系统申请内存,使用完毕后并没有将内存还给系统而导致内存驻留或者浪费的过程。系统内存是有限的,若是一直泄漏会导致系统奔溃,计算机系统不会无限制地让程序申请到内存,当申请内存影响到系统运行时就会停止。
内存泄漏可以简单分为两类:
- 资源上的泄漏:主要是指资源在使用后或不再使用时没有卸载而导致的泄漏。
- 程序上的内存泄漏:主要是指因为Mono的垃圾回收机制并没有识别“垃圾”而造成的泄漏。
Unity3D使用C#作为脚本语言,有垃圾回收机制(GC),GC的详解在这里不过多叙述,但GC也不是万能的,我们还需要手动的进行排查。
资源上的内存泄漏排查
1.Unity3D的MemoryProfiler是个排查内存泄漏的利器,它是由官方开发的专门用于Unity3D 5.x以上版本的内存快照工具。其地址如下:https://bitbucket.org/Unity-Technologies/memoryprofiler。它可以快照闪存信息,并以文件形式保存和加载,我们就可以在不同节点快照来进行对比找出泄漏点。
2.使用Unity3D自带的Memory Profiler工具。它会记录CPU的使用情况,定位CPU耗时的点,也可以记录Momo推内存和资源内存的使用情况。
14、GC优化
未完待续。。。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|