本文介绍了什么是压缩纹理,以及加载压缩纹理的核心步骤。并在 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

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;
}

2.ETC2

ETC2 是 ETC1 的扩展,压缩比率一样,但压缩质量更高,而且支持透明通道,能完整存储 RGBA 信息。ETC2 需要 OpenGL ES 3.0(对应 WebGL 2.0)环境,目前还有不少低端 Android 手机不兼容,iOS 方面从 iPhone5S 开始都支持 OpenGL ES 3.0。ETC2 和 ETC1 一样,长宽可以不相等,但要求是 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 数据:

// ETC2 魔数
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研发。ASTC同样是基于block的压缩方式,但块的大小却较支持多种尺寸,比如从基本的4x4到12x12;每个块内的内容用128bits来进行存储,因而不同的块就对应着不同的压缩率;相比ETC,ASTC不要求长宽是2的幂次方。

// ASTC 魔数
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对象后,即可进行压缩纹理的显示了:

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);

四、总结

压缩纹理的加载,主要是搞清楚如何解析压缩纹理数据。一般而言,压缩纹理加载到内存后,都有一个 Header,通过 Header 可以解析出其宽高等信息,计算出纹理图像大小。最后调用glCompressedTexImage2D方法即可渲染。
可见压缩纹理完全没有图像的解码工作,大大提升加载速度。

最后,介绍几款纹理压缩工具:

  • etc2comp:支持生成etc2纹理
  • etc1tool:支持生成etc1纹理,在Android SDK目录下,Android/sdk/platform-tools/etc1tool
  • ISPCTextureCompressor:支持etc1、astc等
  • astc-encoder:ASTC官方编码器

到此这篇关于Android 使用压缩纹理的文章就介绍到这了,更多相关Android压缩纹理内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android 使用压缩纹理的方案的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部