为什么需要纹理压缩

“最近在玩什么游戏,推荐一个”。不管是谁,总会说过或听过这个问题吧。

这时候,你脑海里面浮现的也许是这样的画面

1

或许最终小伙伴们也能接受这样的游戏

3

但还是会有一些玩家更怀念这样的游戏

2

在软件开发,特别是三维应用中,纹理随处可见,但受限于网络环境和硬件能力,纹理也是一大瓶颈。而且在一般的三维应用中,纹理所占大小基本都会在1/2以上,模型中往往超过2/3。或许你会说,纹理不就是一张图吗,有那么重要吗?

如下两张对比图,可能你会认为前者逼格高,但对于正常人而言,后者显然要好很多。正是有了纹理,如同在骨架上赋予了皮肤,让我们的应用更加的逼真,贴近现实。

4

而你能想象到吗?如上的模型一共有三张纹理,其中之一效果如下:

5

看上去怪怪的。其实在纹理的压缩中,人们先想到了如何去除冗余信息,对称的部分只保留一份,尽可能让不同的部分紧凑,充分利用好每一个像素来保存有效数据。得益于对称在大自然中的普遍性,这种方式确实极大的减少了纹理像素。

纹理的拼接是纹理压缩的开始,采用不同的压缩方式对纹理最终的大小影响也是显著的。比如上面的这张纹理在不同压缩格式下的大小差别也是非常显著的(原始文件为tga格式,通过Photoshop转换为其他格式,默认选项):

6

如果按照上面表格的逻辑,用jpg或png格式就好了,无损压缩,而且也是最小的。确实在很多情况下这是一个比较好的选择,比如网络带宽有限,这样可以很好的节约带宽和下载时间,而Bmp,tga,png以及jpg都是无损格式。但这类压缩存在一个致命缺陷,他们都是基于整幅图片下进行的压缩,比如霍夫曼编码等,这样像素和像素之间在解码的过程中存在依赖关系,无法直接实现单个像素级别的解析,这就发挥不了显卡的并发能力,更重要的是问题在于无论是png还是jpeg最终在显存中解码后都是RGBA的纹理格式,因此并无法减少显存的占用率。比如一张256*256的RGBA纹理,无论是png还是jpg格式,虽然文件大小不一样,在显卡中的大小仍然是256*256*4的显存空间。

正是因为传统的图片格式并没有考虑显卡的这种特性,所以很难满足三维应用中的要求。基于这些问题,如下四点可以作为我们选择纹理压缩格式的衡量标准(引自《Texture Compression using Low-Frequency Signal Modulation》)

  • 解析速度
    在纹理操作中,读取纹理数据是关键步骤,所以解码速度至关重要。

  • 随机读取数据
    能快速的随机读取任意像素

  • 压缩率和纹理质量
    既要保证一个不错的压缩效果,也要把纹理损失控制在一定范围内

  • 压缩速度
    通常纹理压缩在渲染前已经提前准备好,所以如果压缩的速度比解析速度慢,也是可以接受的。

调色板技术

最初想到的就是调色板技术,这个思路很简单,在当时硬件能力不高的情况下也非常的好用,类似GIF格式,通常保存一个8位或4位的调色板。为什么没有16位的调色板,因为16位的RGB的效果本身就相对不错,所以16位调色板的意义并不大。如下是调色板原理示意。

7

对于纹理中颜色个数不超过256,或者愿意适当删减,将颜色数目控制在256以内的话,调色板还是非常高效的压缩技术,相比RGBA的颜色格式要少87.5%的空间。当然,颜色越丰富,所效果损失越严重。如下是同一张纹理的效果对比:

8

调色板方式下还有一个非常明显的优势是风格的变化,只需要更改调色板信息,而不用保存多套纹理,就可以很轻松的实现风格的多样化,这种成本很低,而且还很高效。

9

但是显卡中并不支持这种调色板纹理方式,或者只有很老的显卡会支持,当然我们可以采用一维纹理的方式来模拟调色板,但这种情况下不能开启纹理过滤功能,因为调色板的颜色顺序是随机的,在插值过程中和我们预期的效果会有出入。

10

