我的渲染技术进阶之旅关于OpenGL纹理压缩的相关资料

Posted 字节卷动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的渲染技术进阶之旅关于OpenGL纹理压缩的相关资料相关的知识,希望对你有一定的参考价值。

文章目录

一、为啥要了解压缩纹理

1.1 为啥要使用压缩纹理

为什么图片导入Unity之后,默认会设置为压缩格式?
glTexImage2D这个接口里面,设置图片数据保存为压缩格式后,会有什么好处?

/** 
 * @brief 将图片数据上传到GPU;
 * @param target    目标纹理,GL_TEXTURE_2D(2D纹理)
 * @param level     当图片数据是包含多个mipmap层级时,指定使用mipmap层级。
 * @param internalformat 图片数据上传到GPU后,在显存中保存为哪种格式?
 * @param width
 * @param height
 * @param border
 * @param format 上传的图片数据格式,RGB、RGBA、Alpha等
 * @param type 图片数据变量格式,一般都是GL_UNSIGNED_BYTE(0-255范围)
 * @param pixels 图片数据
 * @return
 */
void glTexImage2D(GLenum   target, GLint   level, GLint   internalformat, GLsizei   width, GLsizei   height, GLint   border, GLenum   format, GLenum   type, const void * pixels);

答案就是:节省显存。

如今的3A大作,各种4K贴图、超高精度模型,一个场景数据量几G,这些数据都是要上传到显存的,为了让游戏适配更多的硬件,开发者们也是各显神通,压缩纹理就是OpenGL官方提供的一种手段。

1.2 如何自定义压缩纹理以及使用压缩纹理的效果

1.2.1 使用压缩纹理节省显存

比如:之前glTexImage2D这个接口的 internalformat 参数值是GL_RGB

//3. 将图片rgb数据上传到GPU;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture2d->width_, texture2d->height_, 0, texture2d->gl_texture_format_, GL_UNSIGNED_BYTE, data);

现在只要设置为对应的压缩纹理格式GL_COMPRESSED_RGB即可。

//3. 将图片rgb数据上传到GPU;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture2d->width_, texture2d->height_, 0, texture2d->gl_texture_format_, GL_UNSIGNED_BYTE, data);

代码编译运行后,正常绘制了立方体,但是怎么样确认压缩纹理的效果呢,就是说怎么样确认显存占用降低?
这里借助GPU状态工具 - GPU-Z 来查看实时显存。

使用未压缩纹理,glTexImage2D接口调用前后显存对比如下:

从52m 变动到 116m,使用了约 64m 显存。

使用压缩纹理,接口调用前后显存对比如下:

从52m 变动到 60m,使用了约 8m 显存。

效果很明显,压缩纹理后,占用的显存是原来的 1/6 左右。

具体查看5.6 压缩纹理

1.2.2 自定义压缩纹理:将压缩好的纹理数据保存在本地

将压缩好的纹理数据,保存到硬盘作为图片文件。

带来的好处是:

  • 无需解析,直接上传GPU。
  • 数据已经压缩,无需再次压缩。
  • 数据已经压缩,上传数据量小

压缩纹理数据已经从GPU下载到内存,并保存为.cpt文件,cpt 是我取自compressed texture的缩写。

具体查看5.7 图片压缩工具

1.2.3 使用自定义的压缩纹理

带来的性能提升,如下所示:

解析耗时(ms)上传耗时(ms)
jpg1571960
cpt416

有很大的性能提升。

回顾一下,我们是先将.jpg图片,解析得到RGB数据,调用OpenGL API进行压缩上传至GPU,然后再从GPU下载压缩纹理数据,保存为.cpt文件。

这其实就是Unity导入图片的时候干的活,现在你知道为什么Unity导入图片那么慢了!

具体查看 5.8 使用压缩纹理

1.2.3 示例原理

截图的效果可以参考下面几篇博客:

二、纹理压缩相关知识

2.0 什么是压缩纹理

纹理压缩(Texture compression)是一种专为在三维计算机图形渲染系统中存储纹理而使用的图像压缩技术。与普通图像压缩算法的不同之处在于,纹理压缩算法为纹素随机存取做了优化。

在实际应用特别是游戏中纹理占用了相当大的包体积,而且GPU无法直接解码目前流行的图片格式,图片必须转换为RGB等类型的格式才能上传到GPU内存,这显然增加了GPU内存的占用。为了处理这些问题于是出现了GPU支持的压缩纹理格式,在GPU中进行解码。压缩纹理属于有损压缩,更在意解码速度,而编码在程序运行之前,因此速度较慢。

2.1 通用压缩纹理格式

2.2 压缩纹理相关API的使用

  1. 获得GPU的型号
