Android* 纹理压缩 - 与代码示例的比较研究





5.00/5 (1投票)
此示例演示了如何在 Android 上使用 OpenGL ES* 加载和使用这些不同的格式。
Intel® 开发者专区提供跨平台应用开发工具和操作指南、平台和技术信息、代码示例以及同行专业知识,以帮助开发者创新和取得成功。加入我们的社区,了解 Android、物联网、Intel® RealSense™ 技术和 Windows,下载工具,获取开发套件,与志同道合的开发者交流想法,并参加黑客松、竞赛、路演和本地活动。
这是一个由 William Guo 最初编写的代码示例,由 Intel 公司图形软件应用工程师 Cristiano Ferreira 扩展,增加了 ETC2 纹理压缩格式。此示例的源代码可在 此处 获取。
引言
在计算机图形学领域,将图像或纹理应用到 2D 或 3D 模型以增强图形细节是一种非常常见的技术。Android* 允许使用多种纹理压缩文件格式,每种格式都有其自身的优点和缺点。Android 纹理压缩示例允许开发者轻松比较五种不同纹理压缩文件格式的纹理:Portable Network Graphics* (PNG)、Ericsson Texture Compression* (ETC)、Ericsson Texture Compression 2* (ETC2)、PowerVR Texture Compression* (PVRTC) 和 S3 Texture Compression* (S3TC),后者也称为 DirectX Texture Compression* (DXTC)。此示例演示了如何在 Android 上使用 OpenGL ES* 加载和使用这些不同的格式。所有支持的纹理格式都并排显示,以便观察其相对大小和质量。选择正确的压缩格式可以让开发者在应用大小、视觉质量和性能之间取得平衡。
该示例加载每种纹理格式,确定纹理的映射坐标,并显示每种纹理的一部分。最终组合将显示完整的图像/纹理,但作为四个独立的、特定于格式的纹理。它们在屏幕顶部单独标记,并且文件大小显示在屏幕底部的一个小条上。
原理/相关术语和纹理格式背景
纹理映射是一种将图像应用到形状或多边形表面的方法。一个有用的类比是想象纹理是包装纸,而 3D 模型是要包装的礼品盒。这就是为什么这个过程也称为“纹理包装”。
Mipmaps 是由主纹理生成的一组优化图像。它们通常是为了提高渲染速度和减少混叠伪影而创建的。每个 mip(mipmap 集合中的位图图像)是主纹理的低分辨率版本,当从远处查看原始纹理或其缩小版本时使用。Mipmaps 的创建和实现基于这样一个基本概念:当物体离我们很远或物体很小时,我们无法感知到与近距离物体相同的细节。基于这个想法,不同的 mip 可以根据物体的大小来表示纹理/图像的不同部分。这会提高渲染速度,因为简化的 mip 具有低得多的 texel(总体纹理像素数)计数——需要处理的像素更少。此外,由于 mipmaps 本质上是抗锯齿的,因此可见的伪影数量也大大减少。PNG、ETC (KTX)、ETC2 (KTX)、PVRTC 和 S3TC 中的 mipmap 支持包含在此示例中。
Portable Network Graphics (PNG)
PNG 是一种位图图像格式,主要以其无损数据文件压缩而闻名。该图像格式支持基于调色板的图像(24 位 RGB 或 32 位 RGBA)或灰度图像(带或不带 alpha 通道)。
优点
- 具有无损压缩方案和高视觉质量
- 支持 8 位和 16 位透明度
缺点
- 文件大小大;这将增加应用大小和内存带宽要求
- GPU 成本最高(即性能最差)
Ericsson Texture Compression (ETC)
Ericsson Texture Compression 是一种在 4x4 像素块上运行的纹理压缩格式。最初,Khronos 使用 Ericsson Texture Compression 作为 OpenGL ES 2.0 的标准(此版本也称为 ETC1)。因此,几乎所有 Android 设备都支持这种纹理压缩格式。最近,随着 OpenGL ES 3.0 的发布,ETC1 的一个重做版本,称为 ETC2,被实施为新标准。两者之间的主要区别在于处理每个像素组的算法。算法的改进带来了在细节方面更高保真度的输出。图像的精细质量并没有以额外的空间为代价。
ETC1 和 ETC2 都支持 24 位 RGB 数据压缩,但它们不支持包含 alpha 分量的任何图像/纹理的压缩。此外,有两种不同的文件格式都属于 ETC 纹理压缩类别:KTX 和 PKM。KTX 是标准的 Khronos Group 压缩格式,它提供了多个图像/纹理的容器。当使用 KTX 生成 mipmaps 时,只会创建一个 KTX 文件。另一方面,PKM 是一种更简单的文件格式,主要用于包含单个压缩图像。在这种情况下生成 mipmaps 将创建多个 PKM 文件而不是一个文件,因此不建议用于此目的。
优点
- 与 PNG 纹理压缩格式相比,文件大小明显更小
- 近乎所有 Android 设备都支持 GPU 硬件加速
缺点
- 质量不如 PNG 纹理压缩(ETC 是一种有损压缩格式)
- 不支持 alpha 通道/分量
压缩工具示例
- Mali* 纹理压缩工具 (开发者中心)
- ETC-Pack Tool
PowerVR Texture Compression (PVRTC)
PowerVR Texture Compression 是一种有损、固定速率的纹理压缩格式,主要用于 Imagination Technologies 的 PowerVR* MBX、SGX 和 Rogue 技术。它目前在所有 iPhone*、iPod* 和 iPad* 设备中用作标准压缩格式。与 ETC 和 S3TC 不同,PVRTC 不是基于块的,而是涉及两个低分辨率图像的双线性放大和低精度混合。除了 PVRTC 格式独特的压缩过程外,它还支持 2-bpp(每像素 2 位)和 4-bpp(每像素 4 位)选项的 RGBA(支持 alpha 通道)。
优点
- 支持 alpha 通道/分量
- 支持 2-bpp 和 4-bpp 模式的 RGBA 数据
- 文件大小远小于使用 PNG 纹理压缩的文件
- PowerVR GPU 上的 GPU 硬件加速
缺点
- 质量不如 PNG 纹理压缩(PVRTC 是一种有损压缩格式)
- PVRTC 仅在 PowerVR 硬件上受支持
- 仅支持正方形(2 的幂次方)尺寸纹理才能一致工作,尽管在某些情况下,压缩纹理也支持矩形
- 将纹理压缩成此格式可能速度较慢
用于压缩的工具
S3 Texture Compression (S3TC) 或 DirectX Texture Compression (DXTC)
S3 Texture Compression 是一种有损、固定速率的纹理压缩格式。这种压缩方式使 S3TC 成为硬件加速 3D 计算机图形纹理的理想纹理压缩格式。在将 S3TC 集成到 Microsoft 的 DirectX* 6.0 和 OpenGL 1.3 后,该压缩格式变得更加普及。S3TC 格式至少有 5 种不同的变体(包括 DXT1 到 DXT5)。该示例支持常用的变体(DXT1、DXT3 和 DXT5)。
DXT1: DXT1 是 S3TC 压缩中最小的模式;它将每 16 个像素块转换为 64 位。此外,它由两个不同的 16 位 RGB 5:8:5 颜色值和一个 4x4 2 位查找表组成。DXT1 不支持 alpha 通道。
DXT3: DXT3 将每 16 个像素块转换为 128 位,由 64 位的 alpha 通道数据和 64 位的颜色数据组成。DXT3 是用于具有锐利 alpha 过渡(不透明与半透明)的图像或纹理的良好格式选择。
DXT5: DXT5 将每 16 个像素块转换为 128 位,由 64 位的 alpha 通道数据和 64 位的颜色数据组成。DXT5 是用于具有渐变 alpha 过渡的图像或纹理的良好格式选择。
优点
- 与 PNG 纹理压缩格式相比,文件大小明显更小。
- 质量尚可,带状伪影少(伪影不明显)
- 压缩/解压缩速度快
- 适用于各种视频芯片部分的 GPU 硬件加速:桌面端几乎普遍支持,但在 Android 设备上的支持仍需增长
缺点
- 质量不如 PNG 纹理压缩(S3TC 是一种有损压缩格式)
- 并非所有 Android 设备都支持
用于压缩的工具
- DirectX 纹理工具 (包含在 DX SDK 中)
访问纹理数据
大多数纹理压缩文件格式在实际纹理数据之前都有一个头部。头部通常包含有关纹理压缩格式名称、纹理宽度、纹理高度、纹理深度、数据大小、内部格式以及文件其他特定属性的数据。
我们的目标是从不同的纹理压缩文件中加载纹理数据并将其映射到 2D 模型,以比较质量和文件大小。头部不应包含在要映射的纹理中,否则会导致图像/纹理表示失真。文件头部都因所考虑的文件压缩格式而异,因此,每种纹理压缩文件格式都需要单独支持才能正确加载和映射纹理。
重要提示:
PVRTC 头部是打包的,因为存在 64 位像素格式数据成员(示例中的 mPixelFormat)。ARM 尝试通过填充 4 个额外字节来对齐头部,使头部总共为 56 字节,而不是原始的 52 字节。这会导致在 ARM 设备上显示图像时出现失真。Intel 设备不填充头部,因此不是问题。打包头部可以解决 ARM 填充问题,并且纹理可以在 ARM 和 Intel 设备上正确显示。
加载和支持纹理格式
加载 PNG
PNG 中的 mipmap 由一个简单函数处理:glGenerateMipmap
——这是 Khronos OpenGL 为此特定目的设计的预定义函数。Sean Barrett 的公共域 stb_image.c 被用于读取和加载 PNG 文件(以及定位和识别要处理的纹理数据)。以下是一段初始化纹理并为 PNG 压缩格式提供 mipmap 支持的代码。
// Initialize the texture glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, pData ); // Mipmap support glGenerateMipmap( GL_TEXTURE_2D );
加载 ETC / ETC2
如前所述,ETC 由两种不同的格式组成——KTX 和 PKM。KTX 是标准压缩格式,用作多个图像/纹理的容器,并且是 mipmapping 的理想选择。另一方面,PKM 设计用于简单的单纹理压缩,因此生成 mipmaps 会导致多个 PKM 文件,效率低下。因此,示例应用中 ETC 纹理压缩的 mipmap 支持仅限于 KTX 文件压缩。Khronos 提供了一个开源 C 库 (libktx),其中支持带有 mipmaps 的 KTX 纹理加载。我们利用了这个工具,并在一个名为 LoadTextureETC_KTX
的纹理加载函数中实现了代码。用于实际加载 KTX 纹理压缩格式文件的函数是 ktxLoadTextureM
(从内存中的数据加载所需的纹理)。这个函数 (ktxLoadTextureM
) 在库 (libktx) 中提供,并在 Khronos 网站(下方“资源”部分)中有文档说明。
以下是一段初始化纹理并为 ETC (KTX) 压缩格式提供 mipmap 支持的代码
// Generate handle & Load Texture GLuint handle = 0; GLenum target; GLboolean mipmapped; KTX_error_code result = ktxLoadTextureM( pData, fileSize, &handle, &target, NULL, &mipmapped, NULL, NULL, NULL ); if( result != KTX_SUCCESS ) { LOGI( "KTXLib couldn't load texture %s. Error: %d", TextureFileName, result ); return 0; } // Bind the texture glBindTexture( target, handle );
加载 PVRTC
为 PVRTC 纹理提供 mipmap 支持有点棘手。读取头部后,偏移量被定义为头部大小加上元数据大小(元数据跟在头部后面,也不是实际纹理数据的一部分)。对于生成的每个 mip,像素会被分组到块中(根据是 4 位每像素还是 2 位每像素——两者都是有效的 PVRTC 格式,会有所不同)。然后,进行裁剪,因此块的高度和宽度被限制在特定边界内。然后,调用 glCompressedTexImage()
函数以 PVRTC 压缩格式识别二维图像。之后,计算像素数据大小,然后将其添加到偏移量中,以便在下一个 mip 中分组像素集。此过程会重复进行,直到没有更多 mip 需要处理为止。
// Initialize the texture unsigned int offset = sizeof(PVRHeaderV3) + pHeader->mMetaDataSize; unsigned int mipWidth = pHeader->mWidth; unsigned int mipHeight = pHeader->mHeight; unsigned int mip = 0; do { // Determine size (width * height * bbp/8), min size is 32 unsigned int pixelDataSize = ( mipWidth * mipHeight * bitsPerPixel ) >> 3; pixelDataSize = (pixelDataSize < 32) ? 32 : pixelDataSize; // Upload texture data for this mip glCompressedTexImage2D(GL_TEXTURE_2D, mip, format, mipWidth, mipHeight, 0, pixelDataSize, pData + offset); checkGlError("glCompressedTexImage2D"); // Next mips is half the size (divide by 2) with a min of 1 mipWidth = ( mipWidth >> 1 == 0 ) ? 1 : mipWidth >> 1; mipHeight = ( mipHeight >> 1 == 0 ) ? 1 : mipHeight >> 1; // Move to next mip offset += pixelDataSize; mip++; } while(mip < pHeader->mMipmapCount);
加载 S3TC
加载 S3TC 纹理文件、确定格式并读取完头部后,将进行 mipmap 支持。每个 mip 都会被遍历,像素会被分组到块中。然后,调用 glCompressedTexImage 函数以 S3TC 压缩格式识别二维图像。然后将块的聚合数据大小添加到偏移量中,以便移动到下一个 mip 并执行相同的操作。此过程会重复进行,直到没有更多 mip 需要处理为止。以下是一段初始化纹理并为 S3TC 压缩格式提供 mipmap 支持的代码。
// Initialize the texture // Uploading mipmaps unsigned int offset = 0; unsigned int width = pHeader->mWidth; unsigned int height = pHeader->mHeight; unsigned int mip = 0; do { // Determine size // As defined in extension: size = ceil(<w>/4) * ceil(<h>/4) * blockSize unsigned int Size = ((width + 3) >> 2) * ((height + 3) >> 2) * blockSize; glCompressedTexImage2D( GL_TEXTURE_2D, mip, format, width, height, 0, Size, (pData + sizeof(DDSHeader)) + offset ); checkGlError( "glCompressedTexImage2D" ); offset += Size; if( ( width <<= 1 ) == 0) width = 1; if( ( height <<= 1 ) == 0) height = 1; mip++; } while( mip < pHeader->mMipMapCount );
结论
根据其使用场景,适当的纹理压缩可以提高视觉质量,显著减小应用大小,并大大提高性能。最佳纹理压缩为开发者及其应用带来了显著的优势。Android 纹理压缩示例应用演示了如何加载和访问 Android 上最流行的纹理格式。下载源代码,并在您的下一个项目中集成最佳的纹理压缩。
关于作者
William Guo 在 Intel 个人形态设备团队实习期间,在从事 Intel 手机和平板电脑项目时创建了这个示例。他目前就读于加州大学伯克利分校,是一名冉冉升起的大二学生,预计毕业时间为 2015 年 5 月。他打算主修电气工程和计算机科学,辅修心理学。
ETC2 示例的更新艺术作品由 Intel 公司开发者关系部门游戏开发体验组首席数字内容设计师 Jeffery A. Williams 提供。