《Unity3D高级编程之进阶主程》第四章,UI(七) - UI优化(二)

这篇我们来继续聊聊优化,UI图集Alpha分离,UI字体拆分,Scroll View 滚屏优化,以及UGUI图在改变颜色或Alpha后,导致对Mesh重构的优化。

===

④ UI图集Alpha分离。

为什么要对UI图集进行Alpha分离?

我们对UI图集的压缩是减少APP包体大小的一部分,这也是减少内存使用量的一个比较有效方法,内存减少的同时对CPU也会降低些消耗。UI图集的压缩好处很多,但同样也会引起些问题,当我们对图集进行压缩后,在屏幕上显示的效果却不尽如人意,模糊,锯齿,线条等劣质的画面出现。这是因为我们在使用压缩模式ECT或PVRTC时将透明通道也一并压缩进去了,导致了渲染的扭曲,因此我们需要把透明通道alpha分离出来单独压缩。这样就既可以压缩图集,达到缩小内存目的,图像显示又不会太失真。

如何分离UI图集的Alpha呢?

我们这里主要是针对NGUI的方案,而UGUI由于是内部集成的,Alpha分离在Unity3D的UGUI中已经帮你完成了,因此我们仅仅针对NGUI来讲一讲如何分离alpha。

首先,用TexturePacker在打图集时将原来打成2张的图集,改成打成一张RGB888的png和一张Alpha8的png。RGB888的PNG图没有alpha,而所有的alpha通道都在Alpha8的PNG里。也可以使用程序分离的方式,把原图中的颜色提取出来放入一张新的图片中,alpha部分提取出来放入另一张图片。

然后,我们需要修改NGUI的原始shader,把原来的只绑定一张主图的shader改成需要绑定一张主图和一张Alpha图的shader。

需要修改下面这4个Shader

	Unlit – Transparent Colored.shader,

	Unlit – Transparent Colored 1.shader,

	Unlit – Transparent Colored 2.shader,

	Unlit – 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 texcol = tex2D(_MainTex, i.texcoord);   
	 fixed4 result = texcol;  
	 result.a = tex2D(_AlphaTex,i.texcoord).r*i.color.a;

上述代码中,注释掉的部分为原始的只用一张图承担颜色和透明通道的,新加入的方式为用_MainTex和_AlphaTex这两张图分别替代颜色和透明通道。

最后,在以上都完成后,选中一个创建好的图集 prefab 会发现 Inspector 窗口下的预览窗口以及 Sprite 选择窗口中看到的 sprite 都是没有 alpha 通道显示,这是因为用于Editor下的展示模式仍然使用的原始的一张图使用两个通道的方式,因此我们也需要修改这些编辑器上的NGUI工具。

修复这个问题我们的解决方案是在编辑器模式下动态生成一个rgba32的texture来替换它,rgb和alpha通道的值分别取两张我们现在拥有的图。

其中需要修改的NGUI编辑类的有以下这几个文件:

	UIAtlas.cs,

	UIAtlasInspector.cs,

	SpriteSelector.cs,

	NGUITools.cs,

	UISpriteInspector.cs

修改以上类里,绘制图片时都启用新生成的图,就是上面所说的用RGB888和Alpha合成的临时图。

其实修改的部分并不多,修改的方向和原理也简单,首先生成两张图一张只带颜色,一张只带Alpha通道,再是 Shader 的 alpha 来源修改为新的alpha图,最后是 Shader修改导致编辑器的显示问题,需要在编辑器部分生成临时的图来替换原来显示的图。

⑤ UI字体拆分。

为什么要拆分UI字体?

项目中字体其实占了很大的空间,如果有几个不同的字体一起展示在屏幕上,会消耗较大的内存。字体很多时候不可避免,但需要规范和整理,并且也需要优化。我们需要更快的性能效率,拆分字体会让加载字体的速度更快,让场景加载的速度更快。

如何拆分UI字体?

我们的解决方案是把字体中的常用字拆出来,另外生成一个字体文件,让字体文件变小,内存变少,最终使得加载变快。

比如在登陆场景中,我们只需要几个数字和字母,所以我们大可以从字体中提取数字和26个字母成立一个新的字体在场景中应用,这样就省去了大的字体的加载。

又比如,注册登陆后的取名字的场景,我们去掉部分使用频率比较少的字,只保留3000个常用字。将字体中常用的3000字拆出来,生成新的字体进而用到场景中去。这样就节省掉了很多无用的字形图片存放在字体里,从而节省了不少内存空间。

⑥ Scroll View 滚屏优化。

Scroll View 使用在类似背包的界面中非常常见,会有巨量的元素存在在窗口中进行渲染,所以在生成和滑动时,会消耗大量的CPU来重构Mesh,进而导致游戏运行缓慢,出现卡顿现象。这是由于我们前面在UGUI源码剖析中介绍过的元素属性上的改变将导致网格的重构,如果不断移动则每帧都需要重构,导致大量的CPU浪费。

要优化这种情况,就必须对滚屏菜单组件进行改造,将原来策略中所有元素都必须一次性实例化的问题,改为只实例化需要显示的实例数量。然后在拖动滑动的期间,实时判断是否有有UI元素被移出画面,这样的元素可以重复利用,将他们填补到需要显示的位置的上去,再对该单位元素的属性重新设置,我们需要的元素信息,让它展现为在该位置需要显示的元素的样子。

