贴图导入面板最迷惑人的地方在这:选了 "Lossless",磁盘上的 .ctex 体积确实很小,但显存占用一点没省。因为 Lossless 只是把 PNG 数据原样塞进去,加载时解码回 RGBA8 上传 GPU——磁盘格式决定体积,GPU 格式决定显存,两码事。VRAM 才是瓶颈的场景(手游、大场景 2D 素材)必须上块压缩,这篇把四种格式的数学和 Godot 的选择逻辑讲透。
先算账:未压缩的代价
一张 $1024 \times 1024$ 的 RGBA8 贴图,显存占用是:
$$1024 \times 1024 \times 4\ \text{B} = 4\ \text{MB}$$
开 mipmap 再乘以 $\frac{4}{3}$(几何级数求和 $\sum_{i=0}^{\infty} 4^{-i} = \frac{4}{3}$),约 5.3 MB。一个角色的 albedo + normal + ORM 三件套就是 16 MB,一百个角色 1.6 GB——还没算场景。磁盘上它可能只是个 200 KB 的 PNG,但 GPU 看到的是解码后的原始字节。
块压缩的数学
VRAM 压缩格式全是块压缩:把图像切成固定大小的块,每块编码成定长字节,解压由 GPU 硬件完成(采样时逐块实时解压,显存里存的一直是压缩态)。所以显存 = 像素数 ÷ 每块像素数 × 每块字节数,和图像内容无关:
| 格式 | 块大小 | 块字节 | 位深 | $1024^2$ 含 mip |
|---|---|---|---|---|
| BC1(S3TC) | $4\times4$ | 8 B | 4 bpp | $\approx 0.7$ MB |
| BC7(BPTC) | $4\times4$ | 16 B | 8 bpp | $\approx 1.4$ MB |
| ETC2 | $4\times4$ | 16 B | 4 bpp(无 alpha) | $\approx 0.7$ MB |
| ASTC 6×6 | $6\times6$ | 16 B | 3.56 bpp | $\approx 0.6$ MB |
以 BC1 为例验算:$1024^2 / (4\times4) = 65536$ 块,乘 8 B 得 512 KB,乘 $\frac{4}{3}$ 约 0.7 MB——只有 RGBA8 的七分之一。BC1 的编码方式是每块存两个 16 位 RGB565 端点加 16 个 2 位索引,每像素从端点插值出一个颜色,所以 4 bpp 且不支持 alpha。要 alpha 得用 BC3/BC7/ETC2 RGBA,位深翻倍。
ASTC 的独特之处是块尺寸可变:同一块 16 字节可以摊到 $4\times4$(8 bpp,质量最高)到 $12\times12$(0.89 bpp)不等,块越大压缩率越高、失真越明显。给 albedo 用 6×6、给法线贴图用 5×5 或 4×4 是常见搭配。
平台支持是硬约束
格式选择首先不是质量问题,是硬件支持矩阵:
- BC1-BC7:桌面 GPU(NVIDIA/AMD/Intel)全覆盖,移动芯片基本不支持。
- ETC2:OpenGL ES 3.0 / Vulkan 的安卓标配,苹果芯片不支持。
- ASTC:iOS 8 以后的苹果设备、近年的中高端安卓都支持,Metal 原生。
- PVRTC:老 iOS 专用,Godot 4 已移除导入支持——ASTC 设备覆盖率足够高,不值得为它维护一条格式分支。
Godot 的解法是"一份源、多份产物":.ctex 文件可以同时内嵌多种压缩格式,运行时按 GPU 支持度挑一份解码。项目设置 rendering/textures/vram_compression/import_etc2_astc(默认开)控制是否把 ETC2/ASTC 数据也打进产物。关掉它,移动端产物只有 BC 数据,ETC2-only 的老设备直接黑图——出包前务必确认目标设备清单。
Godot 的四种压缩模式
导入面板 compress/mode 四个选项,本质是两个维度的取舍:
Lossless:PNG 编码存盘,加载时解码为 RGBA8 上传。磁盘省、显存不省、加载慢(解码在 CPU)。适合 UI 图集这种小且要求无损的。
Lossy:有损(WebP 系)存盘,其余同上。磁盘更小,显存照样 RGBA8。
VRAM Compressed:上面那套块压缩。磁盘和显存双省,加载几乎零 CPU 成本(数据原样 memcpy 上卡)。代价是有损——BC1 的 RGB565 端点对渐变图会产生色带,法线贴图压缩失真会转成光照噪点。这是游戏素材的默认选择。
Basis Universal:先把纹理编码成一种中间格式(ETC1S/UASTC),运行时再由 GPU 驱动转码成目标格式。磁盘体积最小(比直接存 ASTC 还小),一份数据适配所有平台。代价是转码发生在加载时,CPU 耗时每张大几十毫秒,冷加载一批贴图时卡顿明显。适合超在意下载体积的 Web/手游发行。
我的选择流程
2D 像素游戏、素材总量几百 MB 以内:Lossless 全默认,不用想。3D 项目或 2D 大素材:albedo/ORM 用 VRAM Compressed(ASTC 6×6),法线贴图单独 Override 成小 block 的 ASTC 或干脆 Lossless 保精度,UI 保持 Lossless。要压下载体积再上 Basis Universal,但记得用 load_threaded_request 提前量加载,把转码开销摊进 loading 界面。最后拿 RenderingServer 那篇说的 get_rendering_info 核一遍显存占用,数字不对说明有纹理漏了压缩。