glGetString(GL_RENDERER)
  1. 获得GPU的生产厂商
glGetString(GL_VENDOR);
  1. 获取GPU支持哪些压缩纹理
string extensions = (const char*)glGetString(GL_EXTENSIONS);
  1. 判断是否支持ETC1格式的压缩纹理
return (extensions.find("GL_OES_compressed_ETC1_RGB8_texture")!= string::npos);
  1. 判断是否支持DXT格式的压缩纹理
return (extensions.find("GL_EXT_texture_compression_dxt1")!= string::npos ||
    extensions.find("GL_EXT_texture_compression_s3tc")!= string::npos);
  1. 判断是否支持PVRTC格式的压缩纹理
return (extensions.find("GL_IMG_texture_compression_pvrtc")!= string::npos);
  1. 判断是否支持ATITC格式的压缩纹理
return (extensions.find("GL_AMD_compressed_ATC_texture")!= string::npos ||
    extensions.find("GL_ATI_texture_compression_atitc")!= string::npos);
  1. 上传压缩纹理数据
void glCompressedTexImage2D(
        GLenum target,
        GLint level,
        GLenum internalformat,
        GLsizei width,
        GLsizei height,
        GLint border,
        GLsizei imageSize,
        const GLvoid * data);

internalformat即是压缩纹理格式的类型。

  1. 查看设备支持的texture压缩格式
int num_formats;
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &num_formats);
std::cout<<"Texture extensions: "<<num_formats<<std::endl;

int *formats = (int*)alloca(num_formats * sizeof(int));
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);
for(int i=0; i<num_formats; i++)

    std::cout<<i<<" 0x"<<hex<<formats[i]<<dec<<std::endl;
 

//注意使用PVRTC格式纹理时,纹理的filter mode不能设置为 GL_LINEAR_MIPMAP_LINEAR, 
//否则的话加载出来的画线显示黑色, 这里有提到。
  1. glTexImage中指定压缩格式可以对上传的纹理进行压缩以改善内存使用,通过设置intenalFormat为表中一个值实现。通过这种方式进行图像压缩增加了纹理加载的开销,但却能够通过更有效地使用纹理存储空间来增加纹理性能,如果由于某些原因无法对纹理进行压缩,OpenGL就会使用下表中所列出的基本内部格式,并加载未经压缩的纹理。
GL_COMPRESSED_RGB : GL_RGB
GL_COMPRESSED_RGBA : GL_RGBA
GL_COMPRESSED_SRGB : GL_SRGB
GL_COMPRESSED_SRGB_ALPHA : GL_RGBA
GL_COMPRESSED_RED : GL_RED
GL_COMPRESSED_RG : GL_RG


除了这些压缩格式外,OpenGL中还加入了一些特定的压缩格式,即

  • GL_COMPRESSED_SIGNED_RED_RGTC1
  • GL_COMPRESSED_SIGNED_RED_RGTC2
  • GL_COMPRESSED_SIGNED_RG_RGTC2

它们用于各种单颜色通道和双颜色通道压缩纹理,他们代替了兼容版本中GL_LUMINANCEGL_LUMINANCE_ALPHA的功能

  1. 判断纹理是否被成功压缩 和 指定选择压缩格式的方式
//判断纹理是否被成功压缩 
GLint comFlag;
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_COMPRESSED,&comFlag);

//根据选择的压缩纹理格式,
//选择最快、最优、⾃行选择的算法⽅式选择压缩格式。 
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_FASTEST);  	//最快
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_NICEST);   	//质量最好
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_DONT_CARE);	//自行选择

参数说明:

  • target: GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
  • Level: 指定所加载的mip贴图层次。⼀一般我们都把这个参数设置为0。 internalformat:每个纹理理单元中存储多少颜⾊色成分。
  • width、height、depth参数: 指加载纹理理的宽度、⾼高度、深度。==注意!==这些值必须是2的整数次⽅方。(这是因为O
    旧版本上的遗留留下的⼀一个要求。当然现在已经可以⽀支持不不是2的整数次⽅方。但是开发者们还是习惯使⽤用以2的整数次⽅方去
    参数。)
  • border参数: 允许为纹理理贴图指定⼀一个边界宽度。

format、type、data参数:与我们在讲glDrawPixels函数对应的参数相同

glGetTexLevelParameter函数提取的压缩纹理格式如下:

GL_TEXTURE_COMPRESSED:如果纹理被压缩返回1,否则返回0
GL_TEXTURE_COMPRESSED_IMAGE_SIZE:获取压缩后的纹理大小(以字节为单位)
GL_TEXTURE_INTERNAL_FORMAT:所使用的压缩格式
GL_NUM_COMPRESSED_TEXTURE_FORMATS:支持的压缩纹理格式数量
GL_COMPRESSED_TEXTURE_FORMATS:支持的压缩纹理格式数组
GL_TEXTURE_COMPRESSION_HINT: 选择压缩格式的方式

