UGUI的一些性能优化经验

自从Unity问世以来,UI一直都存在比较大的问题,自带的OnGUI不能所见即所得,制作过程比较麻烦。于是出现了很多第三方的优秀的UI插件,比如很多项目里面用到的NGUI,或者后来出的FairyGUI。Unity官方在4.x时代开始推出了自己的新UI系统,名为UGUI。由于是官方出品,所以选择使用的人也比较多。

阿赵我也是基于信任官方出品的心理,在最近的两个项目里面都使用UGUI进行开发。UGUI是一个比较完整的所见即所得式的UI系统, 不过说实在的,我个人感觉它的成熟度还是差了点,有很多需要注意的地方,不然就会产生很多性能上的问题,比如DrawCall问题和运行时的效率问题。我就曾经在项目中遇到了,UGUI用的不好,它产生的消耗比3D场景内的地图和角色消耗还高的情况。老实说,我用UGUI也用的不好,这里也只是分享一点经验,这次主要讲一下UI重绘的消耗和图集的一些问题。

要了解UGUI的一些坑,首先要明白它是基于什么原理来运作的。

首先,UGUI还是基于网格模型渲染的。一个矩形,实际上还是由4个顶点组成的一个mesh显示出来的,和正常的mesh模型渲染一样,它使用了材质球和贴图。我们平常看不到这些元素,是因为渲染UI用的Mesh网格是在运行中才生成的,它默认使用了Untiy内置的一个默认的材质球,然后使用我们指定的UI图片作为贴图。
这里写图片描述
打开一个Image来看看,可以看出Material是空的,那是因为它有默认的材质球。然后Unity也允许你再指定一个Material来渲染。值得注意的是,这个材质球如果你是直接挂在UI的Image上面,那么如果你在运行时修改Material,将会改到的是Share的原始Material。所以最好的做法是在代码里面生成一个材质球的复制实例,然后再赋给Image使用。

明白了单个UI元件的渲染原理之后,接下来需要说说渲染合并的问题。既然一个Image就是一个Mesh,然后上面有单独的贴图显示。那么是不是就代表着,我一个界面里有有100个UI元件,那么就会产生100个Mesh,产生100个DrawCall呢?

从原理上的确是会这样,但实际运行的时候,并没有产生这样的结果。这里Unity引入了一个Canvas(画布)的概念。在默认创建的UGUI根节点上面,你可以看到这个Canvas
这里写图片描述
它定义了一些东西,比如当前画布的渲染模式、排序情况。

然后当你往这个Canvas里面添加其他UI元件的时候,这些元件实际上就是被这个Canvas统和在一起了。比如,同一个Canvas里面有100个Image,Canvas将会把100个单独的Mesh合并成一个大的ShareMesh,用于渲染。如果刚好这100个Image都是使用了相同的图片或者是同一个图集里面的图片,那么由于使用的Mesh只有一个(ShareMesh),材质球都是同一个(内置的默认材质球),贴图也是同一张,所以得到的结果就是,DrawCall只有一个。

这实际上就是UGUI最基本的优化思路了。合并ShareMesh、合并图集,减少DrawCall。但在实际项目的操作中,会遇到很多问题。

就拿ShareMesh来说,我们可以把场景的静态合并拿来对比一下。这个ShareMesh和场景合并成CombinedMesh是一样的,把很多小模型合成一个大模型。但可以想象,场景的模型合并是静态的,也就是说它是摆在那里不会动的。大部分情况下,UI也是不会动的,在这种情况下,合并ShareMesh没有太大的问题。但还是会有很多会动的时候。比如说,Arpg很喜欢在角色头顶上显示角色名字,名字会跟随着角色移动。再比如,聊天里面不停的刷各种文字聊天信息,或者你需要一个图标从左边飞到右边的动画,或者你的血量条需要动态变化长度之类,等等。这些情况下,由于单个显示元素的Mesh 形状发生变化(文字的改变实际上也是改变了Mesh形状),如果原来这些UI元素就是和其他所有UI元素合并同一个ShareMesh的,那么问题就来了,假如你UI元素非常多,产生了一个2M的ShareMesh(这是一个比较极端的例子,实际上一般的ShareMesh都是几百K,出现这么大的ShareMesh本身就需要注意了),由于一个小顶点发生了改变,导致2M的整体ShareMesh也需要重新的生成顶点信息,并且整个ShareMesh重新渲染,这明显是得不偿失的。你会发现在UI动画的过程中,整个游戏都在卡。卡的是cpu方面的实时合并网格顶点,和渲染那边的整个大Mesh重新绘制。

