6年前刚开始做手游的时候,以单机起步,资源量和代码量都很少,那时没有运营的概念,也没有增长黑客这样的sdk,随手打个包不大可能会超过10mb,也不会因为单屏资源量太大导致内存溢出,贴图怎么也想不到会用到超过1024X1024。可是从2D 走向3D,从单机走向网游,资源的管理逐渐成为产品把控中的重要一环。
基础篇
先普及几个概念,懂得略,没看懂这几个词区别的就细细来。
来张暴露年龄的图,如下:
丨游戏的美术资源管理
在早期 win 系统的显示控制面板中,我们都会看到分辨率旁边一个额外的『颜色质量』选项,其中下拉菜单一般包括16位、32位、单色和256色。如果切换这里的颜色质量,屏幕会从五颜六色的画面回滚至黑白电视机般的画面感。为什么呢,其实这里的配置决定的就是你屏幕上单个像素点中的色彩数量,数量最少的就是黑白两色,256种颜色如果用8位二进制数表示,就是2的8次方,所以256色也被简称为8bit(位图,也就是常说的向量图,如果是矢量图,那么计算方式另算)。依次类推,如果颜色用16位二进制数表示,就叫它16位图,2的16次方等于65536种颜色,24位图就包含2的24次方即16777216种颜色。
那看看我现在的电脑
游戏的美术资源管理
恩,乔老爷大法好!
但是等等,如果你留意下会发现32bit 的旁边,赫然备注着 ARGB8888,这4个8又是什么意思?
丨色彩位数
其实在色彩数量中我们已经间接提到了色彩的位数,那就是我们不可能去记16777216这样的数字,所以通常采用位数来表示色彩的数量。
色彩的位数又称为色彩的深度,采用『n位颜色』来说明,所以如果一共有2^n种颜色,那么就计为 n 位色彩,换句说法就是色彩深度是 n 位,也就意味着每个像素点上的色彩位数为 n。
一般来说,常用位数有:
更高级的还有工业中常用的高动态范围影像(High Dynamic Range Image,简称 HDR),这种格式的文件将使用超过一般的256色阶来储存影像,也就是说每一个原色都使用一个32位来存储,所以色域空间更广,通常能记录肉眼在屏幕中无法查看的色彩。如果在 Blender 中打开一个 exr 文件,效果如下。
可以看到 Waveform 图中色彩的波段,如果将其转换成 JPG 这样的24位图,再看看效果:
由于 JPG 是有损压缩,所以肉眼无法看到的色彩都是可以被抛弃的部分。这时看看直方图,是不是发现其实还有很多色彩是被腰斩了?而画面上其实很难察觉区别,是不是这些超出肉眼识别(显示器显示范围)的色彩无用或者无法利用呢?其实只要采用合理的曲线或色阶工具,就可以将所有的色彩收敛到肉眼可看到的色域空间内,效果如下:
之前有做过一个视频教程专门讲这块,因为我用英文讲的,所以得《Color correction in Blender ( waveform & vectorscope monitor ) 》。
可是,图片中的色彩又不是什么超频谱的射线,为什么我们在屏幕上看不到?难道是我们的人眼有 bug?其实不是我们的错,一张图解毒:
如上示意,我们的肉眼其实开挂的,能将色彩信息传递到大脑并感知的数量是远超过32位,不过我没去查过到底多少位,但这个命题应该等价于『人眼可以识别多少个颜色的数量』,真正的问题,其实是出在显示器上。
丨色彩空间
又叫『色域』,可以理解为用来建立色彩感知体系的模型,常见的包括:
打开你的色彩配置参数,可以看到类似下面的配置:
这里三角形描绘的就是当前屏幕可展示的色域范围,如果要做比较,可以把多色域的图叠加起来,效果如下:
明显可以看到苹果了100% 的 sRGB 色域标准,无论是 Mac 还是 iPhone,所以苹果产品的屏幕显示在早期一直都是最接近设计需求的屏幕,不是因为他的硬件本身多牛逼,而是它对色彩的定义牛逼。后来小米出来,说他们实现了90%以上的 NTSC 色域,苹果你就是个渣,可是评价一块屏幕,真不能只是看色域,还有亮度、对比度、响应时间、色彩偏离度(ΔE)、子像素排布方式等等。所以我不是小米黑,但我确实是苹果粉。
丨线性空间
继续翻看色彩管理配置,可以发现下面参数:
如果熟悉 photoshop 的曲线工具,就会记得如下效果:
按照曲线中横纵坐标对平滑度的定义,色彩是一个线性的渐变过程,通过对曲率的调整可以对色彩的明亮等做二次分配,可为什么我们的显示器会采取曲线来显示色彩的渐进呢?那这个关键参数就是 Gamma(伽马)值。
网图,用来解释这几个概念最好了,如下:
这里有3个指标很重要,显示(屏幕)色彩曲线,线性曲线和 gamma 曲线,他们分别代表了不同场景中对色彩的输出。我们的人眼其实是线性的色彩系统,当然我们的大脑会默认看到的都是线性的,因为人眼没有其他标杆参考,所见即所得。可是显示器在生产的过程中,由于交流输入电压本身的曲线特性,其输出亮度的关系一定无法实现线性的(不要问为什么不能用直流电来制造显示器)。比如输入0.5的电压,则会输出大约0.2的亮度,要想获得0.5的亮度,则必须输入0.73。在图上用曲线来理解的话,红色的线条其实就是屏幕的实际色彩空间曲线,手机靓号拍卖平台为了让我们的人眼可以看到最接近真实的图片色彩,或者是希望打印效果和屏幕效果是一致的(因为打印出来后,对色彩的判断只有人眼了,而电脑中的图片需要屏幕做一次中转),就需要将屏幕上看到的色彩校正至线性,也就是称为的 Linear 线性空间。方法很粗暴,那就是绘制一条反向的曲线,将两者叠加,就能获得一条线性色彩空间了。而这个反向曲线就是 Gamma 曲线。
所以在制作的过程中,为了保证输入和输出一致,理想和现实最接近,就需要考虑是否需要在每一个增加色彩的环节执行线性色域转换,例如3D 中对材质和贴图是否需要额外的线性工作流程。
中级篇
理解了基本概念,接下来就说说移动设备相关。需要先普及几个概念,列一下:
丨设备概念
那么,当PPI为267,高度为18sp(30px)的字体物理高度计算方式为267 / 25.4 = 30 / X,X = 2.86mm;当PPI修改为160,高度18sp(18px)的物体你高度计算为 160 / 25.4 = 18 / Y,Y = 2.86mm。因此同样SP字号的字体可以在不同设备上显示为相同的物理尺寸。
丨显示效果
以图例来解释,如下所示:
其中,对于文字的显示,在iPhone不同型号的设备上效果差距原理如下:
其中,由物理和像素显示渲染原因导致的显示差异,可以理解为制作工艺上的成像原理不同。针对最初的iPhone1代,渲染像素和显示像素的比例是1:1,当进入到iPhone5的2X时代,其显示比例升级为1:2,为视网膜屏(Retina)的标准。到目前最新的iPhone6 Plus采用了1:3的显示比例,相当于视网膜屏幕的升级品质(Retina HD),但是由于显示的适配问题,iPhone6 Plus做了一个缩放换算(Downscale,即1920 / 2208 = 1080 / 1242 = 20 / 23),这样就会导致即使是标准适配像素(Pixel Perfect)的切图,也会在显示上发虚,效果如下:
因此,对于iOS的字体设计,也应采用文字使用sp的规范,非文字采用dp做适配设计。
那么对于图片呢?先来看看设备的一些参数,如下:
这里要注意的是,iPhone6Plus有两种显示模式,标准模式分辨率为1242x2208,放大模式分辨率为1080x1920,即iPhone 6的1.5倍,因此可以直接用1.5的倍率做等比适配。
丨适配原则
如果将所有分辨率全部缩小为1X,那么各屏幕的显示比例效果如下:
其中可见,不同屏幕的尺寸高宽都不一样,因此屏幕适配不能靠简单缩放完成(可以看到iPhone6之前所有的屏幕宽度都是320Pt,所以绝对定位和绝对缩放,都将对iPhone6以上设备失效!)。如果让资源根据屏幕做非缩放(绝对)适配,那么效果可能会是这样:
而理想的适配效果,应该是根据屏幕变化做排版的比例(相对)缩放,效果应该如下:
丨适配流程
其中:
如果不需要实现3X的适配,只考虑2X适配,则直接按照iPhone6为基准设备,完成标注、输出和制作。
如果需要兼容 iPad 和 iPhone,美术资源的素材制作以上线DPI 264为准,出图以高线出图,由 PS 的 ATN 提前录制动作,完成两套素材输出(3X 和2X)。
根据包体设计方案,如果是需要出多包体工程,需要在资源路径做分离输出。如果是单包体多资源打包方案,则在单工程内完成多资源并列输出。
如果你能理解以上对标准的定义和选择,那么接下来针对素材的选择就相对简单了,无论游戏还是 App,对资源的管理首先是明确对资源的需求。如果你需要对屏幕做完美适配,那么在方案上就需要准备3套(1X、2X 和3X)资源;如果游戏的画面是粗暴的全屏拉伸,或者是加黑边的方式适配,那么资源层的管理就不需要太过精细。
高级篇
游戏和 APP 应用不一样,大部分资源都是图片,前面说这么多,其实就是为了接下来的重点铺垫:如何为产品选择图片的参数。
丨像素优化
常规的参数看似简单,似乎只有尺寸、文件格式、压缩比等,但实际上在设备调用这些资源时,会读取更深层次的细节参数。
场景中需要导入一张图片,先不管他的目的是什么,但首先资源导入的第一步是进入内存,如果这张图片是一张1MB 的 JPG,那么进入内存后是不是也会占用1MB 呢?肯定不是的嘛!那么一张图片到底在手机中消耗多少内存呢?公式如下:
numBytes = width * height * bitsPerPixel / 8
解释下,OpenGL 中对纹理的宽高计算都是2次的幂数进行换算,所以当任意图片导入内存后,都会被按最接近2次冥的值来换算。例如 a.png 尺寸是 500x320,由于500和320介于256和512之间,那么载入后会被按照512X512来计算纹理,并转换为 Bitmap,其中每个像素点使用4个byte来表示,1个byte(8位)表示 red,另外3个byte分别代表green、blue和alpha透明通道,简称为 RGBA8888。那么这张图的内存就需要占用512X512X4=1MB 的内存。
同时2次方在图片本身的制作中也会产生额外的效果,如果在像素放大到一定程度,色彩的分布不是基于色块边缘,如下:
可以看到,由于像素对色彩的不均匀调和,导致边缘会出现模糊的效果,可是同样的图片,稍作调整,如下:
效果完全不一样,如果要修复这样的效果也行,那就需要祭出二次采样的技术,对混合的色块做像素重匹配。
丨内存优化
正常情况下,内存的调用需要根据场景的切换做适当的释放,理想状态下的内存数据应该是这样的。
从上图可以看到,每一次切换转场,内存都需要对当前载入的资源做一次释放,如果不做释放,那么必然逐渐推高内存压力,直到闪退,悲剧的效果大致如下:
丨格式优化
我们在实际中并不需要对所有图都采取 RGBA8888的格式,32位色彩固然丰富,也可以降一个档次使用 RGBA4444的16位图,或者 RGB565格式。同时系统也提供了大量的压缩文件算法,包括 PVRTC4和 PVRTC2,怎么选择就得看具体的需求,这些格式的大致区别如下:
所以从上可知,不同的格式和不同的压缩方式会对图片的质量产生明显的影响。但是在游戏中,不同场景和不同用途对图片的调用方式也不通,例如一些 UI 界面,属于长期甚至长时间被注意的区域,需要图片品质足够高,在一些一闪而过或者比较小的组件中,或者细节不需要太丰富的区域,就不用考虑太高的品质格式,对于不明真相的用户来说,一般肉眼是分辨不出区别的。这对制作就有了一定的要求,需要制作人员在设计界面框架的时候考虑图片的调用规则。
丨图集优化
问题也来了,既然是图片的规格决定了内存的占用,如果我们有20个50X50的 icon,载入内存的时候岂不是需要20个64X64的容量么?相当于一个1280X1280的内存!你会发现这里产生了大量的内存浪费,如果资源量过多,会直接导致 OOM(内存溢出)。因此在完成格式选择后,需要做的下一步就是对图片做图集处理,20个50X50最终可以合并至一个1000X1000的图片,内存中的浪费明显获得控制。
至于制作方法,推荐采用 TexturePacker来完成,下载地址。
丨压缩优化
好,如果我们已经完成了对格式的选择、正确的释放和图集的制作,内存还是占用太高怎么办?这种情况一般来说就是设计的场景调用资源过量,太多的图片或特效资源,终极解决的方案就只有:图片压缩。
这里对图片的压缩一定不是保存图片时选择的品质率,一定需要兼顾视觉效果和压缩比,实现综合的压缩方案。
在前面我们对图片的像素分布已经有了一定认识,因为决定图片尺寸的关键因素就是色彩数量,如下:
如果纯色的区域,色彩的信息量也就最少,那么在分配色彩数量的时候,其实就可以适当减少分配量。所以压缩的第一步就是对色彩做分块,再分别压缩每个色素块,有点类似于动态分配的效果,有个专业的词叫 Intra Prediction,效果如下:
如果按照不同的文件格式和区块划分,那么压缩效果可以简单得到如下参考值(压缩对象是一个纯色色块)
这里我们需要重点关注的格式,是 PNG,因为这也是游戏中用的最多的图片资源,原因是它具备透明通道,可以支撑大量的动画调用。PNG采用无损压缩是通过索引色去存储和还原图像的,在存储图像前会先判断图像上哪些地方是相同的哪些地方是不同的,然后对图像上所有出现的颜色进行索引,这些颜色就是索引色。储存的索引色数量越多,文件尺寸越大。PNG8最多只能索引256种颜色,PNG24则可以保存1600多万种颜色,但相应的文件尺寸也会大很多。
PNG 的压缩方式很多,可以先用 ImageMagick 工具对图片做重采样,将色彩基于像素做二次分配后,保证压缩后的图片依然清晰,不会出现半个像素中有色彩调和的情况。
默认的命令很简单,如下:
convert ${input} -resize 68x -unsharp 0x1+0.3 -filter Lanczos ${out}
然后,借助 pngquant 神器来实现压缩,这是一个开源的压缩库,效果很好
命令行如下:
pngquant -f --speed 1 --ext opt.png ${input}
那么看看压缩效果,以我的一款 SLG 游戏为例,从进入游戏开始,对内存的调用情况做了持续的监控,如下:
其中,橙色的线是没有开始对图片资源做任何优化时的效果,内存占用高达300mb,基本上低端机型半个小时后直接崩溃。蓝色和灰色是在对图片的格式做调整,CocosStudio 制作工艺上做调整,以及图集制作后的内存检测效果,其实可以发现,这几步是有价值的,但是效果不是特别明显。黄色的线条就是对所有的美术资源做了压缩之后的效果,可以明显看到内存消耗基本降低了一半,并且可以长期稳定在200mb 以内。
而产品的 APK 安装包也从橙色线条时的130mb,调整到蓝色灰色线条时的80mb,最终定格在黄色线条时的整包23mb。我对这个结果还是很满意的!
结束语
资源管理的方案没有万能也没有最佳,只有最合适,所以希望通过这篇文章,可以帮助你对图片资源在产品开发中进行合理地优化。同时我也在最近抽时间将压缩方案固化成程序,并加入了更多的优化算法,提升了压缩性能,经测试可以使得一般 PNG 实现75%以上的压缩比!