## 5.6 压缩纹理 https://www.aiknowledge.cn/images/cpp-game-engine-book/上一节解析了 internalformat 压缩纹理 glTexImage2D glTexImage2D internalformat GLRGB GLCOMPRESSEDRGB GPU-Z glTexImage2D stbiload glTexImage2D stopwatch glGetCompressedTexImage .webp。
CLion项目文件位于 samples\texture\draw_cube_texture_compress
https://www.aiknowledge.cn/images/cpp-game-engine-book/上一节解析了 `urban.webp
上传RGB数据到GPU,使用了下面的代码:
//3. 将图片rgb数据上传到GPU; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture2d->width_, texture2d->height_, 0, texture2d->gl_texture_format_, GL_UNSIGNED_BYTE, data);
接口定义如下:
/** * @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);
注意 internalformat 这个参数。
图片数据上传到到GPU后,可以选择保存的格式。
而我们这一章的主题是压缩纹理,那么我要讲的就是,可以保存为压缩格式。
为什么图片导入Unity之后,默认会设置为压缩格式?
在glTexImage2D这个接口里面,设置图片数据保存为压缩格式后,会有什么好处?
答案就是:节省显存。
如今的3A大作,各种4K贴图、超高精度模型,一个场景数据量几G,这些数据都是要上传到显存的,为了让游戏适配更多的硬件,开发者们也是各显神通,压缩纹理就是OpenGL官方提供的一种手段。
OpenGL支持的纹理数据格式如下:
| 纹理格式 | 压缩纹理格式 |
|---|---|
| GL_ALPHA | GL_COMPRESSED_ALPHA |
| GL_LUMINANCE | GL_COMPRESSED_LUMINANCE |
| GL_LUMINANCE_ALPHA | GL_COMPRESSED_LUMINANCE_ALPHA |
| GL_RGB | GL_COMPRESSED_RGB |
| GL_RGBA | GL_COMPRESSED_RGBA |
| GL_INTENSITY | GL_COMPRESSED_INTENSITY |
之前glTexImage2D这个接口的 internalformat 参数值是GL_RGB,现在只要设置为对应的压缩纹理格式GL_COMPRESSED_RGB即可。
//3. 将图片rgb数据进行压缩上传到GPU。 glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_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 左右。
对比Unity中压缩后的效果,差异不大。
启动时有一段时间的白屏,断点调试发现stbi_load 和 glTexImage2D耗时较长,这里引入stopwatch工具类来进行测量。
Texture2D* Texture2D::LoadFromFile(std::string& image_file_path) { ...... StopWatch stopwatch; stopwatch.start(); unsigned char* data = stbi_load(image_file_path.c_str(), &(texture2d->width_), &(texture2d->height_), &channels_in_file, 0); stopwatch.stop(); std::int64_t decompress_jpg_cost = stopwatch.milliseconds(); ...... stopwatch.restart(); //3. 将图片rgb数据进行压缩上传到GPU。 glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, texture2d->width_, texture2d->height_, 0, texture2d->gl_texture_format_, GL_UNSIGNED_BYTE, data); stopwatch.stop(); std::int64_t upload_and_compress_cost = stopwatch.milliseconds(); ...... }
耗时结果如下:
从硬盘加载jpg并解析得到RGB数据:1571 ms
压缩RGB数据上传到GPU:960 ms
这只是一张4096的图片,就花费了2.5s的时间,我们必须想办法优化。
制定优化方案之前,先理清楚为什么这么慢,主要是3个原因:
对应的解决办法就是:使用GPU支持的压缩纹理数据格式。
具体做法就是:将压缩好的纹理数据,保存到硬盘作为图片文件。
带来的好处是:
https://www.aiknowledge.cn/images/cpp-game-engine-book/OpenGL提供了接口glGetCompressedTexImage,将压缩好的纹理数据,从显存下载到内存,下一节将制作一个工具,对`.webp。