unity纹理压缩方案汇总(持续更新)(转几篇文章 原文链接在文章开头 2019-09-01))

参考链接(总结还是比较全):https://blog.csdn.net/goodai007/article/details/52679333
参考链接(Dither算法进阶):https://zhuanlan.zhihu.com/p/28624490
参考链接(Unity加载模块解析(纹理篇)):https://blog.uwa4d.com/archives/LoadingPerformance_Texture.html
后续有新思路 持续更新

总结一下 查看的时候方便 也防止原链接失效(如有需求 请去原链接支持)

文章包括:
1.纹理压缩策略
2.图片优化神器 - Dither算法进阶方案(附代码)
3.图片alpha分离(TODO 待整理)
4.纹理深度解析 一些测试

**

纹理压缩策略

**
Unity3D引擎对纹理的处理是智能的:不论你放入的是PNG,PSD还是TGA,它们都会被自动转换成Unity自己的Texture2D格式。
在Texture2D的设置选项中,你可以针对不同的平台,设置不同的压缩格式,
IOS设置成PVRTC4
Android平台设置成RGBA16等

手游开发(Android/IOS)中,我会使用3个级别的压缩程度:高清晰无压缩、中清晰中压缩、低清晰高压缩;4种压缩方法:RGBA32, RGBA16+Dithering,ETC1+Alpha,PVRTC4。一般足够应付大部分的需求了。

  • 高清晰无压缩 - RGBA32

    Unity RGBA32 - 高清晰无压缩.png

    RGBA32等同于原图了,优点是清晰、与原图一致,缺点是内存占用十分大;对于一些美术要求最好清晰度的图片,是首选。

    要注意一些png图片,在硬盘中占用几KB,怎么在Unity中显示却变大?因为Unity显示的是Texture大小,是实际运行时占用内存的大小,而png却是一种压缩显示格式;可以这样理解,png类似于zip格式,是一个压缩文件,只不过在运行时会自动解压解析罢了。

  • 中清晰中压缩 - RGBA16 + Dithering

    Unity RGBA16,不抖动处理的渐变图片惨不忍睹

    既然叫RGBA16,自然就是RGBA32的阉割版。

    对于一些采用渐变的图片,从RGBA32转换成RGBA16,能明显的看出颜色的层叠变化

    采用Floyd Steinberg抖动处理后,除非放大,否则肉眼基本看不出区别

    RGBA16的优点: 内存占用是RGBA32的1/2;搭配上Dithering抖动,在原尺寸下看清晰度一模一样;

    缺点: Unity原生不支持Dithering抖动,需要自己做工具对图片做处理;对于需要放大、拉伸的图片,Dithering抖动的支持不好,会有非常明显的颗粒感。

    如何进行Dithering抖动?

    Texture Packer工具中Image Format选择RGBA4444,Dithering选择FloydSteinberg

    在我的项目中,TexturePacker具有非常重要的作用,像UI的图集生成,预先生成好正方形的IOS PVRTC4图集和非正方形的Android ETC1图集、 缩放原图50%等工作都由TexturePacker完成。

    同样,对图像进行抖动处理,也是预先在TexturePacker使用FloydSteinberg算法进行图像抖动,再在Unity中导入使用。

    TexturePacker提供命令行工具,可以做成自动化的工具。

  • RGB16

    RGB16,是主要针对一些,不带透明通道,同时长宽又不是2的次方的图片;对于这些图片,使用RGB16可以降低一半的内存,但是效果会略逊于RGB32。

    当然了,RGB16其实也是可以搭配抖动,也能提升显示效果;但同样的Dithering抖动对拉伸放大是不友好的。

  • 低清晰高压缩 - ETC1+Alpha/PVRTC4

    很多初学者都会疑惑,为什么游戏开发中经常看到一些图片,需要设置成2的次方?因为像ETC1、PVRTC4等这类在内存中无需解压、直接被GPU支持的格式,占用内存极低,而且性能效率也是最好的。

    但是,相对RGBA32,还是能肉眼看出质量有所下降的。

  • ETC1

    ETC1+Alpha一般应用在Android版的UI图集中,ETC1不带透明通道,所以需要外挂一张同样是ETC1格式的Alpha通道图。方法是,在原RGBA32的原图中,提取RGB生成第一张ETC1,再提取A通道,填充另一张ETC1的R通道;游戏运行时,Shader将两张ETC1图片进行混合。

    要配合ETC1+Alpha,还需要Shader支持,这里提供参考直接修改NGUI的Unlit/Transparent With Colored的Shader。

    生成Alpha通道图的方法可参考(后续整理到帖子):http://blog.csdn.net/u010153703/article/details/45502895

  • PVRTC4

    PVRTC4在Unity中是直接支持的,不过要注意的细节是,它必须是二次方正方形;也就是说,长宽在二次方的同时,还必须要相等。

  • 几种纹理格式的对比
    在这里插入图片描述

