Android 使用壓縮紋理

本文介紹了什么是壓縮紋理,以及加載壓縮紋理的核心步驟 。并在 Android OpenGLES 平臺上實現了壓縮紋理的顯示 。
一、壓縮紋理概念傳統的圖片文件格式有 PNG 、 JPEG 等,這種類型的圖片格式無法直接被 GPU 讀取,需要先經過 CPU 解碼后再上傳到 GPU 使用,解碼后的數據以 RGB(A) 形式存儲 , 無壓縮 。
紋理壓縮顧名思義是一種壓縮的紋理格式,它通常會將紋理劃分為固定大小的塊(block)或者瓦片(tile) , 每個塊單獨進行壓縮,整體顯存占用更低,并且能直接被 GPU 讀取和渲染(無需 CPU 解碼) 。
紋理壓縮支持隨機訪問 , 隨機訪問是很重要的特性,因為紋理訪問的模式高度隨機,只有在渲染時被用到的部分才需要訪問到,且無法提前預知其順序 。而且在場景中相鄰的像素在紋理中不一定是相鄰的 ,因此圖形渲染性能高度依賴于紋理訪問的效率 。綜上,相比普通格式圖片 , 紋理壓縮可以節省大量顯存和 CPU 解碼時間 , 且對 GPU 友好 。
二、OpenGL 接口想要使用 OpenGL 加載壓縮紋理,只需要了解一個接口:glCompressedTexImage2D 。
1.glCompressedTexImage2D接口聲明如下,注釋里說明了各參數的含義:
void glCompressedTexImage2D (GLenum target,                             GLint level,                             GLenum internalformat, // 格式                             GLsizei width,  // 紋理寬度                             GLsizei height, // 紋理高度                             GLint border,                             GLsizei imageSize, // 紋理數據大小                             const void *data) // 紋理數據所以加載一個壓縮紋理,主要有以下幾個要點:
  • 獲取到壓縮紋理存儲格式
  • 獲取壓縮紋理的大小
  • 獲取壓縮紋理的圖像大小
2.判斷壓縮紋理是否支持有的設備可能不支持壓縮紋理,使用前需要進行判斷 。
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 紋理}三、壓縮紋理加載為了方便描述,定義了一個壓縮紋理的結構體:
// 壓縮紋理相關信息struct CompressedTextureInfo { bool is_valid; // 是否為一個有效的壓縮紋理信息 GLsizei width; GLsizei height; GLsizei size; GLenum internal_format; GLvoid *data;};下面介紹ETC1、ETC2和ASTC格式的壓縮紋理如何解析成CompressedTextureInfo對象 。
1.ETC1【Android 使用壓縮紋理】ETC1格式是OpenGL ES圖形標準的一部分,并且被所有的Android設備所支持 。擴展名為: GL_OES_compressed_ETC1_RGB8_texture,不支持透明通道,所以僅能用于不透明紋理 。且要求大小是2次冪 。當加載壓縮紋理時 , 參數支持如下格式: GL_ETC1_RGB8_OES(RGB,每個像素0.5個字節)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;}

推薦閱讀