从初级到高级,游戏美术资源管理经验谈

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%以上的压缩比!

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值