|
文/congcong009
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。
一般来说,常用位数有:
- 单色:非黑即白,就两色。
- 2位:4种颜色,又称为 CGA。
- 4位:16种颜色,用于 CGA、EGA 及 VGA。
- 8位(灰阶):介于黑、灰、白色之间,有256个层次。
- 16位彩色(高彩色):基本上是早期电脑显示中常用的色彩,为了性能没必要选24位,直接使用16即可。在16位彩色中,每种原色(RGB)有2^6=64个,共有65536个颜色。
- 24位彩色(真彩色):每种原色都有256个层次,所以总共有 256*256*256 种颜色。
- 32位彩色:除了24位彩色的颜色外,还有额外的8位是储存重叠图层的色彩,也就是 Alpha(透明)频道,我就算了下2的32次方,等于4294967296。
更高级的还有工业中常用的高动态范围影像(High Dynamic Range Image,简称 HDR),这种格式的文件将使用超过一般的256色阶来储存影像,也就是说每一个原色都使用一个32位来存储,所以色域空间更广,通常能记录肉眼在屏幕中无法查看的色彩。如果在 Blender 中打开一个 exr 文件,效果如下。
可以看到 Waveform 图中色彩的波段,如果将其转换成 JPG 这样的24位图,再看看效果:
由于 JPG 是有损压缩,所以肉眼无法看到的色彩都是可以被抛弃的部分。这时看看直方图,是不是发现其实还有很多色彩是被腰斩了?而画面上其实很难察觉区别,是不是这些超出肉眼识别(显示器显示范围)的色彩无用或者无法利用呢?其实只要采用合理的曲线或色阶工具,就可以将所有的色彩收敛到肉眼可看到的色域空间内,效果如下:
之前有做过一个视频教程专门讲这块,因为我用英文讲的,所以得翻墙《Color correction in Blender ( waveform & vectorscope monitor ) 》。
可是,图片中的色彩又不是什么超频谱的射线,为什么我们在屏幕上看不到?难道是我们的人眼有 bug?其实不是我们的错,一张图解毒:
如上示意,我们的肉眼其实开挂的,能将色彩信息传递到大脑并感知的数量是远超过32位,不过我没去查过到底多少位,但这个命题应该等价于『人眼可以识别多少个颜色的数量』,真正的问题,其实是出在显示器上。
丨色彩空间
又叫『色域』,可以理解为用来建立色彩感知体系的模型,常见的包括:
- RGB(red + green + blue)也是最常用的色域空间,常用在显示器配置中,如果从 CRT 的硬件原理上讲,那就是使用R、G、B数值来驱动R、G、B 电子枪发射电子,并分别激发荧光屏上的R、G、B三种颜色的荧光粉发出不同亮度的光线(LED 又是偏反射显示模式,这里不展开了),再通过相加混合产生各种颜色。所以问题来了,如果是不同型号的显示器,他们的硬件设计或参数标准没有统一的话,在显示同一幅图像时,就会产生不同的色彩显示结果。所以评测硬件产品的时候都喜欢把不同厂家的色彩放一起来对比,如下,你看出差别了么?。
- CMYK(cyan + magenta + yellow)常应用于印刷工业,大学没毕业的时候曾经在前程无忧干过3个月的报刊印刷,所以对个 CMYK 印象十分深刻,当时交付的时候会出三个版面:黑白、彩色和套红。印刷厂将通过青(C)、品(M)、黄(Y)三原色油墨的不同网点面积率,利用叠印的方式来表现丰富多彩的颜色和阶调,那还有个 K 呢?其实是黑(BK)色印刷。
- HSV(hue + saturation + value)颜色空间在绘画中会有应用,分别指代色相、饱和度和明度,因为可以指导配色的时候调整色彩的浓度明暗。
- 其他还有很多,例如 HSL(hue + saturation + lightness)、HSB(hue + saturation + brightness)、Ycc、XYZ、Lab等等。
打开你的色彩配置参数,可以看到类似下面的配置:
这里三角形描绘的就是当前屏幕可展示的色域范围,如果要做比较,可以把多色域的图叠加起来,效果如下:
明显可以看到苹果了100% 的 sRGB 色域标准,无论是 Mac 还是 iPhone,所以苹果产品的屏幕显示在早期一直都是最接近设计需求的屏幕,不是因为他的硬件本身多牛逼,而是它对色彩的定义牛逼。后来小米出来,说他们实现了90%以上的 NTSC 色域,苹果你就是个渣,可是评价一块屏幕,真不能只是看色域,还有亮度、对比度、响应时间、色彩偏离度(ΔE)、子像素排布方式等等。所以我不是小米黑,但我确实是苹果粉。
丨线性空间
继续翻看色彩管理配置,可以发现下面参数:
如果熟悉 photoshop 的曲线工具,就会记得如下效果:
按照曲线中横纵坐标对平滑度的定义,色彩是一个线性的渐变过程,通过对曲率的调整可以对色彩的明亮等做二次分配,可为什么我们的显示器会采取曲线来显示色彩的渐进呢?那这个关键参数就是 Gamma(伽马)值。
网图,用来解释这几个概念最好了,如下:
这里有3个指标很重要,显示(屏幕)色彩曲线,线性曲线和 gamma 曲线,他们分别代表了不同场景中对色彩的输出。我们的人眼其实是线性的色彩系统,当然我们的大脑会默认看到的都是线性的,因为人眼没有其他标杆参考,所见即所得。可是显示器在生产的过程中,由于交流输入电压本身的曲线特性,其输出亮度的关系一定无法实现线性的(不要问为什么不能用直流电来制造显示器)。比如输入0.5的电压,则会输出大约0.2的亮度,要想获得0.5的亮度,则必须输入0.73。在图上用曲线来理解的话,红色的线条其实就是屏幕的实际色彩空间曲线,为了让我们的人眼可以看到最接近真实的图片色彩,或者是希望打印效果和屏幕效果是一致的(因为打印出来后,对色彩的判断只有人眼了,而电脑中的图片需要屏幕做一次中转),就需要将屏幕上看到的色彩校正至线性,也就是称为的 Linear 线性空间。方法很粗暴,那就是绘制一条反向的曲线,将两者叠加,就能获得一条线性色彩空间了。而这个反向曲线就是 Gamma 曲线。
所以在制作的过程中,为了保证输入和输出一致,理想和现实最接近,就需要考虑是否需要在每一个增加色彩的环节执行线性色域转换,例如3D 中对材质和贴图是否需要额外的线性工作流程。
中级篇
理解了基本概念,接下来就说说移动设备相关。需要先普及几个概念,列一下:
丨设备概念
- in:英寸,常用来描述屏幕物理尺寸,如4寸屏,1in = 72pt=2.54cm。
- px:pixer 像素,设备显示的最小单位,1px = 1/96in。
- pt:point,印刷名词为磅,标准长度单位,1pt = 1/72in,那么 px 和 pt 的转换公式为 px = pt * dpi /720。在默认windows下,文字被定义为96dpi,则1px = 0.75pt,宋体9pt = 12px,当改变dpi至144后,如1px = 0.5px,那么此时宋体9pt = 18px。
- dpi:dots per inch 打印分辨率 (每英寸所能打印的点数,即打印精度,主要应用于输出,重点是打印设备上,也即是单pt上的px数量)。
- ppi:pixels per inch图像分辨率 (在图像中,每英寸所包含的像素数目),换算公式为 PPI = √(长度像素数² + 宽度像素数²) / 屏幕对角线英寸数。例如 iPhone5 的 ppi =√(1136px² + 640px²)/4 in=326ppi(视网膜 Retina 屏)。
- dp:density-independent pixels 设备独立像素,根据安卓设计规范,非文字需要使用dp。以160PPI屏幕为标准,则1dp=1px,dp和px的换算公式:dp*ppi/160 = px。例如1dp x 320ppi/160 = 2px。
- sp:scale-independent pixels,根据安卓设计规范,一切文字需要使用sp。以160PPI屏幕为标准,当字体大小为 100%时, 1sp=1px。sp 与 px 的换算公式:sp*ppi/160 = px。
那么,当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以上设备失效!)。如果让资源根据屏幕做非缩放(绝对)适配,那么效果可能会是这样:
而理想的适配效果,应该是根据屏幕变化做排版的比例(相对)缩放,效果应该如下:
丨适配流程
- 要让同一套尺寸在制作完成后能适配3个比例,资源的制作基本原则是:
- 选择一种尺寸作为设计和开发基准;
- 定义一套适配规则,自动适配剩下两种尺寸;
- 特殊适配效果给出设计效果。
- 因此在设计和后期切片制作上,需要考虑适当的适配衔接,流程如下(前提是需要做3X的适配):
其中:
- 视觉设计阶段,UI设计师按宽度750px(iPhone 6)做设计稿,除图片外所有设计元素用矢量路径来做。采用中间比例来设计的好处是,在向上向下适配后,可减少元素之间比例的误差量,如果采用414pt为基准,很可能造成适配到320pt时,变化和失真幅度增大。设计定稿后在750px的设计稿上做标注,输出标注图。同时等比放大5倍生成宽度1125px的设计稿,在1125px的稿子里切图。
- 输出两个交付物给开发:一个是程序用到的3x切图资源,另一个是宽度750px的设计标注图,开发在CocosStudio中使用对应资源拼接UI,你用其他工具原理类似。
- 开发拿到750px标注图和3x切图资源,完成iPhone 6(375pt)的界面开发。针对App开发,此阶段不能用固定宽度的方式开发界面,得用自动布局(auto layout),方便后续适配到其它尺寸。针对CocosStudio,推荐1版本的锚点自适应方式,如果是低版本,尽量将所有部件封装成Node,坐标置中,次级Layer制作时封装为相对左下(0,0)坐标,这样在最终Scene调用时可直接匹配(0,0)原点,简化对齐和更新后对相对位置的修改工作量。我就不补图了,懂得人自然看得懂。
- 适配调试阶段,基于iPhone 6的界面效果,分别向上向下调试iPhone 6 plus(414pt)和iPhone 5S及以下(320pt)的界面效果。由此完成大中小三屏适配。
如果不需要实现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,怎么选择就得看具体的需求,这些格式的大致区别如下:
- ALPHA8:此时图片只有alpha值,没有RGB值,一个像素占用一个字节
- ARGB4444:一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个byte,共16个byte,即2个字节。
- ARGB8888:一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个byte,共32byte,即4个字节,这是一种高质量的图片格式,电脑上普通采用的格式,也是 Android 手机上一个 BitMap 的默认格式。
- RGB565:一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个byte ,Green(G)值占6个byte ,Blue(B)值占5个byte,共16byte,即2个字节.对于没有透明和半透明颜色的图片来说,该格式的图片能够达到比较的呈现效果,相对于ARGB8888来说也能减少一半的内存开销。因此它是一个不错的选择。
所以从上可知,不同的格式和不同的压缩方式会对图片的质量产生明显的影响。但是在游戏中,不同场景和不同用途对图片的调用方式也不通,例如一些 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%以上的压缩比!
来源:Blender
原地址:http://www.blenderget.com/how-to-optimize-your-game-asset/
|
|