Android?使用壓縮紋理的方案
本文介紹了什么是壓縮紋理,以及加載壓縮紋理的核心步驟。并在 Android OpenGLES 平臺(tái)上實(shí)現(xiàn)了壓縮紋理的顯示。
一、壓縮紋理概念
傳統(tǒng)的圖片文件格式有 PNG 、 JPEG 等,這種類(lèi)型的圖片格式無(wú)法直接被 GPU 讀取,需要先經(jīng)過(guò) CPU 解碼后再上傳到 GPU 使用,解碼后的數(shù)據(jù)以 RGB(A) 形式存儲(chǔ),無(wú)壓縮。
紋理壓縮顧名思義是一種壓縮的紋理格式,它通常會(huì)將紋理劃分為固定大小的塊(block)或者瓦片(tile),每個(gè)塊單獨(dú)進(jìn)行壓縮,整體顯存占用更低,并且能直接被 GPU 讀取和渲染(無(wú)需 CPU 解碼)。
紋理壓縮支持隨機(jī)訪(fǎng)問(wèn),隨機(jī)訪(fǎng)問(wèn)是很重要的特性,因?yàn)榧y理訪(fǎng)問(wèn)的模式高度隨機(jī),只有在渲染時(shí)被用到的部分才需要訪(fǎng)問(wèn)到,且無(wú)法提前預(yù)知其順序。而且在場(chǎng)景中相鄰的像素在紋理中不一定是相鄰的 ,因此圖形渲染性能高度依賴(lài)于紋理訪(fǎng)問(wèn)的效率。綜上,相比普通格式圖片,紋理壓縮可以節(jié)省大量顯存和 CPU 解碼時(shí)間,且對(duì) GPU 友好。
二、OpenGL 接口
想要使用 OpenGL 加載壓縮紋理,只需要了解一個(gè)接口:glCompressedTexImage2D
。
1.glCompressedTexImage2D
接口聲明如下,注釋里說(shuō)明了各參數(shù)的含義:
void glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, // 格式 GLsizei width, // 紋理寬度 GLsizei height, // 紋理高度 GLint border, GLsizei imageSize, // 紋理數(shù)據(jù)大小 const void *data) // 紋理數(shù)據(jù)
所以加載一個(gè)壓縮紋理,主要有以下幾個(gè)要點(diǎn):
- 獲取到壓縮紋理存儲(chǔ)格式
- 獲取壓縮紋理的大小
- 獲取壓縮紋理的圖像大小
2.判斷壓縮紋理是否支持
有的設(shè)備可能不支持壓縮紋理,使用前需要進(jìn)行判斷。
std::string extensions = (const char*)glGetString(GL_EXTENSIONS); if (extensions.find("GL_OES_compressed_ETC1_RGB8_texture")!= string::npos) { // 支持 ETC1 紋理 } if (extensions.find("GL_OES_texture_compression_astc") != std::string::npos) { // 支持 ASTC 紋理 }
三、壓縮紋理加載
為了方便描述,定義了一個(gè)壓縮紋理的結(jié)構(gòu)體:
// 壓縮紋理相關(guān)信息 struct CompressedTextureInfo { bool is_valid; // 是否為一個(gè)有效的壓縮紋理信息 GLsizei width; GLsizei height; GLsizei size; GLenum internal_format; GLvoid *data; };
下面介紹ETC1、ETC2和ASTC格式的壓縮紋理如何解析成CompressedTextureInfo
對(duì)象。
1.ETC1
ETC1格式是OpenGL ES圖形標(biāo)準(zhǔn)的一部分,并且被所有的Android設(shè)備所支持。
擴(kuò)展名為: GL_OES_compressed_ETC1_RGB8_texture,不支持透明通道,所以?xún)H能用于不透明紋理。且要求大小是2次冪。
當(dāng)加載壓縮紋理時(shí),參數(shù)支持如下格式: GL_ETC1_RGB8_OES(RGB,每個(gè)像素0.5個(gè)字節(jié))
ETC1 壓縮紋理的加載,主要參考了Android源碼:etc1.cpp
解析 ETC1 紋理:
// 解析 ETC1 紋理 static const CompressedTextureInfo ParseETC1Texture(unsigned char* data) { CompressedTextureInfo textureInfo; textureInfo.is_valid = false; const etc1::etc1_byte *header = data; if (!etc1::etc1_pkm_is_valid(header)) { LogE("LoadTexture: etc1_pkm is not valid"); return textureInfo; } unsigned int width = etc1::etc1_pkm_get_width(header); unsigned int height = etc1::etc1_pkm_get_height(header); GLuint size = 8 * ((width + 3) >> 2) * ((height + 3) >> 2); GLvoid *texture_data = data + ETC1_PKM_HEADER_SIZE; textureInfo.is_valid = true; textureInfo.width = width; textureInfo.height = height; textureInfo.size = size; textureInfo.internal_format = GL_ETC1_RGB8_OES; textureInfo.data = texture_data; return textureInfo; }
2.ETC2
ETC2 是 ETC1 的擴(kuò)展,壓縮比率一樣,但壓縮質(zhì)量更高,而且支持透明通道,能完整存儲(chǔ) RGBA 信息。ETC2 需要 OpenGL ES 3.0(對(duì)應(yīng) WebGL 2.0)環(huán)境,目前還有不少低端 Android 手機(jī)不兼容,iOS 方面從 iPhone5S 開(kāi)始都支持 OpenGL ES 3.0。ETC2 和 ETC1 一樣,長(zhǎng)寬可以不相等,但要求是 2 的冪次方。
首先定義好 ETC2 的 Header:
// etc2_texture.h class Etc2Header { public: Etc2Header(const unsigned char *data); unsigned short getWidth(void) const; unsigned short getHeight(void) const; unsigned short getPaddedWidth(void) const; unsigned short getPaddedHeight(void) const; GLsizei getSize(GLenum internalFormat) const; private: unsigned char paddedWidthMSB; unsigned char paddedWidthLSB; unsigned char paddedHeightMSB; unsigned char paddedHeightLSB; unsigned char widthMSB; unsigned char widthLSB; unsigned char heightMSB; unsigned char heightLSB; }; // etc2_texture.cpp Etc2Header::Etc2Header(const unsigned char *data) { paddedWidthMSB = data[8]; paddedWidthLSB = data[9]; paddedHeightMSB = data[10]; paddedHeightLSB = data[11]; widthMSB = data[12]; widthLSB = data[13]; heightMSB = data[14]; heightLSB = data[15]; } unsigned short Etc2Header::getWidth() const { return (widthMSB << 8) | widthLSB; } unsigned short Etc2Header::getHeight() const { return (heightMSB << 8) | heightLSB; } unsigned short Etc2Header::getPaddedWidth() const { return (paddedWidthMSB << 8) | paddedWidthLSB; } unsigned short Etc2Header::getPaddedHeight() const { return (paddedHeightMSB << 8) | paddedHeightLSB; } GLsizei Etc2Header::getSize(GLenum internalFormat) const { if (internalFormat != GL_COMPRESSED_RG11_EAC && internalFormat != GL_COMPRESSED_SIGNED_RG11_EAC && internalFormat != GL_COMPRESSED_RGBA8_ETC2_EAC && internalFormat != GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) { return (getPaddedWidth() * getPaddedHeight()) >> 1; } return (getPaddedWidth() * getPaddedHeight()); }
解析 ETC2 數(shù)據(jù):
// ETC2 魔數(shù) static const char kMagic[] = { 'P', 'K', 'M', ' ', '2', '0' }; static const bool IsEtc2Texture(unsigned char *data) { return memcmp(data, kMagic, sizeof(kMagic)) == 0; } static const CompressedTextureInfo ParseETC2Texture(unsigned char *data, GLenum internal_format) { CompressedTextureInfo textureInfo; textureInfo.is_valid = false; if (!IsEtc2Texture(data)) { LogE("ParseETC2Texture: not a etc2 texture"); return textureInfo; } Etc2Header etc2Header(data); textureInfo.is_valid = true; textureInfo.width = etc2Header.getWidth(); textureInfo.height = etc2Header.getHeight(); textureInfo.size = etc2Header.getSize(internal_format); textureInfo.internal_format = internal_format; textureInfo.data = data + ETC2_PKM_HEADER_SIZE; return textureInfo; }
3.ASTC
由ARM & AMD研發(fā)。ASTC同樣是基于block的壓縮方式,但塊的大小卻較支持多種尺寸,比如從基本的4x4到12x12;每個(gè)塊內(nèi)的內(nèi)容用128bits來(lái)進(jìn)行存儲(chǔ),因而不同的塊就對(duì)應(yīng)著不同的壓縮率;相比ETC,ASTC不要求長(zhǎng)寬是2的冪次方。
// ASTC 魔數(shù) const unsigned char ASTC_MAGIC_NUMBER[] = {0x13, 0xAB, 0xA1, 0x5C}; // ASTC header declaration typedef struct { unsigned char magic[4]; unsigned char blockdim_x; unsigned char blockdim_y; unsigned char blockdim_z; unsigned char xsize[3]; /* x-size = xsize[0] + xsize[1] + xsize[2] */ unsigned char ysize[3]; /* x-size, y-size and z-size are given in texels */ unsigned char zsize[3]; /* block count is inferred */ } AstcHeader; static const bool IsAstcTexture(unsigned char* buffer) { return memcmp(buffer, ASTC_MAGIC_NUMBER, sizeof(ASTC_MAGIC_NUMBER)) == 0; } static const CompressedTextureInfo ParseAstcTexture(unsigned char *data, GLenum internal_format) { CompressedTextureInfo textureInfo; textureInfo.is_valid = false; if (internal_format < GL_COMPRESSED_RGBA_ASTC_4x4_KHR || internal_format > GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR) { LogE("parseAstcTexture: invalid internal_format=%d", internal_format); return textureInfo; } if (!IsAstcTexture(data)) { LogE("parseAstcTexture: not a astc file."); return textureInfo; } // 映射為 ASTC 頭 AstcHeader* astc_data_ptr = (AstcHeader*) data; int x_size = astc_data_ptr->xsize[0] + (astc_data_ptr->xsize[1] << 8) + (astc_data_ptr->xsize[2] << 16); int y_size = astc_data_ptr->ysize[0] + (astc_data_ptr->ysize[1] << 8) + (astc_data_ptr->ysize[2] << 16); int z_size = astc_data_ptr->zsize[0] + (astc_data_ptr->zsize[1] << 8) + (astc_data_ptr->zsize[2] << 16); int x_blocks = (x_size + astc_data_ptr->blockdim_x - 1) / astc_data_ptr->blockdim_x; int y_blocks = (y_size + astc_data_ptr->blockdim_y - 1) / astc_data_ptr->blockdim_y; int z_blocks = (z_size + astc_data_ptr->blockdim_z - 1) / astc_data_ptr->blockdim_z; unsigned int n_bytes_to_read = x_blocks * y_blocks * z_blocks << 4; textureInfo.is_valid = true; textureInfo.internal_format = internal_format; textureInfo.width = x_size; textureInfo.height = y_size; textureInfo.size = n_bytes_to_read; textureInfo.data = data; return textureInfo; }
得到CompressedTextureInfo
對(duì)象后,即可進(jìn)行壓縮紋理的顯示了:
CompressedTextureInfo textureInfo = etc1::ParseETC1Texture(input_data); if (!textureInfo.is_valid) { LogE("LoadTexture: etc1 textureInfo parsed invalid."); } GLuint texture_id = 0; glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D, texture_id); glCompressedTexImage2D(GL_TEXTURE_2D, 0, textureInfo.internal_format, textureInfo.width, textureInfo.height, 0, textureInfo.size, textureInfo.data);
四、總結(jié)
壓縮紋理的加載,主要是搞清楚如何解析壓縮紋理數(shù)據(jù)。一般而言,壓縮紋理加載到內(nèi)存后,都有一個(gè) Header,通過(guò) Header 可以解析出其寬高等信息,計(jì)算出紋理圖像大小。最后調(diào)用glCompressedTexImage2D
方法即可渲染。
可見(jiàn)壓縮紋理完全沒(méi)有圖像的解碼工作,大大提升加載速度。
最后,介紹幾款紋理壓縮工具:
- etc2comp:支持生成etc2紋理
- etc1tool:支持生成etc1紋理,在Android SDK目錄下,Android/sdk/platform-tools/etc1tool
- ISPCTextureCompressor:支持etc1、astc等
- astc-encoder:ASTC官方編碼器
到此這篇關(guān)于Android 使用壓縮紋理的文章就介紹到這了,更多相關(guān)Android壓縮紋理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)密碼明密文切換(小眼睛)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)密碼明密文切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08剖析Android Activity側(cè)滑返回的實(shí)現(xiàn)原理
在很多的App中,都會(huì)發(fā)現(xiàn)利用手指滑動(dòng)事件,進(jìn)行高效且人性化的交互非常有必要,那么它是怎么實(shí)現(xiàn)的呢,本文給大家解析實(shí)現(xiàn)原理,對(duì)Activity側(cè)滑返回實(shí)現(xiàn)代碼感興趣的朋友一起看看吧2021-06-06Android 多種dialog的實(shí)現(xiàn)方法(推薦)
下面小編就為大家分享一篇Android 多種dialog的實(shí)現(xiàn)方法(推薦),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Android編程使用pull方式解析xml格式文件的方法詳解
這篇文章主要介紹了Android編程使用pull方式解析xml格式文件的方法,結(jié)合實(shí)例形式分析了Android調(diào)用pull解析器操作xml格式文件的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-07-07解析activity之間數(shù)據(jù)傳遞方法的詳解
本篇文章是對(duì)activity之間數(shù)據(jù)傳遞的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Android三種實(shí)現(xiàn)定時(shí)器的方法
本文給大家分享了3種Android實(shí)現(xiàn)定時(shí)器的方法的示例,,需要的朋友可以參考下2015-02-02Android EasyPermissions官方庫(kù)高效處理權(quán)限相關(guān)教程
Easypermissions簡(jiǎn)化了Android M的運(yùn)行時(shí)權(quán)限的申請(qǐng)、結(jié)果處理、判斷等步驟。這篇文章主要介紹了Android EasyPermissions官方庫(kù)高效處理權(quán)限相關(guān)教程,需要的朋友可以參考下2017-11-11Android 出現(xiàn)的警告(Service Intent must be explicit)解決辦法詳解
這篇文章主要介紹了Android 出現(xiàn)的警告(Service Intent must be explicit)解決辦法詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04Android實(shí)現(xiàn)左滑關(guān)閉窗口
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)左滑關(guān)閉窗口,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04