那么怎么解决这个问题呢?思路也像场景模型和角色模型的关系一样。首先要知道的是,在根节点下面挂了Canvas之后,还是可以在子节点上面继续的挂Canvas的。这样,从理论上说,Unity会找到最小单位的Canvas作合并。既然大部分的UI是不会动的,那么我们可以做一些分层,把不会动的元素放在同一层,然后会动的元素,根据动的频率和类型,分别在他们的子节点上面挂上Canvas。这样就可以在保证大部分UI元素能合并渲染的基础上,减少整体重绘的范围。

听完上面的一些关于Canvas的说法之后,你是不是有种想法,那么那些会动的东西要不挂多几层Canvas,让它控制得更细腻?当初我也是这样想的。不过后来发现这样做很有问题。Canvas存在一个重绘的问题。如果你把一个Canvas的节点SetActive为false之后,再在它身上做改变坐标之类的操作,再把Canvas节点SetActive为true,这时候Canvas渲染的还是之前的画面,并没有把改变的东西渲染到。在这个时候,要么Canvas里面的元素稍微变化,让它被动的触发重绘,要么调用ForceUpdate的API让Canvas主动重绘。但这样的操作如果在Canvas嵌套了很多层之后,是很难去控制的,也不可能每次都强制主动重绘Canvas,毕竟很多时候是没必要这样做的。最重要的一点是,编写UI框架的时候,不应该要以后写业务逻辑的人去关心业务上导致UI显示发生改变后,再去做些什么样的潜规则才能正常显示出来。所以我建议Canvas也不要分得太多,特别不要嵌套得太多层次,导致失去控制了。

说完Canvas的ShareMesh合并网格,最后再说一下图集的问题。如果游戏内容比较多,是不太可能把所有的UI图片都合并在同一个图集里面的。所以必然会产生多个图集的问题。而使用同一个图集的Image能合并成一个DrawCall的前提,是他们中间没有再穿插着使用了其他图集的Image。这里指的穿插,也包括矩形边缘的穿插。所以你可能看到两张图好像没有叠在一起,但也有可能因为透明区域的原因产生了叠加。
这里写图片描述

这里写图片描述
所以在设计UI的时候,最好避免太多层叠在一起的设计。边界也尽量的分明。

这样是一个理想中的设计方向,但实际上很多UI设计师在设计的时候觉得有这样的限制是不好看的,他们喜欢做一些叠层作为修饰效果。所以也不能绝对的说不能做,只是在做叠层的时候,需要考虑清楚,每叠一层,叠的层是不是可以合并的,尽量多考虑一下叠完之后,图片的元素需要怎样分图集才合理。然后如果是在界面边框上面加花,我觉得这种情况是必须要分离出来叠层了。因为本身UI界面一般都是做成九宫拉伸的,不能因为你加了条很大的龙作为装饰,就导致底框的背景图也出的很大。

虽然很多刚入门Unity的人,特别是程序员,在进公司之后都会被安排为做UI功能。但我一直不认为UI就是一个这么简单的工作。要懂得一个UI系统的潜规则,做好性能优化,让自己做出的界面比别人的好看又流畅,我一直都觉得是一个很困难的主题。

对于UI美术来说,如果不懂这些优化的原理,在设计UI结构的时候可能就会产生很多程序员也无法去解决的问题,最后只能要么性能差,要么大改方案重新做。我觉得懂得思考这些问题之后,才算是一个合格的游戏UI设计师,而不仅仅是会使用Photoshop,画出图片效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值