入门指南
一张纹理进入 GPU 之后发生了什么
一张 300 KB 的 PNG,可能悄悄占掉 5 MB 显存。这篇指南会讲清楚原因、GPU 如何用块压缩把纹理变小,以及该怎么选格式。每个概念都配有可以动手试的演示。
第 1 章
文件大小不等于显存大小
PNG 和 JPEG 是为存储和传输图片设计的。GPU 在绘制纹理之前,要先把图片解开成原始纹素,通常每个纹素 4 字节。
GPU 每帧要对这些纹素采样数百万次,所以它们必须随时可读。因此显存占用只取决于分辨率和像素格式,和文件压得多小无关。再加上 mipmap(给远处表面用的逐级缩小副本),还要多出大约三分之一。
- RGBA8每纹素 32 位213 MiB基准每张 21.3 MiB
- BC7 · ASTC 4×4每纹素 8 位53.3 MiB小 4 倍每张 5.3 MiB
- BC1 · ETC2 RGB每纹素 4 位26.7 MiB小 8 倍每张 2.7 MiB
- ASTC 6×6每纹素 3.56 位23.8 MiB小 9 倍每张 2.4 MiB
- ASTC 8×8每纹素 2 位13.3 MiB小 16 倍每张 1.3 MiB
- ASTC 12×12每纹素 0.89 位6 MiB小 35.7 倍每张 612 KiB
块格式在每一级 mipmap 都会向上取整到完整的块,因此这里的数字与 GPU 实际分配的一致。
第 2 章
为什么 GPU 不能直接用 PNG
着色器采样纹理时,可能按任意顺序读取任意一个纹素,硬件必须立刻找到它。
PNG 和 JPEG 使用变长编码:想读中间某个像素,必须先把它前面的数据全部解码。GPU 格式则是定长的,每个 4×4 块占用完全相同的字节数,所以任意纹素的地址只需一次乘法就能算出来。纹理在显存里始终保持压缩状态,由纹理单元在读取时实时解码。
可以用方向键移动选中位置。
- 选中的纹素
- x 13,y 18
- 所在的块
- 第 3 列,第 4 行
- 在 BC1 中的地址
- (4 × 8 + 3) × 8 字节 = 第 280 字节
64 个块在内存中的排列
同一个纹素在 PNG 中
必须先解压第 0 到第 18 行(32 行中的 19 行)
第 3 章
拆开一个 4×4 块
BC1 是最早被广泛使用的格式,最能直观说明原理:它只用 8 字节存下 16 个纹素。
编码器先选出两个端点颜色。解码器把它们混合成一条直线上的 4 个颜色,每个纹素只记一个 2 位编号,指向离自己最近的那个颜色。颜色变化平滑的块能保留得很好;混有多种无关颜色的块就会失真。
第 4 章
格式很多,取舍各不相同
较新的格式比 BC1 更会花每一位:更多端点、一个块内多条颜色线、更好的透明度,或者可选的块大小。
BC7 和 ASTC 4×4 每纹素 8 位,效果接近原图。ASTC 通过块大小在质量和体积之间取舍:每个块固定 128 位,所以 12×12 的块平均每纹素不到 1 位。Basis Universal 走的是另一条路:只发布一个紧凑文件,到设备上再转码成该 GPU 支持的格式。
| 格式 | 每纹素位数 | 透明度 | 常见平台 | 适合 |
|---|---|---|---|---|
| BC1 | 4 | 1 位 | 桌面、主机 | 显存紧张时的不透明颜色 |
| BC3 | 8 | 支持 | 桌面、主机 | 旧内容中带平滑透明度的颜色 |
| BC4 | 4 | 数据通道 | 桌面、主机 | 单通道:遮罩、粗糙度、高度 |
| BC5 | 8 | 数据通道 | 桌面、主机 | 法线贴图(X 和 Y) |
| BC6H | 8 | 不支持 | 桌面、主机 | 天空盒、光照贴图等 HDR 颜色 |
| BC7 | 8 | 支持 | 桌面、主机 | 高质量颜色,有无透明度均可 |
| ETC1 | 4 | 不支持 | 几乎所有 Android | 非常老的设备上的不透明颜色 |
| ETC2 | 4–8 | 支持 | Android、iOS | 支持广泛的移动端兜底格式 |
| ASTC | 0.89–8 | 支持 | 较新的 Android 和 iOS、Apple Silicon | 移动端可调节的质量 |
| PVRTC | 2–4 | 支持 | 较老的 iPhone 和 iPad | 仅限较老的 Apple 设备 |
| Basis ETC1S | 取决于转码目标 | 支持 | 任意 GPU(通过 KTX2) | 下载体积小,任意 GPU 可用 |
| Basis UASTC | 取决于转码目标 | 支持 | 任意 GPU(通过 KTX2) | 质量高,任意 GPU 可用 |
点击“运行编码器”压缩示例图。第一次运行时会下载编码器。
第 5 章
如何选择格式
两个问题就能很快缩小范围:哪些 GPU 要加载这张纹理?它里面存的是什么数据?
颜色纹理、法线贴图和单通道遮罩对压缩的反应各不相同;桌面、移动和 Web 平台支持的格式家族也不一样。先选出你的场景得到一个合理的起点,再用自己的纹理检查效果。
建议起点
ASTC 6×6
也可以考虑: ASTC 4×4 · ETC2 RGB
较新的 Android 和 iOS GPU 都支持 ASTC。6×6 是较好的平衡点;重点资源用 4×4,老设备用 ETC2。
打开 ASTC 编码器第 6 章
实用建议
养成几个习惯,就能避开大多数纹理压缩问题。
- 01
颜色标记为 sRGB,数据保持线性
反照率和 UI 纹理属于 sRGB。法线、粗糙度等数据贴图必须保持线性,否则光照会出错。
- 02
给法线贴图单独的格式
使用 BC5 或 EAC RG11 这类双通道格式,在着色器里重建 Z。用颜色格式既浪费位数,又会出现明显的凹凸瑕疵。
- 03
生成 mipmap
大约多占 33% 显存,但能避免远处表面闪烁,还能让采样更快。
- 04
尺寸要对齐块大小
大多数格式以 4×4 块为单位,尺寸能被 4 整除就不需要填充。PVRTC 等旧格式还要求边长是 2 的幂且为正方形。
- 05
用真实内容在真实设备上检查
放大到原始尺寸对比解码结果和原图,并确认目标 GPU 支持所选格式。
常见问题
GPU 纹理压缩是无损的吗?
不是。BC、ETC、ASTC 和 PVRTC 都是每块固定大小的有损格式,损失多少取决于格式、块大小和图像内容。
为什么不直接用 PNG 或 JPEG 当纹理?
它们下载更快,但 GPU 无法直接采样,所以会被解码成原始纹素,占用完整的显存。GPU 格式在显存里同样很小。
哪种纹理格式最好?
桌面端 BC7 是稳妥的默认选择,较新的移动设备用 ASTC;如果一个文件要在很多种 GPU 上运行(比如 Web),用 KTX2 + Basis Universal。
“每纹素位数”是什么意思?
它表示平均每个纹素占用的存储。原始 RGBA8 是 32 位,BC1 是 4 位,BC7 是 8 位,ASTC 随块大小从 8 位一直降到约 0.89 位。
用你自己的纹理试试
所有编码器都在你的浏览器本地运行。上传 PNG 或 JPEG,选择格式,然后对比结果。