突破內(nèi)存的桎梏:移動端紋理壓縮應用與分析
導語
最近一段時間AR技術(shù)成為了時下熱門,越來越多的應用開發(fā)者投身到這些技術(shù)中來。應用中出現(xiàn)了3D的AR場景,圖形學也成為了必備的技術(shù)基礎(chǔ)。在開發(fā)過程中,往往為了追求更好的效果而使用了更加高清的素材,使得本就內(nèi)存吃緊的手機面對更加嚴峻的挑戰(zhàn),尤其是對iOS開發(fā)者而言。 為了解決這個問題,我們使用了紋理壓縮技術(shù)。使用這個技術(shù)可以大幅度的降低APP的內(nèi)存(共享顯存)占用,從而在有限的內(nèi)存限制下,使用更豐富的素材。
1 前言
最近一段時間AR技術(shù)成為了時下熱門,越來越多的應用開發(fā)者投身到這些技術(shù)中來。應用中出現(xiàn)了3D的AR場景,圖形學也成為了必備的技術(shù)基礎(chǔ)。在開發(fā)過程中,往往為了追求更好的效果而使用了更加高清的素材,使得本就內(nèi)存吃緊的手機面對更加嚴峻的挑戰(zhàn),尤其是對iOS開發(fā)者而言。
為了解決這個問題,我們使用了紋理壓縮技術(shù)。使用這個技術(shù)可以大幅度的降低APP的內(nèi)存(共享顯存)占用,從而在有限的內(nèi)存限制下,使用更豐富的素材。
2 什么是紋理壓縮
常見的圖片文件格式,比如PNG,JPG,BMP等,是圖像為了存儲信息而使用的對信息的特殊編碼方式。它存儲在磁盤中,或者內(nèi)存中,但是并不能被GPU所識別。
這些文件格式當被讀入后,還是需要經(jīng)過CPU解壓成bitmap,再傳送到GPU端進行使用。
紋理格式是能被GPU所識別的像素格式,能被快速尋址并采樣。壓縮紋理,是一種GPU能直接讀取并顯示的格式,使得圖像無需解壓即可進行渲染,節(jié)約大量的內(nèi)存。
3 常見的壓縮紋理格式
3.1 DXT
DXT紋理壓縮格式來源于S3(Silicon & Software Systems)公司提出的S3TC(S3 Texture Compression),基本思想是把4x4的像素塊壓縮成一個64或128位的數(shù)據(jù)塊,是有損壓縮方式。DXT1-DXT5是S3TC算法的五種變化,用于各種Windows設(shè)備。
壓縮率:DXT1,DXT4,DXT5為4:1,DXT2、DXT3為2:1
主要支持Windows平臺及Tegra系列的GPU的Android手機
支持GPU:
3.2 ETC
Ericsson Texture Compression,是由 Khronos 支持的開放標準,在移動平臺中廣泛采用。它是一種為感知質(zhì)量設(shè)計的有損算法,其依據(jù)是人眼對亮度改變的反應要高于色度改變。類似于DXT,ETC也是把4x4的像素塊壓縮成一個64或128位的數(shù)據(jù)塊,也是有損壓縮。
這個系列,可以說是適用機型最廣的格式。
ETC1支持幾乎所有市面上的Android機,所有iPhone
ETC2支持大部分高端Android機,iPhone 5S及以上
3.3 PVRTC
PowerVR Texture Compression,PVRTC格式與基于塊的壓縮格式,比如S3TC、ETC的不同之處是,它使用2張雙線性放大的低分辨率圖,根據(jù)精度和每個像素的權(quán)重,融合到一起來呈現(xiàn)紋理,并且2-bpp和4-bpp都支持ARGB數(shù)據(jù)。PVRTC格式壓縮比較高,也是有損壓縮。
這個系列,是iPhone支持最廣的格式
只支持長寬相等且為2的冪次方的紋理
支持部分Android機(GPU:PowerVR系列),iPhone全系列機型
支持的GPU
3.4 ASTC
ASTC(Adaptive Scalable Texture Compression,自適應擴展紋理壓縮),這是ARM提出的,去年被Khronos組織認可,納入到標準中來,不過并不是強制性的
有多種壓縮方式可選,具有不同的壓縮率
這個系列,可以說是綜合性能和使用便捷性最好的系列。
支持部分高端Android機型,iPhone6及以上機型
4 主要優(yōu)缺點
在幾乎不損害圖片質(zhì)量和顯示性能的情況下,大幅度降低內(nèi)存(顯存)開銷,紋理壓縮就是這樣的一個技術(shù)。
不過,任何的技術(shù)都有其適用范圍和優(yōu)缺點,需要仔細評估再決定。
4.1 主要優(yōu)點
占用內(nèi)存(顯存)大幅度降低
無額外性能開銷
使用方便,只需少量代碼
4.2 主要缺點
硬件相關(guān),要考慮兼容性
壓縮紋理文件大小比常規(guī)PNG和JPG文件大
需要額外的制作工具,無法直接在移動端生成
5 如何使用壓縮紋理
5.1 保存格式
壓縮紋理是圖片數(shù)據(jù)的一種編碼方式,我們還缺少一個容器去承載。就像MP4文件是H264的視頻的容器一樣。
我們選擇了使用KTX的格式。
KTX是一個為OpenGL和OpenGLES程序設(shè)計的紋理存儲格式。它可以簡單的辨別里面所存儲的紋理格式和其他相關(guān)信息。
5.2 文件結(jié)構(gòu)
- Byte[12] identifier
- UInt32 endianness
- UInt32 glType
- UInt32 glTypeSize
- UInt32 glFormat
- Uint32 glInternalFormat
- Uint32 glBaseInternalFormat
- UInt32 pixelWidth
- UInt32 pixelHeight
- UInt32 pixelDepth
- UInt32 numberOfArrayElements
- UInt32 numberOfFaces
- UInt32 numberOfMipmapLevels
- UInt32 bytesOfKeyValueData
- for each keyValuePair that fits in bytesOfKeyValueData
- UInt32 keyAndValueByteSize
- Byte keyAndValue[keyAndValueByteSize]
- Byte valuePadding[3 - ((keyAndValueByteSize + 3) % 4)]
- end
- for each mipmap_level in numberOfMipmapLevels*
- UInt32 imageSize;
- for each array_element in numberOfArrayElements*
- for each face in numberOfFaces
- for each z_slice in pixelDepth*
- for each row or row_of_blocks in pixelHeight*
- for each pixel or block_of_pixels in pixelWidth
- Byte data[format-specific-number-of-bytes]**
- end
- end
- end
- Byte cubePadding[0-3]
- end
- end
- Byte mipPadding[3 - ((imageSize + 3) % 4)]
- 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;
- constsize_tdataLength = [data length] - (sizeof(KTXHeader) + keyValueDataLength);
- NSMutableArray *levelDatas = [NSMutableArrayarrayWithCapacity: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 = [NSDatadataWithBytes:bytes + dataOffsetlength:levelSize];
- [levelDatasaddObject:mipData];
- dataOffset += levelSize;
- levelWidth = MAX(levelWidth / 2, 1);
- levelHeight = MAX(levelHeight / 2, 1);
- }
原文鏈接:https://www.qcloud.com/community/article/897529,作者:柯靈杰
【本文是51CTO專欄作者“騰訊云技術(shù)社區(qū)”的原創(chuàng)稿件,轉(zhuǎn)載請通過51CTO聯(lián)系原作者獲取授權(quán)】