突破内存的桎梏——移动端纹理压缩应用与分析

发表于2017-07-05
评论0 4k浏览


1    前言


最近一段时间AR技术成为了时下热门,越来越多的应用开发者投身到这些技术中来。应用中出现了3DAR场景,图形学也成为了必备的技术基础。在开发过程中,往往为了追求更好的效果而使用了更加高清的素材,使得本就内存吃紧的手机面对更加严峻的挑战,尤其是对iOS开发者而言。

为了解决这个问题,我们使用了纹理压缩技术。使用这个技术可以大幅度的降低APP的内存(共享显存)占用,从而在有限的内存限制下,使用更丰富的素材。

2    什么是纹理压缩


常见的图片文件格式,比如PNGJPGBMP等,是图像为了存储信息而使用的对信息的特殊编码方式。它存储在磁盘中,或者内存中,但是并不能被GPU所识别。

这些文件格式当被读入后,还是需要经过CPU解压成bitmap,再传送到GPU端进行使用。

纹理格式是能被GPU所识别的像素格式,能被快速寻址并采样。压缩纹理,是一种GPU能直接读取并显示的格式,使得图像无需解压即可进行渲染,节约大量的内存。

突破内存的桎梏——移动端纹理压缩应用与分析

3    常见的压缩纹理格式


3.1  DXT

DXT纹理压缩格式来源于S3(Silicon & Software Systems)公司提出的S3TC(S3 Texture Compression),基本思想是把4x4的像素块压缩成一个64128位的数据块,是有损压缩方式。DXT1-DXT5S3TC算法的五种变化,用于各种Windows设备。

突破内存的桎梏——移动端纹理压缩应用与分析

压缩率:DXT1DXT4DXT54:1DXT2DXT32:1

主要支持Windows平台及Tegra系列的GPUAndroid手机

支持GPU

突破内存的桎梏——移动端纹理压缩应用与分析

3.2  ETC

Ericsson Texture Compression,是由 Khronos 支持的开放标准,在移动平台中广泛采用。它是一种为感知质量设计的有损算法,其依据是人眼对亮度改变的反应要高于色度改变。类似于DXTETC也是把4x4的像素块压缩成一个64128位的数据块,也是有损压缩。

 

Alpha

压缩率

适用

ETC1

N

6:1

OpenGL ES 2.0

ETC2

Y

4:1

OpenGL ES 3.0

这个系列,可以说是适用机型最广的格式。

ETC1支持几乎所有市面上的Android机,所有iPhone

ETC2支持大部分高端Android机,iPhone 5S及以上

3.3  PVRTC

PowerVR Texture CompressionPVRTC格式与基于块的压缩格式,比如S3TCETC的不同之处是,它使用2张双线性放大的低分辨率图,根据精度和每个像素的权重,融合到一起来呈现纹理,并且2-bpp4-bpp都支持ARGB数据。PVRTC格式压缩比较高,也是有损压缩。

 

Alpha

压缩率

适用

PVRTC 2BPP

Y

16:1

OpenGL ES 2.0

PVRTC 4BPP

Y

8:1

OpenGL ES 2.0

这个系列,是iPhone支持最广的格式

只支持长宽相等且为2的幂次方的纹理

支持部分Android机(GPUPowerVR系列),iPhone全系列机型

支持的GPU

突破内存的桎梏——移动端纹理压缩应用与分析

3.4  ASTC

ASTC(Adaptive Scalable Texture Compression,自适应扩展纹理压缩),这是ARM提出的,去年被Khronos组织认可,纳入到标准中来,不过并不是强制性的

 

有多种压缩方式可选,具有不同的压缩率

 

 

 

Block footprint

Bit rate

压缩率

4x4

8.00

25%

5x4

6.40

25%

5x5

5.12

20%

6x5

4.27

20%

6x6

3.56

14%

8x5

3.20

20%

8x6

2.67

5%

10x5

2.56

20%

10x6

2.13

7%

8x8

2.00

25%

10x8

1.60

25%

10x10

1.28

20%

12x10

1.07

20%

12x12

0.89

 

这个系列,可以说是综合性能和使用便捷性最好的系列。

支持部分高端Android机型,iPhone6及以上机型

 

4    主要优缺点


在几乎不损害图片质量和显示性能的情况下,大幅度降低内存(显存)开销,纹理压缩就是这样的一个技术。

不过,任何的技术都有其适用范围和优缺点,需要仔细评估再决定。

4.1  主要优点

占用内存(显存)大幅度降低

无额外性能开销

使用方便,只需少量代码

4.2  主要缺点

硬件相关,要考虑兼容性

压缩纹理文件大小比常规PNGJPG文件大

需要额外的制作工具,无法直接在移动端生成

5    如何使用压缩纹理


5.1  保存格式

压缩纹理是图片数据的一种编码方式,我们还缺少一个容器去承载。就像MP4文件是H264的视频的容器一样。

我们选择了使用KTX的格式。

KTX是一个为OpenGLOpenGL ES程序设计的纹理存储格式。它可以简单的辨别里面所存储的纹理格式和其他相关信息。

5.2  文件结构


