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

  1. [转]各种移动GPU压缩纹理的使用方法

    介绍了各种移动设备所使用的GPU,以及各个GPU所支持的压缩纹理的格式和使用方法.1. 移动GPU大全 目前移动市场的GPU主要有四大厂商系列:1)Imagination Technologies的P ...

  2. 各种移动GPU压缩纹理的使用方法

    本文系原创整理,欢迎转载,请标明链接 http://www.cnblogs.com/luming1979 有问题欢迎加qq群讨论:366239605 介绍了各种移动设备所使用的GPU,以及各个GPU所 ...

  3. OpenGL ES 压缩纹理

    什么是压缩纹理 在实际应用特别是游戏中纹理占用了相当大的包体积,而且GPU无法直接解码目前流行的图片格式,图片必须转换为RGB等类型的格式才能上传到GPU内存,这显然增加了GPU内存的占用.为了处理这 ...

  4. Android 图片压缩、照片选择、裁剪,上传、一整套图片解决方案

    1.Android一整套图片解决方案 http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650820998&idx=1& ...

  5. Android 下压缩图片—微弱失真

    Android下压缩图片的方法: 大概能将3M左右的图片压缩到100K左右, 几乎不失真. 代码如下: import java.io.FileNotFoundException; import jav ...

  6. android图片压缩方法

    android 图片压缩方法: 第一:质量压缩法: private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = ...

  7. android图片压缩的3种方法实例

    android 图片压缩方法: 第一:质量压缩法: private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = ...

  8. OpenGL 加载DDS文件(压缩纹理)

    想必很多人都见过DDS这种文件,它是一个“图片文件”,如果你安装了某些看图软件,你可以直接双击打开它来进行预览. 那么,这种DDS文件和我们常见的TGA/PNG之类的文件有何不同呢? DDS和TGA/ ...

  9. Android 图片压缩器

    概述 Android 图片压缩器:一款高效的图片压缩器库,支持批量压缩,异步压缩.多线程多任务压缩,压缩比设置等特性. 详细 代码下载:http://www.demodashi.com/demo/12 ...

随机推荐

  1. ActiveMQ、RabbitMQ、RocketMQ、Kafka四种消息中间件分析介绍

    ActiveMQ.RabbitMQ.RocketMQ.Kafka四种消息中间件分析介绍 我们从四种消息中间件的介绍到基本使用,以及高可用,消息重复性,消息丢失,消息顺序性能方面进行分析介绍! 一.消息 ...

  2. 平衡树——splay 一

    splay 一种平衡树,同时也是二叉排序树,与treap不同,它不需要维护堆的性质,它由Daniel Sleator和Robert Tarjan(没错,tarjan,又是他)创造,伸展树是一种自调整二 ...

  3. 8000字讲透OBSA原理与应用实践

    摘要:OBSA项目是围绕OBS建立的大数据和AI生态,其在不断的发展和完善中,目前有如下子项目:hadoop-obs项目和flink-obs项目. 文章作者:存储服务产品部开发者支持团队 OBS存储服 ...

  4. 关于标准IO缓冲区的问题

    关于标准IO缓冲区的问题 按照标准IO缓冲区可以分为三类: 不缓存类型: 一旦有数据,直接将数据写入到文件 行缓冲类型: 同全缓冲类型 遇到\n时,将数据写入文件 全缓冲类型: 当程序结束,将数据冲洗 ...

  5. 技术分享 | ARM下中标麒麟系统ky10使用Xtrabackup-8.0.25

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 一.需求背景 查询Percona官方手册,Xtrabackup 8.0可以备份M ...

  6. Taurus.MVC WebAPI 入门开发教程4:控制器方法及参数定义、获取及基础校验属性【Require】。

    系列目录 1.Taurus.MVC WebAPI  入门开发教程1:框架下载环境配置与运行. 2.Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World. 3.Tau ...

  7. 从 Airflow 到 Apache DolphinScheduler,有赞大数据开发平台的调度系统演进

    点击上方 蓝字关注我们 作者 | 宋哲琦 ✎ 编 者 按 在不久前的 Apache  DolphinScheduler Meetup 2021 上,有赞大数据开发平台负责人 宋哲琦 带来了平台调度系统 ...

  8. NC20242 [SCOI2005]最大子矩阵

    题目链接 题目 题目描述 这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大. 注意:选出的k个子矩阵 不能相互重叠. 输入描述 第一行为n,m,k(1 ≤ n ≤ 100 ...

  9. java学习第二天多态.day09

    接口 接口总结 接口表示一种规约(规范.标准),它里面定义了一些列抽象方法(功能),它可以被多个类实现. 1接口名称首写字母用I,表示一个接口,后命名使用驼峰命名 2.接口中定义的都是抽象方法,所以可 ...

  10. Spring5完整版详解

    1.Spring 1.1简介 2002,首次退出来Spring框架的雏形:interface21框架 Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,与2004年 ...