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坐标精度丢失了。
4.2 纹理过滤:点采样、双线性与三线性
纹理过滤解决的是“像素与纹素对应关系”的问题。你想想看,屏幕上的一个像素,映射到纹理上可能不是一个整数坐标,这时候就需要插值。
4.2.1 点采样(Nearest)
最简单粗暴。取最近的纹素。效果嘛,就是像素感十足,像马赛克。适合像素风格游戏,或者不需要平滑过渡的场景。
4.2.2 双线性过滤(Bilinear)
取周围4个纹素,做两次线性插值。效果平滑很多。这是最常用的过滤方式。我个人习惯在UI渲染上使用双线性,因为UI通常不需要太高的纹理细节,平滑就够了。
4.2.3 三线性过滤(Trilinear)
在双线性的基础上,再对两个Mipmap层级做插值。说白了就是“双线性 + Mipmap层级混合”。效果更好,但开销也更大。
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模式。
4.4 各向异性过滤:解决倾斜视角的模糊
这是纹理过滤的终极形态。你想想看,当视角与纹理表面成锐角时,纹理在屏幕上的投影是严重拉伸的。Mipmap只能处理正方形区域,但实际需要的纹理区域是细长的矩形。
各向异性过滤(Anisotropic Filtering, AF)就是解决这个问题的。它会沿着纹理拉伸的方向,采样多个点,然后加权平均。采样点数就是各向异性等级,通常是2x、4x、8x、16x。
| 等级 | 采样数 | 效果 | 性能开销 |
|---|---|---|---|
| 1x | 1 | 等同于三线性 | 低 |
| 2x | 2 | 轻微改善 | 中等 |
| 4x | 4 | 明显改善 | 较高 |
| 8x | 8 | 很好 | 高 |
| 16x | 16 | 极致 | 很高 |
我曾经在开发一个赛车游戏时,路面纹理在远处总是模糊一片。开了4x各向异性过滤后,路面纹理清晰多了,远处的车道线都能看清。嗯,这里要注意,各向异性过滤对显存带宽的消耗很大,移动端慎用。
4.5 纹理压缩与性能
纹理再漂亮,也得考虑显存和带宽。纹理压缩是必须的。常见的格式有:
- BC1/BC3(DXT1/DXT5):PC和主机常用,有损压缩,压缩比高。
- ETC2:Android主流,质量不错。
- ASTC:新一代格式,压缩比和画质可调,iOS和部分Android支持。
- PVRTC:PowerVR GPU专用,老设备常见。
我建议:能用ASTC就用ASTC。它的灵活性最好,可以在画质和性能之间做精细权衡。如果目标平台不支持,再退而求其次用ETC2或BC。
4.6 总结与建议
纹理映射技术,说白了就是“怎么把纹理数据高效、高质量地映射到屏幕上”。核心就三点:
- UV坐标要准确,精度要够。
- 纹理过滤根据场景选:UI用双线性,3D场景用三线性或各向异性。
- Mipmap一定要开,这是性价比最高的抗锯齿手段。
我个人习惯在项目初期就定好纹理规范:什么类型的纹理用什么过滤方式,各向异性等级是多少,压缩格式是什么。这样后期优化时能省很多事。你想想看,如果等到项目快上线了才发现纹理性能有问题,那改起来可就头大了。
好了,纹理映射这块就聊到这儿。下一章我们聊聊光照与阴影,那又是另一个大坑了。