需要预先学习:


1. 为什么不用 PNG 和 JPG

纹理 里加载纹理用的是 png、jpg 这类常见图片格式,解码后上传给 glTexImage2D。这在学习阶段没问题,但看一下 GPU 视角就会发现两个问题:

解码成本。png 与 jpg 是为"存储与传输"设计的压缩:压缩率高,但必须整体解码成未压缩像素才能使用。GPU 无法直接采样一张 png——每帧渲染前都得把整张图解压,显存里实际存放的还是未压缩数据,压缩只在磁盘上省了空间。

随机访问。GPU 采样纹理是随机的:一次双线性插值(见 纹理)要读 4 个相邻纹素,三线性过滤还要跨 mip 层。jpg 的压缩依赖整张图的频域信息(DCT 变换块之间有关联),想取"坐标 (x,y)(x, y) 的一个像素",必须把周围一大片都解出来——随机访问的代价是灾难性的。

显存带宽。未压缩的 RGBA8 纹理每像素 4 字节。一张 4096×4096 的纹理 64 MB,加上 mipmap 约 85 MB;采样时的带宽、显存的占用,都会成为瓶颈(移动端尤其致命)。

GPU 纹理压缩(GPU texture compression,也称块压缩,block compression)为这三个问题而生,它的设计目标与 png/jj 完全不同:

目标 PNG / JPG 的取舍 GPU 纹理压缩的取舍
压缩率 高(1/10 ~ 1/20) 固定且温和(每像素 4~8 bit,即 1/4 ~ 1/2)
解码 CPU 整图解码,代价高 GPU 硬件实时解码,几乎免费
访问 顺序访问 随机访问:任意纹素只依赖自己所在的块
压缩时机 保存时 离线工具预压缩,运行时直接加载

固定压缩率是关键设计:每个块占的字节数固定,GPU 由纹理坐标直接算出块的位置与块内偏移,只解码需要的那个块。解码单元就做在纹理采样器旁边。


2. 块压缩的原理

主流 GPU 压缩格式共享同一套骨架,以 BC1(DXT1)为例:

  1. 把纹理切成 4×44 \times 4 像素的块(block / tile);
  2. 每块存两个 16 位 RGB 颜色(端点色,endpoint),通常一个偏亮、一个偏暗;
  3. 沿端点连线生成 4 个插值色,每像素用 2 bit 存一个索引(index),指出自己取 4 个插值色中的哪一个。
4x4 像素块(16 像素):
┌──┬──┬──┬──┐          块内存储:
│0 │1 │2 │1 │          端点色A(16bit RGB565)
├──┼──┼──┼──┤          端点色B(16bit RGB565)
│2 │2 │1 │0 │          + 16 像素 × 2bit 索引
├──┼──┼──┼──┤
│1 │0 │2 │2 │          总计 4 + 4 + 32 bit = 40 bit
├──┼──┼──┼──┤          每像素 2.5 bit,约为 RGBA8 的 1/5.3
│0 │1 │2 │0 │          解码:索引 -> 端点连线上的插值色
└──┴──┴──┴──┘

整块只有 8 字节(对比未压缩 4×4×4 = 64 字节)。压缩的思路本质是调色板的退化版:每块自带一个 4 色迷你调色板,颜色渐变区域几乎无损;色块边界比 4×4 细的细节会有损失(块与块之间各管各的调色板)。

各种格式的差异,基本都在"端点怎么存、索引几档、支持几个通道"上做文章。


3. 桌面端:BCn 家族

BC(Block Compression,Direct3D 侧称 DXT 或 DXTC / BCn)系列是桌面 GPU 的原生格式:

格式 旧名 每像素位深 内容 典型用途
BC1 DXT1 4 bit RGB(可 1 bit alpha) 无透明度的颜色贴图,最省
BC2 DXT3 8 bit RGB + 4 bit 显式 alpha 已被 BC3 取代,少见
BC3 DXT5 8 bit RGB + 索引式 alpha(第二个 BC1 块管 alpha) 带透明通道的颜色贴图
BC4 ATI1 4 bit 单通道 高度图、遮罩、灰度数据
BC5 ATI2 / 3Dc 8 bit 双通道(两个 BC4) 法线贴图(存 xx、yy)
BC6H — 8 bit RGB 半浮点(HDR) HDR 环境贴图(见 IBL)
BC7 — 8 bit 高质量 RGB(A),更智能的端点与索引模式 高质量颜色贴图,主流选择

两个值得展开的点:

BC4/BC5 是数据通道的压缩。它把端点从"颜色"变成"标量区间"——每块存最小值与最大值,16 个像素各自索引区间上的插值。BC5 两个独立通道恰好装下法线的 xx、yy,zz 用 z=1−x2−y2z = \sqrt{1 - x^2 - y^2} 重建(法线贴图见 法线贴图与TBN矩阵):法线贴图大部分区域是平滑渐变,BC5 几乎无损,而用 BC1/BC3 压法线会产生方向性噪声(RGB 各通道共享调色板,解出来的向量严重偏离单位长度)。

BC6H/BC7 是 DX11 时代的格式。BC6H 端点是半浮点 HDR 颜色,服务于 色调映射 的 HDR 流程;BC7 每块可在多种分区与索引精度模式中选择,质量接近未压缩,代价是编码器复杂(离线压缩慢,无所谓)。


4. 移动端:ETC 与 ASTC

移动 GPU(高通、Arm Mali、Apple)不支持 BCn,有自己的一套。

