2. 资源优化
病从口入,资源好比是入口,它们若出现问题,会引发一连串性能问题。相反,资源若是优化得好,后面所有章节的性能都可受益。这也是把资源优化的章节提到最前的原因。
2.1 纹理优化
纹理优化的目的是让它们占用的内存尽量的小,那么纹理加载进内存后,大小计算公式如下:
纹理内存大小(字节)像素字节==纹理宽度×纹理高度×像素字节像素通道数(R|G|B|A)×通道大小(1字节|半字节)纹理内存大小(字节)=纹理宽度×纹理高度×像素字节像素字节=像素通道数(R|G|B|A)×通道大小(1字节|半字节)
从上面公式可以看到,纹理加载进内存后的大小跟尺寸/像素通道数/通道大小都有关系,我们就从它们着手优化。此外,还可以通过提高复用率和合成图集达到优化的目的。
2.1.1 纹理尺寸
美术常犯的一个错误是不管什么角色什么场景,都会给模型贴上很大尺寸的贴图,通常大于1024x1024。
比如游戏Z是斜45度固定视角对战游戏,在画面中角色渲染所占的画面很小(约150x150),但美术在最初给所有角色模型都制作了1024x1024的贴图。根据项目实际情况,博主将所有角色贴图都缩小至256x256,角色贴图占用缩小至1/16。除了角色贴图,武器/装备/特效/场景等等所有涉及的贴图都缩小至合适的大小。这里的合适大小是指渲染对象在画面中大多数情况下不可能达到的最大尺寸,这个尺寸最好保持2的N次方。
2.1.2 纹理通道
通道优化的目的是降低像素所占的大小,可以通过以下方法达到目的:
-
去除Alpha通道。可以减少通道数量,适用于不需要Alpha混合或Alpha Test的角色和物件。
-
应用单通道图。也可以减少通道数量,比如灰度图,地形高度图,掩码图,Shader掩码图等等。
-
使用16位代替32位图。例如RGB444/RGBA4444就可以减少像素通道大小。
-
压缩贴图适应不同的平台。例如:
- Windows可以压缩成DDS,其中DDS细分DX1~DX5共5种格式,每种应用场景略有不同,但它们只能用于DirectX。
- Android可以压缩成ETC1(不带Alpha)或ETC2(可带Alpha)。
- iOS可以压缩成PVRTC格式。
它们都是GPU直接支持的纹理格式,可以显著减少内存/显存/带宽的占用。
-
避免使用JPG/高压缩率的PNG/GIF等低质量格式。因为当前主流商业引擎在游戏发布过程中,会自动压缩所有纹理,而保留原画质的纹理可以减少纹理压缩后的画质损失。
2.1.3 提高纹理复用率
以下方法提高贴图复用率:
- 建立共享图库。将通用的元素放至共享库,例如按钮/进度条/背景/UI通用元素等。
- 用九宫格图代替大块背景图。九宫格在游戏开发中是比较常见的UI组件。
- 纹理元素通过变换可组合成复合纹理。例下图,上下左右对称的背景图可以用4张相同贴图实例通过旋转/翻转后获得。
- 九宫格+UI元素可以组合成很复杂但消耗相对较小的UI界面。
2.1.4 纹理图集
图集就是一堆小尺寸纹理元素合成的纹理贴图(如下图)。
图集可以降低IO加载次数,也可以减少Draw Calls(详见4.2 Batch合批),但也有副作用:
1、可能超出设备支持的最大尺寸。
2、可能出现大片空白像素(如下图)。
对于副作用1,可以限制图集的最大尺寸(通常不要超过2048x2048),分拆成多张图集。对于副作用2,可以针对性地调整纹理元素的布局或尺寸,使得合成的图集尽可能占满有效像素。
适合生成图集的资源有:UI界面,道具图标,角色头像,技能图标,序列帧,特效等等。
如果是Unity引擎,可以用SpritePacker很方便地生成和预览图集。如果是自研引擎,可以用TexturePacker的命令行工具合成。
2.2 UI
2.2.1 UI图集
所有UI元素都生成图集,而且确保每个界面生成单独的图集,这样可以在界面销毁时可以及时释放UI纹理。要尽量确保每个界面只引用到自己的图集和共享库图集,避免引用到其它界面的图集。
如上图所示,界面A引用界面A图集和共享图集是允许的,但尽量不要引用界面B等其它图集。但实际在游戏开发过程中,很难保证美术做到这一点,通常存在以下问题:
- 如果界面A确实要用到界面B图集的某个元素,怎么办?
参考解决方法:要看被引用元素的通用度,如果只是界面A和B在用,可以将被引用元素拷贝到界面A图集下;如果其它界面也会引用到,就可以将它移到共享图库。
- 有些UI纹理很大且很多界面都有用到,如果放在共享图库会导致共享图库急剧膨胀,怎么办?
参考解决方法:大尺寸纹理建议用九宫格+细节图,或通过组合的方式来代替。
- 如何保证美术制作的UI只引用到自身图集和共享图集?
参考解决方法:实现批处理检查工具,找出每个UI界面引用到的图集列表,引用的图集超过2个便是不合格。
2.2.2 UI层次
由于UI元素很多,主流商业引擎都会对它们合批以减少Draw Calls,但合批优化是有条件的:
1、使用相同的材质。使用引擎默认UI材质+UI图集可以满足这个条件。
2、绘制顺序是连续的。UI的绘制顺序通常就是在场景中的节点顺序。
下图有4张UI图片,但它们都用了系统默认材质,都是共享图集的元素,并且它们在场景中的顺序也是相连的,所以满足合批优化的条件,最终SetPass Calls(Draw Calls)是1。
然而,在实际制作UI过程中,经常会破坏UI合批优化的条件:
- 同个界面往往会引用自身图集和共享图集,破坏优化条件1。
- 界面通常都带有文本,而文本通常是引擎自动生成的另外一张或若干张图集,破坏优化条件1。
- UI节点层次混乱,不同图集的元素相互交叉,破坏优化条件2。
- 部分UI元素使用了自定义材质或Shader,破坏优化条件1。
分析了原因,在UI制作时,就要尽量避免这些情况发生。
2.2.3 UI的其它优化
- 禁用MipMaps。MipMaps的原理是根据绘制对象在绘制空间的大小选取合适的纹理层级,它会增加30%的内存/显存开销。而UI通常都是等长等宽的,跟摄像机距离无关,所以要禁用UI的MipMaps。
- 保持UI纹理的原始尺寸。缩放通常会带来额外的开销,而且会使UI变模糊,降低画质。
- 避免使用大尺寸的背景图。大背景图耗内存,通常还不能共用。可用九宫格+细节图组合而成。
2.3 字体
说字体是性能的杀手毫不为过。游戏使用的字体一般是ttf格式,单个ttf字库少则510M,多则1020M。在文本绘制前,引擎会将字库加载进内存,占用较大的内存空间;在文本绘制时,引擎会在内存中开辟若干张纹理图集缓存字体纹理。每个字至少要两个三角形,若是有阴影/描边/发光等效果,三角形数量扩大数倍之多(下图)。
可以通过以下建议优化字体的性能:
- 控制字体文件数量。除了系统默认字体,自定义字体控制在1~2个为宜。
- 少用字体的阴影/描边/发光等效果。
- 剔除字库中无用的字形。可以借助FontSubsetGUI或FontPruner给字库瘦身。
字库瘦身更多参看这里。