4、纹理优化策略:纹理压缩格式(ETC2、ASTC)、纹理图集(Texture Atlas)、Mipmap的使用与误区

纹理,是座舱图形渲染中最吃显存的一环。我见过不少项目,帧率上不去,一查,纹理带宽爆了。说白了,纹理优化就是给显存和带宽“减负”。今天聊聊三个核心手段:压缩格式、纹理图集、Mipmap。每个都有坑,我踩过不少。

4.1 纹理压缩格式:ETC2 与 ASTC

纹理压缩不是新鲜事。但选错格式,画面直接崩。我个人习惯,移动端座舱首选 ASTC,兼容性够好,质量也高。

ETC2:老牌选手,稳但不够强

ETC2 是 OpenGL ES 3.0 的标配。所有支持 Vulkan 的座舱芯片,基本都支持它。优点是兼容性极好,几乎没有设备不支持。

但缺点也明显:压缩比固定 4:1,不支持透明通道的独立压缩。我曾在项目中用 ETC2 压缩带 alpha 的按钮纹理,结果边缘锯齿严重。后来换成 ASTC,问题解决。

格式 压缩比 质量 适用场景
ETC2 4:1 中等 无透明、非关键纹理
ASTC 可变(4x4~12x12) UI、图标、HUD 元素

ASTC:灵活,但别乱用

ASTC 支持从 4x4 到 12x12 的块大小。块越小,质量越高,但显存占用也大。我建议:

  • UI 图标、小纹理:用 4x4 或 5x5,保证清晰度
  • 背景、大图:用 8x8 或 10x10,省显存
  • 法线贴图:用 6x6,平衡质量与性能
注意:ASTC 编码速度慢。项目迭代期,别每次改图都重新压缩。我习惯在最终打包阶段统一压缩,开发阶段用未压缩的 PNG。

4.2 纹理图集(Texture Atlas):合并不是万能药

纹理图集,就是把多个小图拼成一张大图。好处是减少 draw call,坏处是容易踩坑。

为什么用图集?

GPU 切换纹理是有开销的。你把 100 个图标拼成一张图,原本 100 次切换变成 1 次。座舱仪表盘上图标多,这个优化很有效。

我踩过的坑

我曾经把整个 HUD 的图标塞进一张 4096x4096 的图集里。结果呢?有些图标需要频繁更新(比如车速数字),每次更新都得重新上传整张图集。显存带宽直接炸了。

后来我学乖了:

  • 静态元素(背景、边框)放一张图集
  • 动态元素(数字、指针)单独放,或者用动态纹理更新
  • 图集尺寸别超过 2048x2048,否则 mipmap 生成会出问题
技巧:图集里每个子纹理之间留 2~4 像素的 padding。否则 mipmap 低层级时,相邻纹理的颜色会“串门”。我吃过这个亏,画面出现奇怪的色块。

4.3 Mipmap:用得好是宝,用不好是坑

Mipmap 是预先生成的一组分辨率递减的纹理。GPU 根据物体距离自动选择合适层级。好处是减少锯齿,提升渲染质量。但很多人用错了。

什么时候必须用?

  • 纹理在屏幕上缩小时(比如远处的地面、小图标)
  • 纹理有高频细节(文字、细线)
  • 需要各向异性过滤时(座舱的倾斜视角很常见)

什么时候别用?

  • UI 元素固定大小,不缩放(比如按钮、标签)
  • 纹理是动态生成的(比如摄像头画面)
  • 纹理图集里子纹理间距不够(mipmap 会采样到邻居)
核心原则:Mipmap 增加 33% 的显存占用。如果你的纹理从不缩小,就别开。座舱里很多 HUD 元素是固定大小的,开了 mipmap 纯属浪费。

误区:mipmap 层级选择

GPU 自动选择 mipmap 层级,但有时会选错。比如纹理在屏幕上刚好处于两个层级之间,GPU 会做 trilinear filtering,性能开销大。我建议:

  • 对性能敏感的场景,手动指定 mipmap 层级
  • 或者用 GL_TEXTURE_MIN_LODGL_TEXTURE_MAX_LOD 限制范围
// 限制 mipmap 层级范围,避免不必要的开销
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 3);

4.4 总结:我的建议

纹理优化没有银弹。我个人的工作流是这样的:

  1. 静态 UI 元素:ASTC 6x6 + 纹理图集(2048x2048 以内)
  2. 动态元素:单独纹理,不压缩,用 RGBA 直接上传
  3. 背景大图:ASTC 10x10 + mipmap
  4. 图标、小纹理:ASTC 4x4,不开 mipmap

嗯,大概就是这样。纹理这块,多测试,多调参。每个座舱项目的硬件都不一样,别指望一套方案打天下。