原作者感受:

在项目中,尽可能是使用ETC1和PVRTV4等GPU直接支持的图片格式,不仅内存占用低、性能也更好;当出现质量不及格时,再逐步的提升压缩格式,来满足需要。

因此,实际项目中要混搭各种纹理格式。

**

Dither算法进阶方案

**
在Unity移动平台的游戏开发过程中,贴图资源是往往是占资源量最大的资源。如何在保证视觉效果的同时,尽可能的减少贴图资源,是开发团队会经常遇到的问题。通常来说,对于3D物体的纹理,是可以采用ETC/PVRTC等压缩比很大的算法处理的,但是对于细节要求很高的UI纹理,这样处理造成的失真往往达不到质量要求。对于这类的贴图,我们可以考虑使用失真较小的16位的贴图格式存储。

但是对于颜色数较高的纹理,Unity提供的默认转换方法会呈现明显的色带。针对该问题,keijiro实现了一种dither4444的改进算法。从图1上可以看到,对于画面细节比较平滑的图片,该算法虽然消除了色带现象,同时带来了肉眼可见的噪点。

笔者在keijiro的算法基础上进行了改进,提供了一个将RGB24bit图dither之后转RGB565的方法,基本消除了肉眼可见的失真

实际在项目的应用中,对于不适合ETC/PVRTC压缩的图片,都采用了该文章中的RGB565或者RGB565+A8的方式。在肉眼基本无失真的基础上,节省了部分资源。
在这里插入图片描述

附上OnPostprocessTexture代码(感谢原作者):

void OnPostprocessTexture (Texture2D texture)
{
          if(assetPath.Contains ("_dither565"))
          {
                   var texw = texture.width;
                   var texh = texture.height;

                   var pixels = texture.GetPixels ();
                   var offs = 0;

                   var k1Per31 = 1.0f / 31.0f;

                   var k1Per32 = 1.0f / 32.0f;
                   var k5Per32 = 5.0f / 32.0f;
                   var k11Per32 = 11.0f / 32.0f;
                   var k15Per32 = 15.0f / 32.0f;

                   var k1Per63 = 1.0f / 63.0f;

                   var k3Per64 = 3.0f / 64.0f;
                   var k11Per64 = 11.0f / 64.0f;
                   var k21Per64 = 21.0f / 64.0f;
                   var k29Per64 = 29.0f / 64.0f;

                   var k_r = 32; //R&B压缩到5位,所以取2的5次方
                   var k_g = 64; //G压缩到6位,所以取2的6次方

                   for(var y = 0; y < texh; y++){
                             for(var x = 0; x < texw; x++){
                                      float r = pixels [offs].r;
                                      float g = pixels [offs].g;
                                      float b = pixels [offs].b;

                                      var r2 = Mathf.Clamp01 (Mathf.Floor (r * k_r) * k1Per31);
                                      var g2 = Mathf.Clamp01 (Mathf.Floor (g * k_g) * k1Per63);
                                      var b2 = Mathf.Clamp01 (Mathf.Floor (b * k_r) * k1Per31);

                                      var re = r - r2;
                                      var ge = g - g2;
                                      var be = b - b2;

                                      var n1 = offs + 1;
                                      var n2 = offs + texw - 1;
                                      var n3 = offs + texw;
                                      var n4 = offs + texw + 1;

                                      if(x < texw - 1){
                                                pixels [n1].r += re * k15Per32;
                                                pixels [n1].g += ge * k29Per64;
                                                pixels [n1].b += be * k15Per32;
                                      }

                                      if(y < texh - 1){
                                                pixels [n3].r += re * k11Per32;
                                                pixels [n3].g += ge * k21Per64;
                                                pixels [n3].b += be * k11Per32;

                                                if(x > 0){
                                                          pixels [n2].r += re * k5Per32;
                                                          pixels [n2].g += ge * k11Per64;
                                                          pixels [n2].b += be * k5Per32;
                                                }

                                                if(x < texw - 1){
                                                          pixels [n4].r += re * k1Per32;
                                                          pixels [n4].g += ge * k3Per64;
                                                          pixels [n4].b += be * k1Per32;
                                                }
                                      }

                                      pixels [offs].r = r2;
                                      pixels [offs].g = g2;
                                      pixels [offs].b = b2;

                                      offs++;
                             }
                   }

                   texture.SetPixels (pixels);
                   EditorUtility.CompressTexture (texture, TextureFormat.RGB565, TextureCompressionQuality.Best);
          }
}

