纹理影响着应用程序包的大小、内存占用量、内存带宽,从而影响游戏性能、用户体验以及电耗等,本节将介绍如何对纹理已经优化。
在(Cocos2d-X的纹理(1))一节中,我们讲到了将png、jpg、bmp图片生成Image,将图片的data和type等存入Image,再由Image生成texture,其中jpg等也算是压缩图片。但是这些压缩图片通过GL命令glTexImage2D传入GPU的时候,也需要转换成GPU所认识的format和type,然后传输到GPU,在GPU内存中存为对应的GPU格式。所以这些压缩图片不算压缩纹理,这些图片仅减小了应用程序包,并没有对GPU内存优化带来帮助。
压缩纹理是指在CPU端已经压缩好,并且被GL命令所能识别,传输到GPU端可以直接可以从压缩纹理中进行采样渲染。这样要求压缩纹理具备4个特点:1、解压速度快:使得渲染系统可以直接从压缩纹理中读取数据;2、随机读取:采样的时候是根据纹理坐标去纹理中进行采样,传统压缩技术是使用可变压缩比例,读取某个像素点需要解压很大一部分相关像素,压缩纹理采用固定压缩比例,访问像素是可以根据索引读取一小块内容,精准定位;3、不用太在意压缩质量,使用很高的压缩率,因为以大局为重;4、不用在意编码速度,因为将普通纹理压缩成压缩纹理是在游戏开发中进行。压缩纹理的传输使用GL命令glCompressTexImage2D。
压缩纹理的随机读取的原理是:按照压缩比例将纹理分成多个像素块,对每个像素块进行压缩,每个像素块的像素信息存储在一个像素集合中,对这个像素集合制作一个索引图。采样的时候,将纹理坐标转换为块索引值,以及该像素点在像素块中的偏移,根据块索引值定位到像素块,再在像素块中根据偏移抓去到像素点的信息。像素块的信息还可以根据实际情况进行缓存。
不同的GPU支持不同的压缩纹理格式,Apple公司因为使用PowerVR的GPU,所以对pvr压缩纹理格式百分之百支持,Android手机则对khronos支持的ETC格式比较支持。具体支持的压缩纹理格式,可以通过Cocos2d-X提供的API进行check(底层也是调用了OpenGL ES的API进行GL Extension检查)。在OpenGL ES3.0之后,提供了压缩纹理标准。
PVRTC2开始支持NPOT的纹理,支持2bpp和4bpp和压缩比例,支持Alpha通道。
ETC是一种有损压缩纹理格式,支持4bpp的压缩比例,不支持Alpha通道,针对Alpha通道,可以通过以下2个方式解决:1、将Alpha通道转化为灰度图,关联到原始纹理,使得纹理高度扩大一倍,在fragment shader中多做一次alpha采样即可(在Cocos2d-X需要实现一个自定义的shader),但是有限制,因为纹理高度是有最大值,针对高度在最大值一半以上的纹理不可使用这个办法;2、单独生成一张纹理,利用多重纹理的原理,将两个纹理同时绑定到GL。
虽然压缩纹理已经对游戏进行了优化,但是在游戏过程中使用了大量的纹理,所以我们还需要从纹理的生命周期进行进一步优化。在Cocos2d-X中提出了纹理缓存的概念。纹理缓存的目的是使在当前场景需要使用的纹理保存在内存中,且同一个纹理只需要加载一次(在场景开始的时候进行加载,在(Cocos2d-X的渲染系统(5))一节中,我们介绍了glTexImage2D用来加载图片,可以采取每帧加载一张的方式,调用的就是TextureCache的addImageAsync函数)。TextureCache是单例。
在通过sprite的create方法(传入参数为一个图片)创建sprite的时候,调用TextureCache的addImage方法,如果该图片已经在TextureCache存在,那么就直接返回对应的texture,如果不存在,就实例化一个Image,再实例化一个TexImage(引用计数为1),将TexImage和图片名称(也可以用自定义值)存储在TextureCache的一个unordered_map成员变量中。然后通过sprite的initWithTexture方法将texture赋值给sprite的成员变量,引用计数为2。如果没有TextureCache,当创建texture的那一帧结束的时候,引用计数减1,在sprite消失的时候,texture引用计数减1,当创建texture的那一帧结束,且所有相关的sprite都消失,texture就消失了。且texture不容易被其他类使用。如果使用TextureCache,那么创建的时候引用计数为1,被元素使用加1,元素消失减1。引用计数为1的时候,说明没有被使用,可以通过removeUnusedTextures的方法删除不用的texture,减少内存。如果确定不用,可以使用removeTexture方法release。removeAllTextures对所有texture进行release。sprite的createWithTexture不会将texture 加入TextureCache
纹理缓存只是提供了这些方法,还需要是开发者在上层通过引用计数来对纹理进行控制。缓存机制用于处理资源的创建、缓存、删除、共享,上层的引用计数机制用来管理多个场景之间资源过度和共享。
比如:在一个场景中,使用TextureCache控制该场景中所有的texture(1、2、3),在场景结束的时候,所有的元素都消失,所有的texture引用计数都为1,如果使用removeAllTextures,那么所有的texture都被删除,假如下一个场景,还会用到(2、3、4)的texture,那么就造成浪费。可以引入了上层引用计数机制。在进入一个场景的时候,对这个场景中所用到的资源引用计数加1,对上一个场景用到的资源引用计数减1,删除引用计数为0的资源,将引用计数为1的资源进行加载。大的游戏不可能在场景开始的时候加载所有资源,那么就要根据游戏的进度进行逐步加载。
VolatileTextureMgr记录应用程序正在使用的纹理信息,包括路径、数据等纹理信息,如果游戏重启等,OpenGL ES context被重新创建,可以通过VolatileTextureMgr进行纹理恢复。VolatileTextureMgr会占用一些内存。
比如100*100的图片,格式为RGBA8888,那么占得内存大小为100*100*32/8=4W byte。需要考虑多级纹理。
更好的优化纹理,需要硬件层面进行提升(加载、内存、特殊压缩纹理格式的支持),软件方面(提前加载纹理、减少不用纹理的内存占用、将小纹理合并成大纹理以减少绘制次数、使用多级纹理、使用多重纹理减少绘制次数、使用alpha预乘),资源方面(使用适当的纹理格式、使用压缩纹理、设置正确的defalutAlphaPixelFormat)
笔者制作网站的目的,主要是借用自己之前的知识背景(Android App开发和图形学知识),将自己学习笔记拿出来,和大家一起进行交流,毕竟每个人的知识体系不同,有交流才会有提高,所以欢迎大家通过各种方式和我联系。
网址:www.geekfaner.com
youku教学视频:http://i.youku.com/geekfaner
“百度传课”教学视频(高清版_推荐):http://www.chuanke.com/s2588605.html