Mipmap 生成与移动端贴图压缩失真:ASTC 格式下的细节保留
在移动端游戏开发中,纹理通常占据了整包体积与运行时 GPU 带宽的 60% 以上。ASTC(Adaptive Scalable Texture Compression)作为跨 Android 与 iOS 平台的主流硬件纹理压缩标准,以其灵活的块大小和出色的压缩比被广泛应用。
然而,在面对高频细节(如微观法线、金属粗糙度 Mask、带有透明镂空的树叶)时,若不当配置 Mipmap 生成算法与 ASTC 块尺寸,会出现**法线高光反常模糊、植被远景叶片萎缩消失、通道图颜色渗色(Cross-channel Bleeding)**等典型失真。
Mipmap 核心价值与两大致命失真
Mipmap 是一组逐级减半的预滤波贴图金字塔。它不仅消除了纹理在高频远景下的欠采样锯齿(Aliasing)与摩尔纹,更关键的是确保了屏幕像素在投影到纹理空间(Texel Footprint)时始终与 L1/L2 纹理缓存行对齐,大幅降低了不规则跳跃采样的带宽惩罚。
失真 1:法线贴图均值滤波导致的几何平面化(Toksvig 效应)
传统 Mipmap 生成采用简单的 Box 滤波(4 像素算术平均)。对于三维单位法线向量 $\vec{N}$,当两个相反方向的法线 $(0.7, 0, 0.7)$ 和 $(-0.7, 0, 0.7)$ 混合时,平均结果为 $(0, 0, 0.7)$,其模长变为 $0.7$。
在片段着色器中重新执行normalize()后,法线方向直接变成了纯垂直方向 $(0, 0, 1)$。这意味着随着物体远离摄像机,原本凹凸不平的微观几何起伏在低级 Mipmap 中被强行抹平,导致远景物体失去所有微表面粗糙质感,呈现出反常的镜面塑料光泽。
失真 2:Alpha 镂空贴图的覆盖率萎缩(Alpha Coverage Shrinking)
对于树叶、铁丝网等使用 Alpha Test(clip())渲染的物体,低级 Mipmap 在均值混合时,边缘半透明像素会使原本处于阈值边缘的 Alpha 值持续被稀释变小。当 Alpha 低于 Cutout 阈值(如 0.5)时,整个叶片在远景 Mipmap 级别下会被全部剪裁剔除,造成“远看树木叶子全部掉光”的秃斑现象。
ASTC 压缩底层原理与块尺寸(Block Size)权衡
ASTC 将每块固定的128 比特(16 字节)物理内存划分到不同大小的像素网格上:
| 格式 | 块大小 (Texels) | 比特率 (bpp) | 压缩比 (相对 RGBA32) | 推荐应用场景 |
|---|---|---|---|---|
| ASTC 4x4 | 16 | 8.00 | 4:1 | 主角面部法线、主视角高精度武器特写 |
| ASTC 6x6 | 36 | 3.56 | 9:1 | 次世代角色/场景的 Albedo、环境法线 |
| ASTC 8x8 | 64 | 2.00 | 16:1 | 大多数静态场景贴图、地形地表、特效序列帧 |
| ASTC 10x10 | 100 | 1.28 | 25:1 | 远景低频植被、天空盒、环境光遮蔽图 |
ASTC 采用双端点颜色(Color Endpoints)加插值权重网格(Infill Weight Grid)的编码机制。当在同一个压缩块内部存在跨通道剧烈跳变(例如 R 通道是金属度 0,G 通道是粗糙度 1,B 通道是环境遮蔽 0.5)时,由于 128 比特容量不足以同时表达多个独立维度的梯度,会产生严重的通道间串扰与色斑渗色。
Toksvig 法线粗糙度补偿算法实现
为了解决法线 Mipmap 均值化造成的粗糙度失真,Toksvig 提出了经典修正理论:通过计算 Mipmap 生成时法线向量模长的收缩比例,反向补偿并放大材质的粗糙度值 $\alpha$:
$$F_{toksvig} = \frac{|\vec{N}{avg}|}{|\vec{N}{avg}| + \alpha^2 (1 - |\vec{N}_{avg}|)}$$
$$\alpha_{compensated}^2 = \frac{\alpha^2}{F_{toksvig}}$$
// HLSL 片段着色器:带有 Toksvig 滤波补偿的法线与粗糙度解析 void EvaluateToksvigRoughness( Texture2D normalTex, SamplerState sam, float2 uv, float rawRoughness, out float3 outNormalWS, out float outCompensatedRoughness) { // 采样原始法线纹理(包含 Mipmap 均值混合结果) float4 sampledNormal = normalTex.Sample(sam, uv); // 从 RG 压缩通道还原法线切线空间向量 float2 xy = sampledNormal.rg * 2.0 - 1.0; float lenSq = dot(xy, xy); float z = sqrt(max(0.0, 1.0 - lenSq)); outNormalWS = float3(xy, z); // 计算法线未归一化时的实际模长 (Length) // 模长越短,说明当前 Mip 级别混合了越多微观起伏 float normalLen = saturate(length(float3(xy, z))); // Toksvig 粗糙度展宽公式 float alpha = rawRoughness * rawRoughness; float alphaPrime = alpha / (normalLen + (1.0 - normalLen) * alpha); outCompensatedRoughness = sqrt(saturate(alphaPrime)); }Alpha 贴图 Mipmap 覆盖率保真(Coverage Preserving)
针对树木叶片远景萎缩,在导入贴图生成 Mipmap 时,必须使用基于保形算法(Coverage-Preserving Mipmap Generator)的 Scale 因子,自动缩放每一级 Mipmap 的 Alpha 通道:
#if UNITY_EDITOR using UnityEditor; using UnityEngine; public class TextureMipmapProcessor : AssetPostprocessor { private void OnPreprocessTexture() { TextureImporter importer = (TextureImporter)assetImporter; // 植被叶片纹理:启用 Alpha 保形滤波与 Mipmap Bias 修正 if (importer.assetPath.Contains("Foliage_Leaves")) { importer.mipmapFilter = TextureImporterMipFilter.BoxFilter; importer.mipMapsPreserveCoverage = true; // 开启 Alpha 覆盖率保护 importer.alphaCutoffValue = 0.5f; // 设定剪裁基准阈值 importer.streamingMipmaps = true; // 启用流式加载 } // 法线贴图格式硬性约束 if (importer.textureType == TextureImporterType.NormalMap) { // 移动端法线贴图最高给到 ASTC 6x6,核心主角可给 4x4,杜绝 8x8 及以上 TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings(); settings.format = TextureImporterFormat.ASTC_6x6; importer.SetPlatformTextureSettings(settings); } } } #endif通过引入 Toksvig 着色器粗糙度实时补偿、Alpha 覆盖率 Mipmap 保护机制以及基于贴图用途的 ASTC 梯度选型矩阵,游戏在移动端既能收获极小的纹理包体与低显存带宽,又能完美保持近景与远景微观光影的物理质感。