GL_EXT_texture_compression_s3tc压缩格式如下:

2.3 压缩纹理的常见格式

基于OpenGL ES的压缩纹理有常见的如下几种实现:

  1. ETC1(Ericsson texture compression)
  2. ETC2(Ericsson texture compression)
  3. PVRTC (PowerVR texture compression)
  4. ATITC (ATI texture compression)
  5. S3TC (S3 texture compression)

ETC1

ETC1格式是OpenGL ES图形标准的一部分,并且被所有的android设备所支持。
扩展名为: GL_OES_compressed_ETC1_RGB8_texture不支持透明通道,所以仅能用于不透明纹理。 且要求大小是2次幂。
当加载压缩纹理时,参数支持如下格式: GL_ETC1_RGB8_OES(RGB,每个像素0.5个字节)

ETC2

ETC2ETC1 的扩展,压缩比率一样,但压缩质量更高,而且支持透明通道,能完整存储 RGBA 信息。

ETC2 需要 OpenGL ES 3.0(对应 WebGL 2.0)环境,目前还有不少低端 Android 手机不兼容,ios 方面从 iPhone5S 开始都支持 OpenGL ES 3.0。
ETC2 和 ETC1 一样,长宽可以不相等,但要求是 2 的幂次方。

PVRTC

支持的GPU为Imagination TechnologiesPowerVR SGX系列。
OpenGL ES的扩展名为: GL_IMG_texture_compression_pvrtc

当加载压缩纹理时,参数支持如下几种格式:

  • GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG (RGB,每个像素0.5个字节)
  • GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG (RGB,每个像素0.25个字节)
  • GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG (RGBA,每个像素0.5个字节)
  • GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG (RGBA,每个像素0.25个字节)

ATITC

支持的GPU为Qualcomm的Adreno系列。
支持的OpenGL ES扩展名为: GL_ATI_texture_compression_atitc
当加载压缩纹理时,参数支持如下类型的纹理:

  • GL_ATC_RGB_AMD (RGB,每个像素0.5个字节)
  • GL_ATC_RGBA_EXPLICIT_ALPHA_AMD (RGBA,每个像素1个字节)
  • GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD (RGBA,每个像素1个字节)

S3TC/DXTC

也被称为DXTC,在PC上广泛被使用,但是在移动设备上还是属于新鲜事物。支持的GPU为NVIDIA Tegra系列。

OpenGL ES扩展名为: GL_EXT_texture_compression_dxt1GL_EXT_texture_compression_s3tc
当加载压缩纹理时,参数有如下几种格式:

  • GL_COMPRESSED_RGB_S3TC_DXT1 (RGB,每个像素0.5个字节)
  • GL_COMPRESSED_RGBA_S3TC_DXT1 (RGBA,每个像素0.5个字节)
  • GL_COMPRESSED_RGBA_S3TC_DXT3 (RGBA,每个像素1个字节)
  • GL_COMPRESSED_RGBA_S3TC_DXT5 (RGBA,每个像素1个字节)

2.4 压缩纹理工具

每种压缩纹理以及相应的厂商都提供了压缩纹理的工具,包括可视化工具和命令行工具,可自行下载

  1. Imagination Technologies PowerVR
    PVETextTool

  2. Qualcomm Adreno
    Adreno Texture Tool

  3. ARM Mali
    Mail Texture Compression Tool

  4. nVIDIA Tegra
    DirectX Texture Tool

二、相关参考链接

以上是关于我的渲染技术进阶之旅关于OpenGL纹理压缩的相关资料的主要内容,如果未能解决你的问题,请参考以下文章

我的渲染技术进阶之旅关于OpenGL纹理压缩的相关资料

我的OpenGL学习进阶之旅关于OpenGL ES 绘制纹理,因为加载纹理坐标设置错误,导致纹理无法渲染的问题

我的OpenGL学习进阶之旅关于OpenGL ES 绘制纹理,因为加载纹理坐标设置错误,导致纹理无法渲染的问题

我的渲染技术进阶之旅收集到的关于KTX(Khronos Texture)的一些资料:用于OpenGLVulkan和其他GPU API的纹理轻量级容器

我的渲染技术进阶之旅收集到的关于KTX(Khronos Texture)的一些资料:用于OpenGLVulkan和其他GPU API的纹理轻量级容器

我的OpenGL学习进阶之旅收集的3D渲染技术之UVMap和Mesh相关资料