图形学自问自答4——图片和纹理

美女镇楼

1. 图片格式和纹理格式

图片格式,如 jpg/png 等
纹理格式,如 ETC1/ETC2/PVRTC/ASTC等

1.1 图片

电脑硬盘上的图片文件用来保存图形或图像的信息,包括大小、颜色等。图片的格式很多,但总体上可以分为点阵图和矢量图两种。

  • 点阵图:也叫位图,记录画面上每一个像素的颜色信息,当对点阵图进行缩放时会失真
  • 矢量图:也叫向量图,矢量图不记录画面中每个像素的信息,而是记录元素的形状和颜色的算法,当矢量图被打开时,查看软件根据矢量图中的信息进行运算,展示运算的结果。无论显示画面是大还是小,画面对应的算法是不变的,所以对画面进行倍数相当大的缩放,其显示效果不会失真

常见的位图格式:

  • bmp 无压缩位图格式
  • png 使用 lz77 压缩算法对位图进行了无损压缩,带 alpha 通道
  • jpg 有损压缩格式,不带 alpha 通道

1.2 点阵图文件大小因素

考虑无压缩的 bmp 格式位图,文件在硬盘上的大小和以下几个因素有关

  • 颜色的丰富度
    假如一张位图只有黑白两种颜色,每个像素使用一位数字即可表示,若使用一个字节(8位)来表示一个像素的颜色,则可以表示=256种颜色,最用的位图,将像素颜色的RGBA分量都使用一个字节来表示,一个像素需要4个字节表示

  • 像素数量
    像素数量越多,需要越大的空间来表示,像素的数量由图片的宽度和高度决定

  • 计算公式
    常用的位图(RGBA32)文件大小计算公式

  • jpg 和 png 格式图片的大小
    jpg 和 png 因为针对图片的像素数据进行了压缩,节省了空间,但因为压缩算法的原因,压缩比与图片本身的颜色分布有关,图片的大小没有固定的计算方法,一个大概的趋势是:颜色的分布越有序,图片文件越小,也就是相邻像素的颜色相差越小,文件越小。关于 jpg 和 png 的压缩算法,可以参考PNG图片压缩原理解析和PNG原理以及jpeg压缩算法

1.3 纹理

图片文件被加载到内存后,被转换为显卡能够识别的纹理提交到显存中,供 GPU 进行采样,纹理采样是根据 uv 坐标获得对应纹理中纹素颜色值的过程。
不同的显卡和图形API对纹理格式的支持不一样,RGBA32被显卡和图形API完全支持,DirectX特有的DXTx只有微软的部分设备支持,Android设备广泛支持ETC系列,iOS则支持 PVRTC 系列。

1.4 加载纹理的过程

CPU将图片从硬盘加载到内存中,进行解码得到 RGBA32 格式位图,然后在 CPU 端进行编码,得到GPU支持的纹理格式,提交到显存供 GPU 采样访问。


图片加载和提交

这个过程中需要进行解码、重新编码,非常消耗 CPU,为了减少 CPU 的消耗,因此可以把图片预先转换成某种纹理格式放到硬盘上,CPU 加载后不需要解码和重新编码,直接提交给 GPU。大大节省了纹理从加载到使用的性能。

1.5 Unity 中的处理

在Unity中,任何图片文件格式都存在一个导入过程,导入后的文件格式都是Texture2D,在Texture2D的导入设置选项中需要针对不同平台设置纹理压缩格式,在构建时,会直接将转换后的纹理格式构建到安装包或 assetbundle 中。

2. 问题

2.1 纹理为什么需要压缩,为什么不直接使用 RGBA32 格式?

使用纹理压缩格式的好处

  • 减少外存大小,也就是在硬盘或安装包的大小,使用RGBA32格式,一个像素的纹理占用4个字节,1024x1024的图片将占用4MB的安装包空间,使用压缩格式的纹理可以显著减少空间占用
  • 减少内存大小,纹理被加载到内存时,占用的内存空间和外存一样大,不需要解压成RGBA32格式,GPU端也不需要解压,可以直接访问
  • 带宽消耗,从CPU传递给GPU的是压缩数据,数据量小,在移动平台上,GPU和CPU共享内存,GPU芯片读取内存同样消耗带宽,带宽消耗过大通常是设备发热的最大元凶

2.2 为什么不直接将 jpeg/png 作为纹理格式提交给 GPU

GPU 通常并行处理若干数据,无法预测纹理中纹素被访问的先后顺序,因此纹理格式必须支持GPU的随机访问

随机访问:任意给定像素坐标,能够快速算出它在图像数据中的地址,从而取得图像数据

几乎所有的纹理压缩算法都以块为单位压缩和存储纹素,可以理解为,每个纹素块(通常为4x4)占用的字节数是确定的,所以当我们需要访问某一个坐标的纹素时,可以明确得到该纹素所在块的数据,GPU纹理采样单元针对该块进行解压,读取对应纹素数据即完成采样。

而jpeg/png这类压缩算法考虑了图片的整体数据,像素数据之间互相依赖,无法针对其中某一块进行解压,必须全部解压才能正确访问纹素,所以不适宜作为纹理格式。

png/jpeg 等压缩方法通常用来优化文件的存储和传输

2.3 纹理压缩有损吗?

纹理压缩算法几乎都是有损压缩,通常情况下有限度的信息损失换到的性能上的提升是值得的。

2.4 如果使用了 GPU 和图形 API 不支持的纹理压缩格式,会发生什么?

如果使用了硬件不支持的纹理压缩格式,Unity在运行时会对纹理进行解压所到无压缩格式,而这种无压缩格式只是格式上是无压缩的,但是因为原始数据是有损压缩,所以视觉上纹理精度和压缩格式是一致的,因此单从视觉上很难察觉,而且占用的内存是双份纹理的开销,而带宽开销是无压缩格式的纹理大小所占用的开销。

例如:ETC格式在PC端Dx11的显卡是不支持的,如果使用,开销计算如下

1024 x 1024 ETC2 8bit的格式在PC端,会产生5M的内存开销(1M压缩的原图+4M解压出来的RGBA32bit图)
1024 x 1024 ETC 4bit格式在PC端,会产生4.5M的内存开销(0.5M压缩的原图+4M解压出来的RGBA32bit图)
而如果贴图格式在目标设备上支持,则不会发生内存中的解压缩操作,压缩过的图大小是多少,在内存中的设备上大小就是多少:

1024 x 1024 ETC2 8bit的格式在Android端,会产生1M的内存开销(1M压缩的原图)
1024 x 1024 ETC 4bit格式在Android端,会产生0.5M的内存开销(0.5M压缩的原图)

2.5 纹理压缩和解压是速度重要吗?

纹理压缩是一次性的工作,例如 Unity 在导入资源时会有一段转换时间,图片被处理成对应的纹理格式后存储了下来,此时纹理压缩的速度慢一些是可以接受的
GPU 对纹理进行采样时,通常只会解压一个纹素块,不会全部解压,这个过程本身就是很快的

你可能感兴趣的:(图形学自问自答4——图片和纹理)