**

纹理深度解析 测试

**

资源加载是加载模块中最为耗时的部分,其CPU开销在Unity引擎中主要体现在Loading.UpdatePreloading和Loading.ReadObject两项中,相信经常查看Profiler的朋友对这两项肯定毫不陌生了。

Loading.UpdatePreloading,这一项仅在调用类似LoadLevel(Async)的接口处出现,主要负责卸载当前场景的资源,并且加载下一场景中的相关资源和序列化信息等。下一场景中,自身所拥有的GameObject和资源越多,其加载开销越大。

在很多项目中,存在另外一种加载方式,即场景为空场景,绝大部分资源和GameObject都是通过OnLevelWasLoaded回调函数中进行加载、实例化和拼合的。对于这种情况,Loading.UpdatePreloading的加载开销会很小。

  • 资源加载性能测试代码

    以下为我们测试时所使用的测试代码,我们将每种资源均制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到不同硬件设备上的资源加载性能比较。
    在这里插入图片描述

  • 纹理资源

    纹理资源是项目加载过程中开销占用最大的资源之一,其加载效率由其自身大小决定。目前,决定纹理资源大小的因素主要有三种:分辨率、格式和Mipmap是否开启。

    1. 分辨率 & 格式

      分辨率和格式是影响纹理资源加载效率的重要因素,因为这两项的设置对纹理资源的大小影响很大。因此,我们对这两种因素进行了详细的测试:

      测试1:相同格式、不同分辨率的加载效率测试

      我们选取了两张分辨率为2048x2048的普通纹理资源,并在打成AssetBundle时,将其分辨率最大值分别设置为512x512、1024x1024和2048x2048,纹理格式均设置为ETC1(Android)和PVRTC(iOS)、且关闭Mipmap功能。所以,三组纹理的内存占用分别为256KB、1MB和4MB,其对应AssetBundle大小为156KB、531KB和1.92MB(对于Android平台)、175KB、628KB和2.4MB(对于iOS平台)。Unity 版本为5.2,压缩格式为默认的LZMA压缩。
      在这里插入图片描述

      1、纹理资源的分辨率对加载性能影响较大,分辨率越高,其加载越为耗时。设备性能越差,其耗时差别越为明显;
      2、设备越好,加载效率确实越高。但是,对于硬件支持纹理(ETC1/PVRTC)来说,中高端设备的加载效率差别已经很小,比如图中的红米Note2和三星S6设备,差别已经很不明显。

      测试2:不同格式、相同分辨率的加载效率测试

      我们选取了两张分辨率为1024x1024的普通纹理资源,并在打成AssetBundle时,根据不同平台将其纹理格式分别设置不同格式用于打包。对于Android平台,我们使用ETC1、ETC2、RGBA16和RGBA32四种格式,对于iOS平台,我们使用PVRTC 4BPP、RGBA16和RGBA32三种格式,同时,对于每张纹理均关闭Mipmap功能。所以,三组纹理的内存占用分别为1MB、1MB、4MB 和 8MB(Android平台)/1MB、4MB 和 8MB(iOS平台)。

      Andriod:在这里插入图片描述
      ios:
      在这里插入图片描述

      1、纹理资源的格式对加载性能影响同样较大,Android平台上,ETC1和ETC2的加载效率最高。同样,iOS平台上,PVRTC 4BPP的加载效率最高。
      2、RGBA16格式纹理的加载效率同样很高,与RGBA32格式相比,其加载效率与ETC1/PVRTC非常接近,并且设备越好,加载开销差别越不明显;
      3、RGBA32格式纹理的加载效率受硬件设备的性能影响较大,ETC/PVRTC/RGBA16受硬件设备的影响较低。

      注意事项:这里需要指出的是测试中所使用的ETC1和ETC2纹理均为RGB 4Bit格式,所以对于半透明纹理贴图,需要两张ETC1格式的纹理进行支持(一张RGB通道,一张Alpha通道)。逐一加载两张ETC1格式的纹理,其加载效率要低于RGBA16格式,但可以通过加载方式来进行弥补。这一点我们将在后续文章中进行详细说明。

    2. 开启Mipmap功能

      开启Mipmap功能同样会增大一部分纹理大小,一般来说,其内存会增加至原始大小的1.33倍。因此,我们对开启Mipmap功能前后的加载性能进行了详细的测试:

      测试3:开启/关闭Mipmap功能的加载效率测试

      我们仍然使用两张分辨率为1024x1024的普通纹理资源,分别使用ETC1格式、PVRTC格式、RGBA16格式和RGBA32格式(测试所用纹理与测试2相同),并在打成AssetBundle时,一组开启Mipmap功能,一组关闭Mipmap功能。

      Android:
      在这里插入图片描述
      在这里插入图片描述
      IOS:
      在这里插入图片描述
      在这里插入图片描述
      通过上述测试,我们可以看出:开启Mipmap功能会导致资源加载更为耗时,且设备性能越差,其加载效率影响越大。

      1、严格控制RGBA32和ARGB32纹理的使用,在保证视觉效果的前提下,尽可能采用“够用就好”的原则,降低纹理资源的分辨率,以及使用硬件支持的纹理格式。
      2、在硬件格式(ETC、PVRTC)无法满足视觉效果时,RGBA16格式是一种较为理想的折中选择,既可以增加视觉效果,又可以保持较低的加载耗时。
      3、严格检查纹理资源的Mipmap功能,特别注意UI纹理的Mipmap是否开启。在UWA测评过的项目中,有不少项目的UI纹理均开启了Mipmap功能,不仅造成了内存占用上的浪费,同时也增加了不小的加载时间。
      4、ETC2对于支持OpenGL ES3.0的Android移动设备来说,是一个很好的处理半透明的纹理格式。但是,如果你的游戏需要在大量OpenGL ES2.0的设备上进行运行,那么我们不建议使用ETC2格式纹理。因为不仅会造成大量的内存占用(ETC2转成RGBA32),同时也增加一定的加载时间。下图为测试2中所用的测试纹理在三星S3和S4设备上加载性能表现。可以看出,在OpenGL ES2.0设备上,ETC2格式纹理的加载要明显高于ETC1格式,且略高于RGBA16格式纹理。因此,建议研发团队在项目中谨慎使用ETC2格式纹理。
      在这里插入图片描述

    正是由于以上加载效率问题,我们在UWA测评报告中加入了对每个检测到的纹理资源参数的详细展示,以方便开发团队可以快速查看资源的使用情况,只需对相关信息列进行排序,即可定位引发性能问题的具体资源。在这里插入图片描述
    说明:以上测试数据为我们所用的测试纹理加载数据,需要指出的是,不同纹理的加载效率是不相同的,因为其内容的不同会造成AssetBundle压缩包大小的不同,进而造成最终加载效率的不同。这里我们给出的具体性能比较,其本意是让大家通过数据直观了解到纹理格式、分辨率和Mipmap功能对加载性能的影响。另外,我们后续会进行更多的测试,以期为大家提供更为普遍的测试结果。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值