从表现上观察就如同下面所描述的那样:

	我们在窗口中实例化10排元素显示在那里滚动窗口中,其中5排是展示在中央的窗口上的,另外5排中的顶上2排因为超出了窗口被裁剪而无法看见,

	同样的下面3排也是因为超出了窗口被裁剪无法看见,在整个10排元素整体向上滑动期间,顶上2排变成了3排,底下3排变成了2排,

	其中最顶上的1排超过了重置的界线,就被移动到了底下去了,这样整体10排元素,变成了顶上2排,底下3排的局面,

	这样不断反复,不断在移动顶上或底下的1排元素,把他们移动到需要补充的位置上去。看起来像是,很顺畅地上下滚屏整个500个元素那样,实际上是对这5排元素在不断重复的利用而已。

Scroll View 自定义组件是大部分项目都必须的,大部分项目都会遇到这类问题也会去改善这个问题,有一个自己优化过的自定义组件,能很快很高效的解决这类问题。

⑦ UGUI图在改变颜色或Alpha后,导致对Mesh重构的优化。

这里再稍微解释下为什么在UGUI的图元素在改变颜色或Alpha后会导致Mesh的重构?UGUI的Mesh的合并机制是拥有相同的材质球的Mesh合并在一起才能达到最佳效果,一个材质球对应一个图集,只有相同图集内的图片才需要合并在一起。

UGUI中当元素需要对颜色进行改变时,UGUI是通过改变顶点的颜色来实现颜色的变化的。改变当前元素的顶点颜色,然后需要将它们重新合并到整块的Mesh里去,因为不能直接从原来合并好的Mesh上找到当前的顶点位置,所以需要一次整体的合并重构Mesh。

元素改变了 alpha 则会更糟糕,由于改变 alpha 的效果无法通过改变顶点的颜色来实现,于是就需要拆分出一个另外的材质球来进行渲染,通过对材质球的参数改变来实现 alpha 的效果。这样做不但重构了Mesh,还多出来个材质球,就相当于多一个Drawcall,效率消耗相当大。

倘若在动画里,每一帧都对UGUI的颜色和Alpha进行改变,那么UGUI每一帧都会对Mesh进行重构一次,并且每帧都生成新的材质球来实现 alpha 的透明效果。这样做消耗了大量的CPU运算,通常使得UI界面在运行动画时效率特别低下,即使拆分动静分离也无济于事。

如何对此做优化呢?我们不希望在UI颜色改变时,导致Mesh重构,这样动画中消耗掉太多CPU,那么我们就自己建一个材质球,提前告诉UGUI:我们使用自己的特殊的材质球进行渲染。当颜色动画对颜色和 alpha 更改时,我们直接对我们自定义的材质球进行颜色和 alpha 的改变。这样UGUI就不需要重构Mesh了,因为把渲染的工作交给了新的材质球,而不是通过 UGUI 设置顶点颜色和新材质球来达到效果。

如何操作?

	首先,我们需要把UGUI的Shader下载下来。

	然后,建立一个自己的材质球,并且材质球里使用下载下来的UGUI的Shader。

	再次,把这个材质球放入Image或RawImage的Material上去,与Image或RawImage绑定。

	接着,写个类比如class ImageColor继承MonoBehaviour,里面有个public 的颜色变量,比如public Color mColor,类里面只干一件事,在update里一直判断是否需要更改颜色,如果颜色被更改,就把颜色赋值给Material。

	最后,把动画文件中的颜色部分从更改Image或RawImage的颜色变为更改 ImageColor 的颜色变量。这样UGUI颜色动画在播放时,不会直接去改变 Image 或 RawImage 的颜色,改变的是我们创建的 ImageColor 的颜色。

通过 ImageColor 来改变材质球属性,最后达到不重构Mesh的效果。切换元素的贴图时也一样可以做到不重构的效果,由于贴图更换会导致重构,为了达到不重构的目的可以给一个自定义材质球的并且更换材质球中的贴图。

不过要注意下,因为启用了自定义的材质球,所以Drawcall就提高了,因为每个材质球都会单独增加一次Drawcall。并且当 alpha 不是1的时候,会与原有的UGUI产生的材质球的透贴形成不同的渲染排序,原因是当两张透贴放在一起渲染时,alpha混合会导致渲染排序混乱而前后不一致。所以使用时要小心谨慎,权衡利弊用在恰当的地方将发挥大的功效,用在不恰当的地方则事倍功半。

这个半透明物体的排序问题,归根结底是无法写入深度数据问题,是3D渲染中无法彻底解决的问题。我们会在后面的渲染管线与图形学章节中详细介绍。这里解决半透明排序问题,可以通过改变自定义的 Shader 中的渲染次序(RenderQueue)来解决。

· 书籍著作, Unity3D, 前端技术

感谢您的耐心阅读

Thanks for your reading

  • 版权申明

    本文为博主原创文章,未经允许不得转载:

    《Unity3D高级编程之进阶主程》第四章,UI(七) - UI优化(二)

    Copyright attention

    Please don't reprint without authorize.

  • 微信公众号,文章同步推送,致力于分享一个资深程序员在北上广深拼搏中对世界的理解

    QQ交流群: 777859752 (高级程序书友会)