可见调色板的使用非常灵活,如果运用得当,很多复杂的问题都可以很好的解决,但毕竟显卡不支持这种纹理方式,而且毕竟也有256颜色的限制,在稍微复杂的情况下就有点捉襟见肘。而在顶点着色器上,每次都要操作两次(获取索引值,读取调色板对应的颜色),而且调色板也需要作为参数,或指定一个全局的调色板,这样就会存在内存和显存之间的频繁切换,从性能的角度来也不是最优方案。

纹理压缩

调色板方式有着很多的优点,但也有致命的缺点,在调色板的基础上不断改良,最终形成了现在这种相对平衡的压缩方案。

11

        首先,意识到有损压缩下的显示效果还是不错的,所以压缩后以16位的颜色格式存储,如上是RGB和16位的对比效果图。再次则是自带“调色板”,化整为零,方便自身携带。

12

上图是纹理压缩原理图,对于一张原始纹理,会创建两张小纹理A和B,可以认为是原始纹理的缩略图,同时还有一个矩阵M,M的行列和原始纹理的长宽一致,里面的值类似于调色板中的索引,实现纹理A和纹理B的混合。示意图如下:

13

可以说目前主流的纹理压缩格式,比如DXT,PVR和ETC纹理压缩在原理上如出一辙,但在细节上会有很多独特的改善。我们逐个道来,对比其中的优劣。

DXT

       DXT是一种有损纹理压缩算法,微软的Direct中支持,DXT的格式包括DXT1~DXT5,其中DXT1和DXT5较为多见,后面会做详细讨论。可以说DXT是目前应用最广泛的纹理压缩格式,可以认为所有的PC端显卡都支持DXT压缩,维基百科说记录,该专利有效期到2017年10月2号。

14

如上图,DXT的压缩思路也比较一致,有两个Color A(00)/B(11),而4*4矩阵中的索引比较简单,在DXT不同的格式中,差值的因子稍有不同,比如在DXT1中,差值得到的另外两个颜色的公式为:C2=  2/3*C0 + 1/3*C1, C3 =  1/3*C0 + 2/3*C1,这里有一个小技巧,尽管分母是3,但都会近似到2的N次幂,比如2/3约等于5/8,为什么?如下是食人怪纹理的DXT1效果:

15

DXT算法非常容易理解,而且整体看上去效果不错,但如果对局部特写,会发现在细节上会有很多丢失,这也是算法本身导致的,毕竟每个块只有两个颜色,而其他颜色都是在这两个颜色区间的差值,如果当前区域内还有其他显著颜色则必然会有丢失。

16

这种信息的丢失主要集中在比较细的边界中,但DXT1在压缩率上是RGB的6倍,这种问题可以通过提高纹理分辨率的方式来解决,高宽放大41%,这样整个纹理是以前的2倍,但压缩率还能保持为3倍,也是可以接受的。

在DXT中还有一个主要的损失,就是RGB的24位转为了16位颜色,16位中R&B各占5位,但是G占了6位,这是因为人眼对绿色最为敏感。

17

另外一个问题就是DXT3和DXT5之间的对比,相比DXT1不支持透明度(但支持是否透明),DXT5要大一倍(多了64bit),和之前颜色保存方案一样对透明度也保存了两个16位的颜色和对应的调色板,对RGBA的效果也得到了保证,但DXT3思路不一样,它是对每一个像素保存了4bit的透明度,同样也是多了64bit,但此时毕竟只有16个透明度选项,相比DXT5,在压缩率上相当,但对透明色的处理不够细腻,因此在实用性上并不推荐DXT3。

尽管DXT在细节上有明显硬伤,在总体效果不错,而且确实是一种强大的压缩方式,所以在多数纹理压缩选择中都是最佳方案,几乎可以认为是PC下的标准压缩格式。

PVR&ETC

也许是出于专利和商业角度,也许确实DXT在移动端确实无法满足要求,DXT并没有在移动端得到很大的支持,相反,在iOS设备中支持的是PVR压缩,在Android中支持的是ETC压缩。下面详细介绍一下PVR的压缩和ETC解压缩的过程。

       DXT在细节上缺陷明显,最重要的原因是当把纹理分为4*4像素的区域块后,每个块之间都是独立的,尽管这极大的简化了压缩算法,但却丢失了相邻块之间这种普遍的相似性。这是算法本身导致的,而PVR则会考虑该区域块对应的右侧,下侧和右下侧的三个区域块的关联性。