ETC(Ericsson Texture Compression):

  • ETC1:Android 早期的通用格式,只有 RGB、没有 alpha——透明贴图得拆成两张(颜色 + alpha 各一张)合成;
  • ETC2:ETC1 的扩展(OpenGL ES 3.0 起 GPU 必须支持),补全 RGBA,含 EAC(alpha 单通道)变体,对应关系类似 BC1~BC5。

ASTC(Adaptive Scalable Texture Compression,自适应可伸缩纹理压缩)是现在的移动端主流(OpenGL ES 3.2 / 大多数 2015 后的设备,Apple 全系 Metal 原生支持):

  • 块大小可变:从 4×44 \times 4 到 12×1212 \times 12 共十几种块尺寸可选——小块质量高(每像素 8 bit),大块压缩狠(每像素可低到 0.89 bit),质量与压缩率按需权衡,这是对 BCn 固定 4×44 \times 4 的最大改进;
  • 每块内部也有多种编码模式(双平面、权重范围、端点量化),细节丰富的地方自动用好模式;
  • 原生支持 HDR、法线贴图专用模式(类似 BC5 的通道分离思想);
  • 压缩工具按内容选择块尺寸,质量普遍优于 ETC2。
格式 平台支持 每像素位深 质量 现状
BCn 桌面(N/A/Intel)、主机 4 ~ 8 bit 固定 BC7 很好 桌面标配
ETC2 OpenGL ES 3.0+ 的 Android 4 ~ 8 bit 固定 一般 兼容兜底
ASTC ES 3.2 / Metal / Vulkan 现代设备 0.89 ~ 8 bit 可选 同码率下最好 移动端首选

注:跨平台项目(手机 + PC)通常为同一张纹理离线压缩多套格式(PC 用 BC7/BC5,移动用 ASTC),运行时按设备选择加载。引擎的资源系统按后缀或清单区分,如 brick_color.bc7 / brick_color.astc。


5. 在 OpenGL 中使用

压缩纹理的用法与普通纹理几乎一样,区别只在 internalFormat 指定压缩格式、数据是已经压缩好的块数据(不能把 png 塞进去让 OpenGL 压缩):

// BC7 压缩的 RGBA 纹理
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM,
                       width, height, 0, dataSize, compressedData);

// BC5 压缩的法线贴图
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RG_RGTC2,
                       width, height, 0, dataSize, compressedData);

// ASTC 4x4
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_4x4,
                       width, height, 0, dataSize, compressedData);

格式支持用扩展查询确认(GL_texture_compression_bptc、GL_KHR_texture_compression_astc_ldr 等)。压缩数据通常装在 KTX(Khronos Texture,跨平台容器,含 mip 链与格式描述)或 DDS(DirectDraw Surface,Windows 生态传统容器)文件里,用 ktx 库或 SOIL2 等加载,比自己解析省事。

5.1 压缩工具链

离线压缩是内容管线的固定环节:

工具 说明
Compressonator AMD 出品,GUI + CLI,BCn 全家
astcenc Arm 的 ASTC 官方编码器,可调块尺寸与质量档
nvtt / texturec NVIDIA Texture Tools / bgfx 的 texturec,跨格式批量
引擎内置 Unity / UE 的纹理导入设置(勾选压缩格式即可)

6. 工程注意事项

mipmap 逐级压缩。压缩发生在每一个 mip 级上,glGenerateMipmap 无法为压缩纹理生成 mip—— mip 链必须离线生成再逐级压缩,这也是 KTX 容器存在的意义之一。

sRGB 与压缩的叠加。颜色贴图是 sRGB 编码(见 伽马校正),压缩格式有对应的 sRGB 变体(如 GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM):硬件在解码块之后再做 sRGB → 线性转换。用错变体(线性格式装 sRGB 数据)会让颜色与 alpha 加权失真,这是移动端常见的"画面偏灰"来源之一。

法线、数据贴图不要用颜色格式压。整理一张选择表:

贴图类型 桌面推荐 移动推荐 备注
颜色 / albedo BC7(或 BC1/BC3 省空间) ASTC 6x6 / 8x8 用 sRGB 变体
法线贴图 BC5 ASTC(normal 模式或 4x4/5x5) 见 法线贴图与TBN矩阵
粗糙度 / 金属度 / 遮罩 BC4(单通道打包) ASTC 多张可打进通道复用一张 BC7
HDR 环境贴图 BC6H ASTC HDR 服务 IBL 预计算

压缩纹理不可写。压缩格式没有 CPU 侧的 glTexSubImage2D 局部修改的合理语义(块的端点色依赖全块内容),需要运行时写入的纹理(渲染目标、动态数据)只能用未压缩格式——压缩与 帧缓冲 的渲染到纹理互不相干,只作用于"采样用的静态纹理"。

尺寸必须对齐。块压缩要求纹理边长是块尺寸(BCn 为 4)的整数倍,或依赖硬件对边缘块的处理(ASTC 支持任意尺寸,边缘块自动裁剪)。离线工具一般会自动处理,手写加载器要注意补齐。

注:一张 4096×4096 的 RGBA8 纹理 64 MB,BC7 压到 8 bit/px 后是 16 MB,ASTC 8x8(2 bit/px)只要 4 MB——对带宽敏感的移动端,纹理压缩同时省下显存、加载时间与采样带宽三样,是所有引擎的必做项。它与 mipmap(纹理)、各向异性过滤一样,属于"上线前必须配置"的纹理基础项。