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





5.00/5 (5投票s)
Android* 纹理压缩 - 与代码示例的比较研究
Intel® Developer Zone 提供跨平台应用开发工具和操作指南、平台和技术信息、代码示例以及同行专业知识,以帮助开发者创新并取得成功。加入我们的社区,获取 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 中的 Mipmaps 支持包含在本示例中。
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 工具
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 的 DirectX 纹理工具(包含在 DX SDK 中)
访问纹理数据
大多数纹理压缩文件格式都有一个放在实际纹理数据之前的头。头通常包含有关纹理压缩格式名称、纹理宽度、纹理高度、纹理深度、数据大小、内部格式以及文件其他特定属性的数据。
我们的目标是从不同的纹理压缩文件中加载和映射纹理数据到 2D 模型,以比较质量和文件大小。头信息不应包含在要映射的纹理中,否则会导致图像/纹理表示失真。文件头都因考虑的压缩格式而异,因此,每个纹理压缩文件格式都需要单独支持才能正确加载和映射纹理。
重要提示
PVRTC 头因为存在 64 位像素格式数据成员(示例中的 mPixelFormat)而被打包。ARM 尝试通过填充 4 个额外字节来对齐头,使头总共为 56 字节,而不是原始的 52 字节。这反过来会导致图像在 ARM 设备上显示时失真。Intel 设备不填充头,因此不是问题。打包头可以解决 ARM 填充问题,并且纹理可以在 ARM 和 Intel 设备上正确显示。
加载和支持纹理格式
加载 PNG
PNG 中的 Mipmaps 由一个简单的函数处理: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 的 Personal Form Factor 团队实习期间,在从事 Intel 手机和平板电脑的工作时创建了这个示例。他目前在加州大学伯克利分校就读,即将成为一名二年级学生,预计于 2015 年 5 月毕业。他打算主修电气工程和计算机科学,辅修心理学。
该示例和文章已更新,以包含 ETC2 格式,更新者为 Cristiano Ferreira,他目前在 Intel 从事开发者关系工作。有关此示例的问题,可通过 Cristiano.ferreira[at]intel.com 联系 Cristiano。
ETC2 示例的更新艺术作品由 Jeffery A. Williams 提供,他是 Intel 公司开发者关系部游戏开发体验组的首席数字内容设计师。
资源
- 纹理映射 图 1
http://upload.wikimedia.org/wikipedia/commons/3/30/Texturedm1a2.png - Mipmapping 图 2
http://en.wikipedia.org/wiki/File:MipMap_Example_STS101.jpg - PNG* 信息