3. 资源压缩基础:纹理压缩(ETC2/ASTC)、音频压缩(Vorbis/Opus)、模型LOD与网格压缩
资源压缩,说白了就是给游戏资源「减肥」。我见过太多项目,美术资源动辄几百兆,结果在低端机上直接闪退。你想想看,一张 2048×2048 的 RGBA 纹理,不压缩就是 16MB,一个场景贴几十张,内存直接爆炸。
这一章我们聚焦三个核心方向:纹理、音频、模型。每个方向我都会给出实战中验证过的方案和参数。
核心原则:压缩不是无脑压到最小,而是在「视觉/听觉可接受」的前提下,把带宽和内存压到最低。
3.1 纹理压缩:ETC2 与 ASTC
纹理压缩是资源减肥的大头。移动端目前主流就两个格式:ETC2 和 ASTC。
3.1.1 ETC2:Android 的底线
ETC2 是 OpenGL ES 3.0 的强制要求。换句话说,2014 年之后的 Android 设备都支持。它的压缩比固定为 4:1(RGBA),每像素 4 位。
| 特性 | ETC2 RGB | ETC2 RGBA |
|---|---|---|
| 压缩比 | 6:1 | 4:1 |
| 每像素位数 | 4 bpp | 8 bpp |
| Alpha 通道 | 不支持 | 支持(1 位或 8 位) |
| 质量 | 良好 | 中等 |
我的习惯:不带 Alpha 的漫反射贴图用 ETC2 RGB,带 Alpha 的用 ETC2 RGBA。但注意,ETC2 的 Alpha 质量一般,如果边缘有半透明渐变,建议换 ASTC。
3.1.2 ASTC:高通的王牌
ASTC 是 ARM 推出的格式,灵活性远超 ETC2。它支持从 8×8 到 4×4 的块大小,对应 0.5 bpp 到 8 bpp。我一般在项目中这样选:
- UI 贴图:ASTC 4×4(8 bpp),质量接近原始 RGBA
- 场景漫反射:ASTC 6×6(3.56 bpp),体积和质量的平衡点
- 法线贴图:ASTC 5×5(5.12 bpp),保留细节
- HDR 环境贴图:ASTC 8×8(0.5 bpp),反正没人盯着看
// Unity 中批量设置 ASTC 压缩格式的脚本片段
using UnityEditor;
using UnityEngine;
public class TextureCompressor : EditorWindow
{
[MenuItem("Tools/Batch Set ASTC")]
static void SetASTC()
{
string[] guids = AssetDatabase.FindAssets("t:Texture2D",
new[] { "Assets/Textures" });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
TextureImporter importer = AssetImporter.GetAtPath(path)
as TextureImporter;
if (importer == null) continue;
// 设置 Android 平台为 ASTC 6×6
TextureImporterPlatformSettings settings =
importer.GetPlatformTextureSettings("Android");
settings.overridden = true;
settings.format = TextureImporterFormat.ASTC_6x6;
importer.SetPlatformTextureSettings(settings);
importer.SaveAndReimport();
}
Debug.Log("批量压缩完成");
}
}
我曾经踩过的坑:有一款游戏在小米 6 上跑得好好的,换到华为麒麟 990 上纹理全花了。查了半天,发现是 ASTC 的块大小选得太小(4×4),部分 GPU 的硬件解码器对 4×4 支持不完整。后来统一改成 6×6,问题解决。
3.2 音频压缩:Vorbis 与 Opus
音频压缩常被忽视,但它的体积占比其实不小。一个 3 分钟的 BGM,44.1kHz 16bit 立体声 WAV 是 30MB。不压缩?项目包体直接翻倍。
3.2.1 Vorbis:老当益壮
Vorbis(.ogg)是游戏界的老兵。它的优势是解码速度快,兼容性极好。我一般用在:
- 背景音乐:128-192 kbps,人耳几乎听不出区别
- 环境音效:96-128 kbps,反正被其他声音盖住
- 语音对白:64-96 kbps,保留中频清晰度
3.2.2 Opus:新一代王者
Opus 是后起之秀。同样码率下,它的音质比 Vorbis 好 15-20%。我个人的建议是:
| 场景 | 推荐格式 | 码率 | 理由 |
|---|---|---|---|
| BGM | Opus | 96 kbps | 体积比 Vorbis 小 30%,音质更好 |
| 语音 | Opus | 48 kbps | 专门优化过语音编码 |
| 短音效 | Vorbis | 128 kbps | 解码延迟更低 |
| 旧设备兼容 | Vorbis | 128 kbps | 部分老 GPU 硬解不支持 Opus |
// FFmpeg 批量转换音频脚本
# 转 Opus(推荐)
ffmpeg -i input.wav -c:a libopus -b:a 96k output.ogg
# 转 Vorbis(兼容)
ffmpeg -i input.wav -c:a libvorbis -b:a 128k output.ogg
# 批量处理
for f in *.wav; do
ffmpeg -i "$f" -c:a libopus -b:a 96k "${f%.wav}.ogg"
done
避坑指南:我曾经把一段 5 分钟的 BGM 压成 Opus 32 kbps,结果在游戏里听起来像收音机。后来定了个规矩:BGM 不低于 96 kbps,语音不低于 48 kbps。低于这个阈值,压缩伪影会严重影响体验。
3.3 模型 LOD 与网格压缩
模型压缩分两个层面:LOD(细节层次)和网格数据压缩。前者减少绘制三角形数,后者减少顶点数据量。
3.3.1 LOD 策略
LOD 的核心是「远处少画点」。我常用的策略是:
- LOD0:原始模型,100% 顶点数,近距离使用
- LOD1:减面 50%,中距离使用
- LOD2:减面 75%,远距离使用
- LOD3:减面 90%,极远距离或使用 Billboard
切换距离怎么定?我有个经验公式:
// LOD 切换距离计算
// 假设模型包围盒半径为 R
float lod0Distance = R * 5; // 5倍半径内用最高模
float lod1Distance = R * 15; // 15倍半径内用中模
float lod2Distance = R * 40; // 40倍半径内用低模
// 超过 40 倍半径,用 Billboard 或直接不渲染
3.3.2 网格压缩
网格数据压缩主要针对顶点属性:位置、法线、UV、切线。我推荐使用 OpenGL 的 GL_OES_vertex_half_float 扩展:
| 属性 | 传统格式 | 压缩格式 | 节省 |
|---|---|---|---|
| 位置 | float × 3 (12B) | half × 3 (6B) | 50% |
| 法线 | float × 3 (12B) | SNORM × 4 (4B) | 67% |
| UV | float × 2 (8B) | half × 2 (4B) | 50% |
| 切线 | float × 4 (16B) | SNORM × 4 (4B) | 75% |
// 使用 half float 的顶点结构体(C++)
struct CompressedVertex {
// 位置:half float
uint16_t x, y, z;
// 法线:SNORM 4 字节(x,y,z,w),w 存储符号
int8_t nx, ny, nz, nw;
// UV:half float
uint16_t u, v;
// 切线:SNORM 4 字节
int8_t tx, ty, tz, tw;
// 总大小:6 + 4 + 4 + 4 = 18 字节
// 原始大小:12 + 12 + 8 + 16 = 48 字节
// 压缩率:62.5%
};
注意:half float 的精度范围是 ±65504,但精度只有约 3.3 位十进制。如果你的场景坐标超过 1000 单位,位置用 half 会丢精度。我一般只在角色模型上用 half,地形和建筑用 float。
3.4 本章知识体系
下面这张图总结了资源压缩的完整链路。你可以把它贴在工位上,每次做资源优化时对照着看。