18

如上,是PVR中一个block的结构图,同样的两张Color A/B,这里会有透明模式和非透明模式,这种策略是可取的,首先不用单独增加透明度的字段,尽管透明度的增加会牺牲RGB的质量,但在透明情况下,RGB的作用并不如在不透明情况下那样重要,这种损失也是可以接受并弥补。

首先,在生成Color A/B时,会对原始纹理做一些处理(最简单的就是翻转或旋转90度),得到一张delta image,这个delta可以用来调整生成的Color A/B,比如最简单的一个方式就是点乘每个delta向量。

然后基于Color A/B来计算该block中对应的M,相比于DXT1中的1/3和2/3,PVR中对应的值分别为:

19

在计算M的过程中,会对已有的Color A/B进行优化,这个过程称为SVD(Singular ValueDecomposition),因为非常耗时,时间复杂度是O(n^3),所以仅对中心块进行优化,而对四周不进行此操作。

20

虽然PVR也提供了2bpp(bits perpixel)的格式,但效果很烂,所以基本只有4bpp是可用的。另外因为考虑了区域块之间的相关性,还有SVD算法对效果的优化,不难想象,PVR纹理在压缩时性能慢的难以忍受。但没办法,毕竟PVR是iOS官方支持的纹理压缩,只能忍。

上面是PVR的压缩过程,下面我们对应的看一下ETC下解压缩的过程。

21

如上,是ETC中Color A/B的大致分布(其中一种较为简单的情况,总共两种情况,取决于diffbit是0还是1)。而cw1和cw2对应所需的“调色板”。

在ETC中,对调色板做了一个优化,下面是索引和值的对应关系:

22

这个调色板并不复杂,结合M中对应的索引(2bit),获取每个原始像素对应Color A/B的偏移量

23

如上是ETC的解压,至此,我们详细介绍了三种主流的压缩格式,他们之间思路相仿,但具体细节上各有所长,为了压缩的性能,可以说里面有很多小的技巧值得我们借鉴。当然孰优孰劣,每个人或许都会有自己的判断。下面是一个详细的各种压缩格式下效果和压缩比的对比图,各位感兴趣的可以自行对比。

 

24

25

 

总结

终于写完了,本文主要参考资料和图片来自joostdevblog中对DXT和调色板的详细介绍,以及上面提到的Texture Compression这篇经典论文,以及维基百科中关于DXT纹理压缩,以及OpenGL官网上对ETC纹理的介绍。在学习纹理压缩这个过程中,也在思考这样设计的目的,不同纹理之前的细微区别,究竟是商业因素还是技术问题,导致了这些差异,也在试图总结,这么多的trick他们是怎么想到的,虽然看起来不起眼,但里面都是经验和智慧的结晶,整合在一起确实让人叹服,个人也在不断的梳理方方面面,希望能通过自己的理解,能让大家有条理的,相对深入的了解纹理压缩的原因,更好的理解运用这些技术。当然,难免有出错的地方也希望指正。

但从现实的角度来看,受制于专利和硬件厂商,我们并没太多选择的余地,Android下就要用ETC,iOS下只能PVR,而在PC上不用DXT估计就要被嘲讽了。但这也是一个很棘手的问题,比如在WebGL下,特别是Android下差异化很大,是否支持纹理压缩,甚至在同一个设备不同的浏览器,因为驱动的不一致,可能系统自带的会支持ETC压缩,而微信等QQ浏览器下并不支持。而且华为的手机貌似在浏览器级别下都不支持ETC(硬件支持,还是驱动的问题)。而如果在移动设备上不用压缩,显存是有限的,除非你在数据量上做出牺牲,怎么解决都很矛盾,相比而言,iOS下则要舒服很多。设备的多样性带来的烦恼,让我觉得乔布斯的伟大之处:优秀的同事不是为了计算机而工作,而是因为计算机是传达某种情感的最佳媒介,他们渴望分享,正是因为这种精神,有些人宁愿做诗人也不愿意做银行家,我想把这种精神溶入产品里,而计算机就是我传达情感的媒介。

好了,鸡汤不是白喝的,长按下图有奇迹,转发更能保平安~

0

posted @ 2016-05-16 09:35  fu*k  阅读(13165)  评论(3编辑  收藏  举报