4、纹理映射技术:UV坐标、纹理过滤、Mipmap与各向异性过滤

纹理映射,说白了就是把一张图片“贴”到3D模型上。没有它,我们的3D世界就是一堆光秃秃的线框,毫无细节可言。我刚开始做渲染引擎时,觉得纹理映射不就是个坐标映射嘛,简单。后来踩了无数坑才发现,这里面的门道深着呢。

4.1 UV坐标:模型与纹理的桥梁

每个3D模型的顶点,除了位置、法线,通常还带有一组UV坐标。U和V的取值范围一般是0到1,分别对应纹理图片的横向和纵向。

举个例子,一个立方体的正面,四个顶点的UV可能是(0,0)、(1,0)、(0,1)、(1,1)。这样纹理图片就能完整地覆盖这个面。

但这里有个坑:模型师在展UV时,如果不注意接缝和拉伸,渲染出来的效果会非常难看。我在项目中遇到过,一个角色模型的头部UV拉伸严重,导致眼睛纹理变形。排查了半天,最后发现是模型导出时UV坐标精度丢失了。

注意:UV坐标的精度问题。在移动端或低精度设备上,float16的UV可能导致纹理闪烁。我建议在关键模型上使用float32精度。

4.2 纹理过滤:点采样、双线性与三线性

纹理过滤解决的是“像素与纹素对应关系”的问题。你想想看,屏幕上的一个像素,映射到纹理上可能不是一个整数坐标,这时候就需要插值。

4.2.1 点采样(Nearest)

最简单粗暴。取最近的纹素。效果嘛,就是像素感十足,像马赛克。适合像素风格游戏,或者不需要平滑过渡的场景。

4.2.2 双线性过滤(Bilinear)

取周围4个纹素,做两次线性插值。效果平滑很多。这是最常用的过滤方式。我个人习惯在UI渲染上使用双线性,因为UI通常不需要太高的纹理细节,平滑就够了。

4.2.3 三线性过滤(Trilinear)

在双线性的基础上,再对两个Mipmap层级做插值。说白了就是“双线性 + Mipmap层级混合”。效果更好,但开销也更大。

我的经验:在移动端,三线性过滤的开销其实没有想象中那么大。现代GPU的纹理单元硬件支持得很好。我曾经在低端机上测试过,性能损失不到5%,但画质提升明显。

4.3 Mipmap:解决远处纹理闪烁的利器

为什么需要Mipmap?想象一下,一个巨大的纹理贴在一个很小的屏幕上。远处的物体,一个像素可能对应纹理上的几十个纹素。如果只用原始纹理,就会产生严重的锯齿和闪烁。

Mipmap就是预先生成的一系列分辨率递减的纹理。从原始尺寸开始,每次缩小一半,直到1x1。渲染时,根据像素覆盖的纹理区域大小,选择合适的层级。

// 生成Mipmap的伪代码
for (int level = 1; level < maxLevel; ++level) {
    int width = previousWidth / 2;
    int height = previousHeight / 2;
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            // 对上一级的2x2区域做平均
            mip[level][y][x] = average(mip[level-1][2y][2x], ...);
        }
    }
}

Mipmap的层级选择:通常根据屏幕空间导数(dFdx/dFdy)来计算。GPU硬件会自动计算,我们只需要设置好采样器的Mipmap模式。

核心要点:Mipmap不是万能的。它解决了远处纹理的闪烁问题,但近处纹理的细节丢失是它的代价。所以,纹理的原始分辨率要足够高。

4.4 各向异性过滤:解决倾斜视角的模糊

这是纹理过滤的终极形态。你想想看,当视角与纹理表面成锐角时,纹理在屏幕上的投影是严重拉伸的。Mipmap只能处理正方形区域,但实际需要的纹理区域是细长的矩形。

各向异性过滤(Anisotropic Filtering, AF)就是解决这个问题的。它会沿着纹理拉伸的方向,采样多个点,然后加权平均。采样点数就是各向异性等级,通常是2x、4x、8x、16x。

等级 采样数 效果 性能开销
1x 1 等同于三线性
2x 2 轻微改善 中等
4x 4 明显改善 较高
8x 8 很好
16x 16 极致 很高

我曾经在开发一个赛车游戏时,路面纹理在远处总是模糊一片。开了4x各向异性过滤后,路面纹理清晰多了,远处的车道线都能看清。嗯,这里要注意,各向异性过滤对显存带宽的消耗很大,移动端慎用。

避坑指南:我曾经在某个项目中,为了追求极致画质,把所有纹理都开了16x各向异性过滤。结果在低端机上直接卡成PPT。后来我学乖了:根据纹理类型动态调整。比如地面、墙面这种大面积的倾斜纹理,用4x或8x;小物体用2x或直接三线性。

4.5 纹理压缩与性能

纹理再漂亮,也得考虑显存和带宽。纹理压缩是必须的。常见的格式有:

  • BC1/BC3(DXT1/DXT5):PC和主机常用,有损压缩,压缩比高。
  • ETC2:Android主流,质量不错。
  • ASTC:新一代格式,压缩比和画质可调,iOS和部分Android支持。
  • PVRTC:PowerVR GPU专用,老设备常见。

我建议:能用ASTC就用ASTC。它的灵活性最好,可以在画质和性能之间做精细权衡。如果目标平台不支持,再退而求其次用ETC2或BC。

小技巧:纹理的Mipmap也要压缩。不要生成Mipmap后再压缩,而是先压缩原始纹理,然后让GPU硬件生成压缩后的Mipmap。这样效率更高,质量也更好。

4.6 总结与建议

纹理映射技术,说白了就是“怎么把纹理数据高效、高质量地映射到屏幕上”。核心就三点:

  1. UV坐标要准确,精度要够。
  2. 纹理过滤根据场景选:UI用双线性,3D场景用三线性或各向异性。
  3. Mipmap一定要开,这是性价比最高的抗锯齿手段。

我个人习惯在项目初期就定好纹理规范:什么类型的纹理用什么过滤方式,各向异性等级是多少,压缩格式是什么。这样后期优化时能省很多事。你想想看,如果等到项目快上线了才发现纹理性能有问题,那改起来可就头大了。

好了,纹理映射这块就聊到这儿。下一章我们聊聊光照与阴影,那又是另一个大坑了。