",="" sans-serif;="" color:="" black;"="">Byte[12] identifier
",="" sans-serif;="" color:="" black;"="">UInt32 endianness
",="" sans-serif;="" color:="" black;"="">UInt32 glType
",="" sans-serif;="" color:="" black;"="">UInt32 glTypeSize
",="" sans-serif;="" color:="" black;"="">UInt32 glFormat
",="" sans-serif;="" color:="" black;"="">Uint32 glInternalFormat
",="" sans-serif;="" color:="" black;"="">Uint32 glBaseInternalFormat
",="" sans-serif;="" color:="" black;"="">UInt32 pixelWidth
",="" sans-serif;="" color:="" black;"="">UInt32 pixelHeight
",="" sans-serif;="" color:="" black;"="">UInt32 pixelDepth
",="" sans-serif;="" color:="" black;"="">UInt32 numberOfArrayElements
",="" sans-serif;="" color:="" black;"="">UInt32 numberOfFaces
",="" sans-serif;="" color:="" black;"="">UInt32 numberOfMipmapLevels
",="" sans-serif;="" color:="" black;"="">UInt32 bytesOfKeyValueData
",="" sans-serif;="" color:="" black;"="">  
",="" sans-serif;="" color:="" black;"="">for each keyValuePair that fits in bytesOfKeyValueData
",="" sans-serif;="" color:="" black;"="">    UInt32   keyAndValueByteSize
",="" sans-serif;="" color:="" black;"="">    Byte     keyAndValue[keyAndValueByteSize]
",="" sans-serif;="" color:="" black;"="">    Byte     valuePadding[3 - ((keyAndValueByteSize + 3) % 4)]
",="" sans-serif;="" color:="" black;"="">end
",="" sans-serif;="" color:="" black;"="">  
",="" sans-serif;="" color:="" black;"="">for each mipmap_level in numberOfMipmapLevels",="" sans-serif;="" color:="" black;"="">*
",="" sans-serif;="" color:="" black;"="">    UInt32 imageSize; 
",="" sans-serif;="" color:="" black;"="">    for each array_element in numberOfArrayElements",="" sans-serif;="" color:="" black;"="">*
",="" sans-serif;="" color:="" black;"="">       for each face in numberOfFaces
",="" sans-serif;="" color:="" black;"="">           for each z_slice in pixelDepth",="" sans-serif;="" color:="" black;"="">*
",="" sans-serif;="" color:="" black;"="">               for each row or row_of_blocks in pixelHeight",="" sans-serif;="" color:="" black;"="">*
",="" sans-serif;="" color:="" black;"="">                   for each pixel or block_of_pixels in pixelWidth
",="" sans-serif;="" color:="" black;"="">                       Byte data[format-specific-number-of-bytes]",="" sans-serif;="" color:="" black;"="">**
",="" sans-serif;="" color:="" black;"="">                   end
",="" sans-serif;="" color:="" black;"="">               ",="" sans-serif;="" color:="" black;"="">end
",="" sans-serif;="" color:="" black;"="">           end
",="" sans-serif;="" color:="" black;"="">           Byte cubePadding[0-3]
",="" sans-serif;="" color:="" black;"="">       end
",="" sans-serif;="" color:="" black;"="">    end
",="" sans-serif;="" color:="" black;"="">    Byte mipPadding[3 - ((imageSize + 3) % 4)]
",="" sans-serif;="" color:="" black;"="">end

5.3  使用KTX格式

typedef struct __attribute__((packed))
{
    uint8_t identifier[12];
    uint32_t endianness;
    uint32_t glType;
    uint32_t glTypeSize;
    uint32_t glFormat;
    uint32_t glInternalFormat;
    uint32_t glBaseInternalFormat;
    uint32_t width;
    uint32_t height;
    uint32_t depth;
    uint32_t arrayElementCount;
    uint32_t faceCount;
    uint32_t mipmapCount;
    uint32_t keyValueDataLength;
} KTXHeader;

KTXHeader *header = (KTXHeader *)[data bytes];
BOOL endianSwap = (header->endianness == 0x01020304);
self.width = endianSwap ? CFSwapInt32(header->width) : header->width; self.height = endianSwap ? CFSwapInt32(header->height) : header->height; self.internalFormat = endianSwap ? CFSwapInt32(header->glInternalFormat) : header->glInternalFormat; uint32_t mipCount = endianSwap ? CFSwapInt32(header->mipmapCount) : header->mipmapCount; uint32_t keyValueDataLength = endianSwap ? CFSwapInt32(header->keyValueDataLength) : header->keyValueDataLength; const uint8_t *bytes = [data bytes] + sizeof(KTXHeader) + keyValueDataLength; const size_t dataLength = [data length] - (sizeof(KTXHeader) + keyValueDataLength); NSMutableArray *levelDatas = [NSMutableArray arrayWithCapacity:MAX(mipCount, 1)]; const uint32_t blockSize = 16; uint32_t dataOffset = 0; uint32_t levelWidth = self.width, levelHeight = self.height; while (dataOffset < dataLength) { uint32_t levelSize = *(uint32_t *)(bytes + dataOffset); dataOffset += sizeof(uint32_t); NSData *mipData = [NSData dataWithBytes:bytes + dataOffset length:levelSize]; [levelDatas addObject:mipData]; dataOffset += levelSize; levelWidth = MAX(levelWidth / 2, 1); levelHeight = MAX(levelHeight / 2, 1); }

 

levelDatas 就是读取出来的纹理数据

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论