为什么需要纹理压缩?

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

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

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

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

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

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

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

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

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

如果按照上面表格的逻辑,用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位调色板的意义并不大。如下是调色板原理示意。

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

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

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

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

纹理压缩

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

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

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

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

DXT

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

如上图,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效果:

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

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

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

另外一个问题就是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则会考虑该区域块对应的右侧,下侧和右下侧的三个区域块的关联性。

如上,是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中对应的值分别为:

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

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

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

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

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

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

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

总结

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

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

原文发布于微信公众号 - LET(LET0-0)

原文发表时间:2016-05-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

教程 | 如何用TensorFlow在安卓设备上实现深度学习推断

3575
来自专栏WOLFRAM

打造自动化数据科学家:新的分类和预测函数

1023
来自专栏算法+

音频自动增益 与 静音检测 算法 附完整C代码

静音检测 在WebRTC中 是采用计算GMM (Gaussian Mixture Model,高斯混合模型)进行特征提取的。

93610
来自专栏AI研习社

Github 项目推荐 | Windows 10上的 GPU 加速深度学习工具

有很多工具能够帮助开发者在 Linux 和 Mac 上构建深度学习环境(比如 Tensorflow,不幸的是,TensorFlow 无法在 Windows 上轻...

1472
来自专栏机器之心

教程 | 如何构建自定义人脸识别数据集

在接下来的几篇博文中,作者将带领大家训练一个「计算机视觉+深度学习」的模型来执行人脸识别任务。但是,要想训练出能够识别图像或视频流中人脸的模型,我们首先得收集人...

1102
来自专栏MixLab科技+设计实验室

Javascript也可以玩机器学习

前端工程师们~js也可以用来玩机器学习的。 今天看到这些相关的资源,分享给大家~~ 数据可视化这块应该都算比较熟悉的了,建议从通用机器学习库开始学习。 既然有这...

3356
来自专栏机器之心

资源 | 一张速查表实现Apache MXNet深度学习框架五大特征的开发利用

选自AWS blog 机器之心编译 参与:Smith Apache MXNet 是一个功能全面,且具有高度可扩展性的深度学习框架,可支持创建和训练最新型的深度学...

2856
来自专栏杨熹的专栏

什么是 Q-learning

在这个游戏中,agent 从一个给定的位置开始,即起始状态。 在不穿越迷宫墙壁的前提下,在每个状态时,都可以选择上下左右四个方向走一步,或者原地不动, 上下...

1362
来自专栏python小白到大牛

Python识别验证码的另一种花样玩法

这里使用了 pytesseract 来进行验证码识别,它是基于 Google 的 Tesseract-OCR ,所以在使用之前需要先安装 Tesseract-O...

1145
来自专栏PPV课数据科学社区

【可视化】Excel制作INFOGRAPHIC

最近在重新整理日报,周报,月报的数据展现形式,越发觉得一份数据如何展现对于我们数据分析师的受众而言是非常重要的,数据是一种艺术,其原因之一在于如何把数字通过我们...

2554

扫码关注云+社区