最近在做Unity的内存优化,代码几乎没有可以优化的地方了,那么只有从图片素材进行。
在Unity里渲染的大小分别是:
442 X 563 = 0.9MB
512 X 1024 = 0.5MB
512 X 512 = 256KB
256 X 256 = 64kB
256 X 563 = 0.5MB
442 X 256 = 442KB
看出来区别了吧!无论怎么比较,就是 2次方的正方形最优!
在项目中,尽可能是使用ETC1和PVRTV4等GPU直接支持的图片格式,不仅内存占用低、性能也更好;当出现质量不及格时,再逐步的提升压缩格式,来满足需要。
RGB16
2、如果我的图片不是2次方的正方形,能做得到吗?
而RGB16,是主要针对一些,不带透明通道,同时长宽又不是2的次方的图片;对于这些图片,使用RGB16可以降低一半的内存,但是效果会略逊于RGB32。
几种纹理格式的对比?
格式 .................. 内存占用 ..........质量 .........透明 ....... 二次方大小 ..........建议使用场合
RGBA32 ..... 1 .....★★★★★ .....有 .....无需 .....清晰度要求极高
RGBA16+Dithering .....1/2 .....★★★★ .....有 .....无需 .....UI、头像、卡牌、不会进行拉伸放大
RGBA16 .....1/2 .....★★★ .....有 .....无需 .....UI、头像、卡牌,不带渐变,颜色不丰富,需要拉伸放大
RGB16+Dithering .....1/2 .....★★★★ .....无 .....无需 .....UI、头像、卡牌、不透明、不会进行拉伸放大
RGB16 .....1/2 .....★★★ .....无 .....无需 .....UI、头像、卡牌、不透明、不渐变,不会进行拉伸放大
RGB(ETC1) + Alpha(ETC1) .....1/4 .....★★★ .....有 .....需要二次方,长宽可不一样 尽可能默认使用,在质量不满足时再考虑使用上边的格式
RGB(ETC1) .....1/8 .....★★★ .....无 .....需要二次方,长宽可不一样 .....尽可能默认使用,在质量不满足时再考虑使用上边的格式
PVRTC4 .....1/8 .....★★ .....无 .....需要二次方正方形,长宽一样 尽可能默认使用,在质量不满足时再考虑使用上边的格式
内存占用,相对于RGBA32做比较
认识TexturePacker的界面:
Data Format:
导出什么引擎数据,默认cocos2d,下拉列表中有很多,基本常用的引擎都支持了
Data File :
导出文件位置(后缀名.plist)
Texture Format:
纹理格式,默认png
Image format:
图片像素格式,默认RGBA8888...根据对图片质量的需求导出不同的格式
Dithering:
抖动,默认NearestNeighbour,(如果图像上面有许许多多的“条条”和颜色梯度变化)将其修改成FloydSteinberg+Alpha;
Scale:
让你可以保存一个比原始图片尺寸要大一点、或者小一点的spritesheet。比如,如果你想在spritesheet中加载“@2x"的图片(也即为Retina-display设备或者ipad创建的)。但是你同时也想为不支持高清显示的iphone和touch制作spritesheet,这时候只需要设置scale为 1.0,同时勾选autoSD就可以了。也就是说,只需要美工提供高清显示的图片,用这个软件可以自己为你生成高清和普清的图片。
Algorithm TexturePacker:
里面目前唯一支持的算法就是MaxRects,即按精灵尺寸大小排列,但是这个算法效果非常好,因此你不用管它。
Border/shape padding:
即在spritesheet里面,设置精灵与精灵之间的间隔。如果你在你的游戏当中看到精灵的旁边有一些“杂图”的时候,你就可以增加这个精灵之间的间隔。
Extrude:
精灵边界的重复像素个数. 这个与间隔是相对应的--如果你在你的精灵的边边上看到一些透明的小点点,你就可以通过把这个值设设置大一点。
Trim:
通过移除精灵四周的透明区域使之更好地放在spritesheet中去。不要担心,这些透明的区域仅仅是为了使spritesheet里面的精灵紧凑一点。--当你从cocos2d里面去读取这些精灵的时候,这些透明区域仍然在寻里。(因为,有些情况下,你可能需要这些信息来确定精灵的位置)
Shape outlines:
把这个选项打开,那么就能看到精灵的边边。这在调试的时候非常有用。
AddSprite:
添加图片Add Folder:根据文件夹添加图片
Publish:
导出资源文件(.plist和png)
补充一下:
针对大图往往是许多小图合成的图集 Atlas,且 GPU 在处理图片是总是需要 POT (Power of Two)大小的纹理这两个特性。另一种处理方式是把小图挤到半张 Atlas 里面,另外半张放透明度信息。
这样处理, iOS 平台下的开销也就不会太大。其实,由于小图往往长宽比不规则,很多原本需要一张大图的Atlas完全可以放在半张图里面。
本文主要是介绍后面的处理方式。当然,也就讲个思路。先把图片排到半张图得到半图,再利用 Mali 的 TCT 工具获得所需的全图。最后写一个 Shader 来得到完整的图片信息。
Unity 相关知识点
Unity 对于平台不支持的压缩格式,会默认转为 RGBA 32bpp。而 Android 平台普遍支持的含透明度格式为 RGBA 16bpp。如果采用 RGB ETC 4bpp 的两幅图,那么需要 8bpp(使用该格式可能会导致遮罩出现问题)。如果能够把两幅图放在同一张纹理里面,那么能够再节省一半,大概4bpp(shader处理的时候会比较消耗GPU)。
Unity 在为 Android 打包时,默认对JPG采用 ETC1,对PNG采用 RGBA 16。
NPOT 的图最终会被转为 POT 的图,而且 Unity 会把 NPOT 的图会被转为 RGBA 32 格式 (GUI Texture 支持 NPOT,图片格式不会改,但是最终送 GPU 的时候还是会转为 POT)。
几种主要的纹理格式
- DXT
DXT 是 DirectX 提供的一种压缩格式。只能针对 POT 格式纹理进行处理。DDS 文件采用此种压缩方式进行文件存储。
支持的纹理格式 占用空间
DXT1 RGB5A1 4bpp 压缩比 4:1
DXT2 RGBA4444 8bpp 压缩比 2:1
DXT3 RGBA4444 同上 同上
DXT4 通过线性插值生成 Alpha 同上 同上
DXT5 同上 同上 同上
DXT纹理压缩
- ETC
ETC1 (Ericsson Texture Compression) 仅仅支持 RGB 4bpp 的图,不支持 Alpha 通道。OpenGL ES 3.0 能支持 ETC2,但是 Android 4.3 才开始支持 GLES 3.0。
ETC1 采用 4X4 的像素区域编入64位空间,也就是 4bpp。
ETC 将像素区域分为4X2(2X4)两个部分。每个部分有一个基色,在基色基础上给两个部分分别 444RGB 的偏移或者 555RGB/333RGB的偏移。每个部分还有3位的亮度选择。 Each pixel is then offset from the base color by adding one of four signed values to the base color for its half of the 4×4 group.
关于ETC2的官方介绍ETC 纹理压缩和 Alpha 通道处理ETC 拼接图 Shader 的编码 - PVRTC
PVRTC 分 4bpp 和 8bpp。具体的编码方式参见 Wiki 。
代码优化:
架构设计优化
下面看看对比:(MB 的变化)
// LoadResources 和 Instance 的对比
// 协程
IEnumerator LoadResources()
{
//清除干净以免影响测试结果
Resources.UnloadUnusedAssets ();
// 5s 秒后看结果 (72.8MB)
yield return new WaitForSeconds (5.0f);
// 通过 Resource.load 加载资源 (72.8MB)
GameObject tank = Resources.Load("Role/Tank")as GameObject;
yield return new WaitForSeconds (0.5f);
// Instantiate 一个资源出来 (75.3MB)
GameObject tankInst = GameObject.Instantiate(tank,Vector3.zero,Quaternion.identity) as GameObject;
yield return new WaitForSeconds (0.5f);
// Destory 一个资源 (没有立刻释放,存在镜像缓存,事实上内存变化非常小)(74.7MB)
GameObject.Destroy(tankInst);
yield return new WaitForSeconds (0.5f);
// 释放无用资源 (72.3MB)
tank = null;
Resources.UnloadUnusedAssets (); // 这个是内存 的释放是完美的
yield return new WaitForSeconds (0.5f);
}
以 AssetBundle.Load 和 Instance 优化对比
IEnumerator LoadAssets()
{
// 清除干净以免影响测试结果 (59.9MB)
Resources.UnloadUnusedAssets();
// 等待 5 秒以看到效果
yield return new WaitForSeconds(5.0f);
// 创建一个 www类 (62.0MB)
WWW bundle = new WWW(path);
yield return bundle;
yield return new WaitForSeconds (0.5f);
// AssetBundle.Load 一个资源 (64.5MB)
Object obj = bundle.assetBundle.Load("tank");
yield return new WaitForSeconds (0.5f);
// Instantiate 一个资源出来 (65.6MB)
GameObject tankInst = Instantiate(obj) as GameObject;
yield return new WaitForSeconds (0.5f);
// Destory 一个资源 (63.9MB)
GameObject.Destroy(tankInst);
yield return new WaitForSeconds (0.5f);
// Unload Resources (63.7MB)
bundle.assetBundle.Unload(false); // 支持映射
yield return new WaitForSeconds(0.5f);
// 释放无用资源 (61.8MB)
// obj = null;
// Resources.UnloadUnusedAssets();
yield return new WaitForSeconds (0.5f);
}
通过静态绑定的方法 来 Instantiate 一个资源
IEnumerator InstResources()
{
// 62.0MB
Resources.UnloadUnusedAssets ();
yield return new WaitForSeconds (5.0f);
// 64.4MB
GameObject tank = Resources.Load("Role/Tank")as GameObject;
GameObject inst = GameObject.Instantiate (tank,Vector3.zero,Quaternion.identity)as GameObject;
yield return new WaitForSeconds (1f);
// 64.0MB
GameObject.Destroy (inst);
yield return new WaitForSeconds (1f);
//释放无用资源 (6.23MB)
tank = null;
Resources.UnloadUnusedAssets ();
yield return new WaitForSeconds (1f);
}
总结: