相比普通格式图片,纹理压缩可以节省大量显存和 CPU 解码时间,且对 GPU 友好。

背景

游戏开发中纹理是内存占用大户,移动设备因为内存有限,问题更加明显。据统计,淘宝互动小程序性能卡口 70% 以上都是因为内存超标,而内存超标的主要原因则是图片素材过多、过大等。

我们知道传统的图片文件格式有 PNG 、 JPEG 等,这种类型的图片格式无法直接被 GPU 读取,需要先经过 CPU 解码后再上传到 GPU 使用,解码后的数据以 RGB(A) 形式存储,无压缩。

而纹理压缩顾名思义是一种压缩的纹理格式,它通常会将纹理划分为固定大小的块(block)或者瓦片(tile),每个块单独进行压缩,整体显存占用更低,并且能直接被 GPU 读取和渲染(无需 CPU 解码),举例来说,一张1024x1024 的 JPEG 图片,使用RGBA格式,显存占用在 4M~5.3M 左右,而如果采用 ASTC_4x4 纹理压缩格式后,理论内存占用约在1.3M左右,相比普通纹理,可以减少70%+内存,具体数据见本文第三部分。

除此之外纹理压缩支持随机访问,随机访问是很重要的特性,因为纹理访问的模式高度随机,只有在渲染时被用到的部分才需要访问到,且无法提前预知其顺序。而且在场景中相邻的像素在纹理中不一定是相邻的 ,因此图形渲染性能高度依赖于纹理访问的效率。综上,相比普通格式图片,纹理压缩可以节省大量显存和 CPU 解码时间,且对 GPU 友好。

在WebGL上,我们可以通过相关 Extension 使用纹理压缩。纹理压缩的格式有很多种(详见下文),并且不同的厂商和机型支持的格式也不完全一致,因此使用压缩纹理前,需要判断设备是否支持。实际开发中,一般不会直接使用WebGL API加载压缩纹理,而是使用游戏引擎,目前主流的游戏引擎如 pixi.js 等均支持纹理压缩,开发者可以不用关心其中细节,只需要跟普通图片一样传入素材地址,剩下的都交给引擎来做。

纹理压缩不是银弹,虽然优势很多,但是其自身也有一些使用限制,主要有:

  • 有损压缩。所有的压缩纹理均为有损压缩,因此需要开发者 or 设计师验证压缩效果是否符合预期;

  • 尺寸要求。部分压缩纹理要求宽高相等(PVRTC),或者宽高必须是2的幂次方,使用有些不便;

  • 体积。压缩纹理虽然显存占用小,但是文件体积通常会比 JPEG 更大(看具体压缩格式),IO时间会更长;

  • 格式&兼容性问题。压缩纹理格式多样,需要针对不同平台选用不同格式,意味着同一份素材可能需要存储多份格式;

因此,是否需要使用压缩纹理需要开发者进行权衡,比如,游戏首帧资源我们通常希望越快约好,这时可以使用普通纹理,而对于非首帧资源或者出现内存瓶颈时则可以考虑使用纹理压缩。

主流纹理压缩格式、原理及兼容性情况

  纹理压缩格式

格式

WebGL扩展名

简介

ETC1

WEBGL_compressed_texture_etc1

ETC(Ericsson Texture Compression)是Khronos 开放标准,专利来自于瑞典爱立信公司,它在移动平台中广泛采用,是一种为感知质量设计的有损算法,它基于人眼对亮度而不是色度更敏感这一事实,在每个block定义四种不同的亮度偏移,即四种不同的颜色可用,可以认为这些颜色就是一个局部调色板,ETC会把4x4的像素块压缩成一个64或128位的数据块。

ETC有两种压缩格式:ETC1和ETC2。纹理长宽必须是2的幂次方;

  • ETC1基本所有Android机型都支持,但是缺陷是不支持Alpha通道;

  • ETC2 是ETC1的扩展,向下兼容ETC1。支持Alpha,但是需要开启OpenGL ES 3.x。

ETC2

WEBGL_compressed_texture_etc

ASTC

WEBGL_compressed_texture_astc

ASTC(Adaptive Scalable Texture Compression)是目前最强大的纹理压缩格式,由ARM & AMD研发。ASTC同样是基于block的压缩方式,但块的大小却较支持多种尺寸,比如从基本的4x4到12x12;每个块内的内容用128bits来进行存储,因而不同的块就对应着不同的压缩率;相比ETC,ASTC不要求长宽是2的幂次方。

PVRTC

WEBGL_compressed_texture_pvrtc

PVRTC(PowerVR Texture Compression),专为 PowerVR 图形核心系列设计,IOS平台都支持,它使用2张双线性放大的低分辨率图,根据精度和每个像素的权重,融合到一起来呈现纹理;PVRTC 2-bpp把一个8×4的像素单元组压成一个64位的数据块,压缩效果比较差;PVRTC 4-bpp把一个4×4的像素单元组压成一个64位的数据块;PVRTC压缩要求图片的大小必需是正方形而且边长必需是2的幂次方。

S3TC

WEBGL_compressed_texture_s3tc

S3TC(S3 Texture Compression)基本思想是把4x4的像素块压缩成一个64或128位的数据块,有损压缩。S3TC算法有五种变化DXT1-DXT5,一般在桌面设备上面使用,详见wiki。

  压缩纹理素材生产

如上所示,设计师产出png/jpeg等素材后,可以通过工具生成.ktx格式的压缩纹理素材,随后就可以在项目中直接使用了。不同格式的压缩纹理生产工具也不一样(PVETextTool、Adreno Texture Tool、...),而为了在各个平台中都能使用,通常需要生成不同格式的压缩纹理,社区有一些工具做了二次封装,可以生成多种格式的压缩纹理,如texture-compressor等。

  KTX文件格式

KTX(Khronos texture)是一种通用的纹理压缩存储格式,OpenGL(ES)、Vulkan等均支持,KTX文件中包含了纹理加载所需的所有参数及数据,比如format 、type、宽高等等,更多信息见wiki。

如下是一个ktx文件的内容:

基于这个格式,可以实现KTX Loader,用于解析KTX资源,生成纹理(通常游戏引擎会自带)。如下所示,读取KTX文件到ArrayBuffer然后解析拿到元信息:

// KhronosTextureContainer
constructor(arrayBuffer, facesExpected, baseOffset = 0) {this.arrayBuffer = arrayBuffer;this.baseOffset = baseOffset;// Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:// '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n'// 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0Aconst identifier = new Uint8Array(this.arrayBuffer, this.baseOffset, 12);if (identifier[0] !== 0xAB|| identifier[1] !== 0x4B|| identifier[2] !== 0x54|| identifier[3] !== 0x58|| identifier[4] !== 0x20|| identifier[5] !== 0x31|| identifier[6] !== 0x31|| identifier[7] !== 0xBB|| identifier[8] !== 0x0D|| identifier[9] !== 0x0A|| identifier[10] !== 0x1A|| identifier[11] !== 0x0A) {return;}// load the reset of the header in native 32 bit uintconst dataSize = Uint32Array.BYTES_PER_ELEMENT;const headerDataView = new DataView(this.arrayBuffer, this.baseOffset + 12, 13 * dataSize);const endianness = headerDataView.getUint32(0, true);const littleEndian = endianness === 0x04030201;this.glType = headerDataView.getUint32(1 * dataSize, littleEndian); // must be 0 for compressed texturesthis.glTypeSize = headerDataView.getUint32(2 * dataSize, littleEndian); // must be 1 for compressed texturesthis.glFormat = headerDataView.getUint32(3 * dataSize, littleEndian); // must be 0 for compressed texturesthis.glInternalFormat = headerDataView.getUint32(4 * dataSize, littleEndian); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,)this.glBaseInternalFormat = headerDataView.getUint32(5 * dataSize, littleEndian); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only)this.pixelWidth = headerDataView.getUint32(6 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,)this.pixelHeight = headerDataView.getUint32(7 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,)this.pixelDepth = headerDataView.getUint32(8 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,)this.numberOfArrayElements = headerDataView.getUint32(9 * dataSize, littleEndian); // used for texture arraysthis.numberOfFaces = headerDataView.getUint32(10 * dataSize, littleEndian); // used for cubemap textures, should either be 1 or 6this.numberOfMipmapLevels = headerDataView.getUint32(11 * dataSize, littleEndian); // number of levels; disregard possibility of 0 for compressed texturesthis.bytesOfKeyValueData = headerDataView.getUint32(12 * dataSize, littleEndian); // the amount of space after the header for meta-data...
}

  使用压缩纹理(WebGL)

在 WebGL 上使用纹‍理压缩主要‍有如下步骤:

  1. ‍下载纹理压缩素材;

  2. 解析ktx文件;

  3. 判断设备支‍持的纹理压缩格式;

  4. 通过getExtension获取纹理压缩扩展;

  5. 上传纹理压缩数据到GPU;

其中上传纹理主要指compressedTexImage2D、compressedTexImage3D‍‍‍两个API,其入参均可以从‍KTX文件中拿

var ext = gl.getExtension('WEBGL_compressed_texture_etc');var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA8_ETC2_EAC, 512, 512, 0, textureData);

  兼容性情况

  1. Android平台: Android平台由于机型、厂商众多,纹理压缩的支持情况较为复杂;其中ETC1支持的最为广泛,但是由于ETC1不支持Alpha通道,导致其使用场景有限,ETC2覆盖度也挺高但是需要启用OpenGL es 3.x;据google play统计,Android中高端机型对ASTC的支持度覆盖度有77%以上(具体到GPU型号上,高通骁龙415及以上(2015),ARM Mali T624(2012)及以上,NVIDIA Tegra k1(2014)及以上)

  2. iOS平台: iOS平台PVRTC格式支持最广泛,苹果也推荐使用此格式;在2013 A7芯片发布后,开始支持(ETC/ETC2)格式,2014 A8芯片及以上,开始支持ASTC格式;

综上,Android平台选用ETC + ASTC,iOS平台高版本使用ASTC、低版本PVRTC兜底即可覆盖所有设备。开发者运行时可以通过API glgetString(GL_EXTENSIONS)获取当前设备支持的压缩纹理格式,WebGL通过getSupportedExtensions()API获得相同信息。

纹理压缩性能表现

  体积

素材大小1024x1024:

结论:

  1. 相比jpeg等图片格式,纹理压缩通常体积会更大,这会导致IO时间变长;

  2. 不同格式的纹理压缩体积也不一样,压缩率高体积虽然降下来,但是素材质量会降低,使用时需要权衡;

  3. 纹理压缩格式GZip压缩效果不明显;

  下载时间 & 内存

测试机型: pixel4、iPhone11ProMax

游戏引擎: pixi.js

压缩纹理在小程序实际场景中的性能表现

批量加载JPEG纹理内存增长情况

批量加载ASTC纹理内存增长情况

结论:纹理压缩格式相比普通纹理内存优势巨大,可以减少50%以上内存占用,但与此同时,素材下载时间会延长;

  纹理上传GPU时间

不同纹理格式GPU上传时间

结论:纹理压缩格式GPU上传时间几乎可以忽略不记,相比普通纹理具有巨大的优势,也可以抵消一部分压缩纹理下载的耗时;

小程序Canvas纹理压缩实现方案

小程序下,我们是基于 OpenGL ES API 封装 WebGL API,纹理压缩也不例外,由于WebGL扩展中支持的纹理压缩格式在OpenGL ES中都有对应实现,比如 WEBGL_compressed_texture_astc扩展对应到GL的扩展名为 GL_KHR_texture_compression_astc_ldr等,因此只需要根据扩展名称映射到OpenGLES实现即可,比较简单,这里不再展开。

总结

纹理压缩在现代计算机图形中占据重要定位,现如今主流移动设备GPU都已支持纹理压缩,在实际场景中可以充分利用此能力优化游戏应用以带来更好的用户体验。

参考

  1. http://sv-journal.org/2014-1/06/en/index.php?lang=en#8

  2. https://developer.android.com/guide/playcore/asset-delivery/texture-compression

  3. https://docs.unity3d.com/es/2019.4/Manual/class-TextureImporterOverride.html

  4. https://blog.imaginationtech.com/pvrtc-the-most-efficient-texture-compression-standard-for-the-mobile-graphics-world/

  5. https://cesium.com/blog/2017/02/06/texture-compression/

团队介绍

阿里跨平台技术人才储备丰富,独行快,众行远,欢迎优秀的你加入【淘系终端体验平台-跨平台技术团队】,一起打造靠谱的跨平台方案!这里有H5容器、Weex、Flutter、小程序、游戏互动等诸多解决方案,既有技术深度也有广泛业务场景,欢迎优秀的小伙伴来一起搞事情,一起把技术做稳一起为业务提效,手淘跨平台技术团队欢迎你的加入!

✿  拓展阅读

作者|楚奕

编辑|橙子君

出品|阿里巴巴新零售淘系技术

内存优化: 纹理压缩技术相关推荐

  1. 纹理优化三-KTX纹理压缩

    KTX简介: KTX (Khronos Texture) 是一种纹理存储格式,大部分移动设备的 GPU 均支持这种格式.可以有效降低设备的显存占用,提高运行效率和稳定性. ktx是容器,astc是算法 ...

  2. 关于cocosCreator纹理压缩的介绍

    最近看关于cocosCreator的优化文章,发现纹理优化这块有很大的空间可以操作,于是打算写一篇关于纹理压缩的文章记录一下学习过程. 一.压缩纹理是什么 在游戏中纹理占用了相当大的包体积,但GPU无 ...

  3. S3TC- DirectX 6.0 标准纹理压缩

      简介: 纹理贴图是贴在3D对象上的位图,它可以在不增加3D场景中几何体复杂性的前提下得到逼真的表面细节.纹理贴图可以从木纹.大理石一直到复杂图形如人物.建筑.树木等等.为了模拟现实生活中的场景,必 ...

  4. DXT纹理压缩格式解析

    我们知道游戏中对于3D物体表面细节的表现最重要的还是靠贴图来实现的,那么越是高分辨率越是真彩色的贴图自然表现力也是越强,但是同时带来的问题是所需占用的内存会成倍的上升,而节省内存这一点在目前的游戏中还 ...

  5. DXT纹理压缩,Multiple Render Targets

    游戏中对于3D物体表面细节的表现最重要的还是靠贴图来实现的,那么越是高分辨率越是真彩色的贴图自然表现力也是越强,但是同时带来的问题是所需占用的内存会成倍的上升,而节省内存这一点在目前的游戏中还是非常非 ...

  6. unity 纹理压缩 内存优化

    1.界面打开慢可分为首次打开慢和再次打开慢,首次打开慢一般是由于需要加载过多的UI资源.而再次打开慢就是程序不合理造成的了.首次界面打开加载的资源(如:贴图)会被缓存在内存中,这样再次打开界面由于内存 ...

  7. 显存优化:纹理压缩功能介绍与使用说明

    由于近期在开发者群里发现一些开发者对纹理压缩不太理解,遇到一些使用上的问题,所以本次文章中对纹理压缩进行详细的说明和使用上的介绍,希望能对有需求的开发者带来帮助. 本篇文章已经先更新到官网的文档中,也 ...

  8. 倾斜摄影超大场景的三维模型的顶层合并的纹理压缩与抽稀处理技术分析

    倾斜摄影超大场景的三维模型的顶层合并的纹理压缩与抽稀处理技术分析 倾斜摄影超大场景的三维模型的顶层合并需要对纹理进行压缩和抽稀处理,以减小数据量和提高数据的传输和展示性能.以下是一种常用的纹理压缩和抽 ...

  9. 【MDCC技术大咖秀】Android内存优化之OOM

    大神分析的很全面,所以就转过来保存一份,转自:http://www.csdn.net/article/2015-09-18/2825737/1 以下为正文: Android的内存优化是性能优化中很重要 ...

最新文章

  1. 关于智能车竞赛总结 | 山东大学(威海) - 山魂五队
  2. poj1781In Danger(约瑟夫) 问题
  3. Android中的资源复用小技巧
  4. python怎么开发软件_怎么使用python进行软件开发
  5. python调用java文件_Python程序中调用Java代码的实践
  6. mybatis-plus中like的使用说明
  7. 离开北上广的互联网工程师最终都去了哪里?
  8. Javascript动态函数
  9. Ubuntu enca命令可以用来查看文件的编码格式
  10. Matlab求解微分、积分方程
  11. 汽车零部件开发工具OSEK NM协议栈源代码及配置功能
  12. 小信号谐振器电路仿真实验
  13. ROS Dst-Nat 后内网不能通过公网IP访问内网服务器解决方法
  14. 【毕业设计】深度学习YOLO抽烟行为检测 - python opencv
  15. 2020apple教育优惠购买策略
  16. Mac OS - 让Myeclipse10支持Retina显示屏
  17. 金山词霸不断的弹出窗口(金山词霸防欺诈提示),犹如病毒软件
  18. GRUB4DOS(十一) grldr可启动的软盘或硬盘分区的说明
  19. 超全面UI基础设计规范
  20. 网吧克隆——XP系统母盘制作全攻略

热门文章

  1. 2021年材料工艺学教程期末试卷(智慧树自测最新)
  2. 在C语言中十六进制与十进制的互相转换
  3. Design an Instagram
  4. 2022-2028年中国航空轮胎行业市场发展潜力及投资风险预测报告
  5. 小米2S手机,手机连接数据线到电脑上没反应(Win7/8)
  6. 第10周-自选阅读项目
  7. React学习笔记八-受控与非受控组件
  8. Matlab从三维矩阵中取出一列并且reshape
  9. 全国计算机应用考试模拟试题,全国计算机应用能考试模拟试题.doc
  10. 将分钟数换算成天数和年数