mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7
2101 字
6 分钟
有关Mipmap算法的笔记
2026-05-05

Mipmap#

空间换时间的优化解法#

一句话总结就是,对一张纹理图片进行多层压缩,然后对于MVP变换之后屏幕中显示的像素,GPU会根据一个物体在屏幕中的像素大小调用计算公式,得出运用哪一层级的图

1. 为什么我们需要 Mipmap?(痛点与灾难)#

想象一下,我们在 UE5 里铺了一块非常精细的 1024×10241024 \times 1024 的高光泽大理石地板(包含 100 万个纹素)。

  • 当你站得很远时:这块巨大的地板在屏幕上可能只占据了 10×1010 \times 10(100 个)像素。
  • 灾难发生:如果让这 100 个屏幕像素去那 100 万个纹素里“抽卡”采样,它们抽到的颜色会非常稀疏、极其随机。只要相机稍微移动一点点,像素采样的位置就会发生剧烈变化,导致远处的地板疯狂闪烁,甚至出现一圈圈恶心的摩尔纹 (Moiré pattern)!这就是图形学中臭名昭著的走样 (Aliasing)
2. Mipmap 的核心思想:空间换时间(提前计算)#

既然当场“抽卡”会闪烁,那我们不如提前算好远处的像素该长什么样!

当我们把一张贴图导入 UE5 时,引擎会在后台默默地帮你生成一连串越来越小的贴图:

  • Level 0: 1024×10241024 \times 1024(原图,适合极近距离)
  • Level 1: 512×512512 \times 512(长宽各缩小一半,相当于四个像素平均成一个)
  • Level 2: 256×256256 \times 256
  • Level 10: 1×11 \times 1(整张图混合成了一个纯色像素,适合无限远)

逻辑链:当相机渲染某个像素时,GPU 会计算这个像素覆盖了贴图上多大的区域。如果覆盖区域很大(说明离得很远),GPU 就不去原图里找了,而是直接去 Level 3 或者 Level 4 那种已经“平均模糊”好的小贴图里去取颜色。这样不仅彻底消灭了闪烁,还能因为小贴图更容易被塞进 GPU 高速缓存里,大幅提高渲染效率!

3. 三线性插值 (Trilinear Interpolation)#

如果地面上 Level 1 和 Level 2 的交界处是生硬切换的,画面上就会出现一条明显的“分界线”。

为了解决这个问题,聪明的现代渲染管线不仅会在同一层 Mipmap 里做双线性插值 (Bilinear),还会**把相邻的两层 Mipmap(比如层级 **DD和层级 D+1D+1)都采样出来,然后根据深度的距离,在层与层之间再做一次线性插值 (Lerp)

这(双线性 ×\times 层间线性)合在一起,就是大名鼎鼎的三线性插值 (Trilinear Interpolation) 啦!

4. 经典的代价:多占了多少内存?#

天下没有免费的午餐。既然多生成了这么多缩略图,必然会占用更多的显存。那是多少呢?

是一个等比数列求和:1+14+116+...1 + \frac{1}{4} + \frac{1}{16} + ...

结论:Mipmap 会让贴图多占用原本大小的 13\frac{1}{3} 内存。但这 13\frac{1}{3} 的代价换来了极大的抗锯齿效果和性能提升,是绝对稳赚不赔的买卖!

Mipmap的调用时机#

GPU 在渲染屏幕上的某一个像素时,它怎么知道这个像素现在该用 Level 0,还是 Level 3?

如何计算 Mipmap 层数DD

1. 核心思路:测量“一个屏幕像素,盖住了多少个纹素”#

如果 1 个屏幕像素,正好对应贴图上的 1 个纹素,说明不放大也不缩小,用 Level 0 原图。

如果 1 个屏幕像素,因为离得很远,盖住了贴图上的 4×44 \times 4(共 16 个)纹素,那我们就应该去找那个把 16 个纹素压成 1 个的图,也就是 Level 2(因为 22=42^2 = 4)。

所以,我们的目标是算出屏幕像素在贴图上的“脚印大小” (Footprint),也就是公式里的 LL

2. 第一步:找邻居 (微分思想)#

看 PPT 左边的图。GPU 在光栅化时,像素从来都不是孤立计算的!它们是以 2×22 \times 2** 的像素块 (Quad)** 为单位并行的。

为了知道当前像素 (x,y)(x,y) 的脚印有多大,GPU 会偷偷看一眼它右边的邻居 (x+1,y)(x+1, y)上边的邻居 (x,y+1)(x, y+1)

3. 第二步:映射到 UV 空间求距离#

看 PPT 右边的图。我们把这三个像素的 UV 坐标找出来,画在贴图空间里。

  • 屏幕上往右走 1 个像素,UV 变化了多少?用向量表示就是 (dudx,dvdx)(\frac{du}{dx}, \frac{dv}{dx})。算一下这个向量的长度。
  • 屏幕上往上走 1 个像素,UV 变化了多少?用向量表示就是 (dudy,dvdy)(\frac{du}{dy}, \frac{dv}{dy})。算一下这个向量的长度。
4. 第三步:取最大值求 LL,对数求 DD#

因为受透视影响,像素映射到 UV 空间通常是个扭曲的四边形。为了保守起见,我们取上面两个长度里的最大值作为正方形的边长 LL

L=max((dudx)2+(dvdx)2,(dudy)2+(dvdy)2)L = \max \left( \sqrt{\left(\frac{du}{dx}\right)^2 + \left(\frac{dv}{dx}\right)^2}, \sqrt{\left(\frac{du}{dy}\right)^2 + \left(\frac{dv}{dy}\right)^2} \right)

算出了 LL(比如 L=4L=4,意味着这个像素跨越了 4 个纹素的宽度),那该用第几层 Mipmap 呢?

直接求以 2 为底的对数就好啦!

D=log2LD = \log_2 L

(如果 L=4L=4,那么 D=2D=2,所以去查 Level 2 的 Mipmap)

Mipmap小数情况处理#

1. 致命的痛点:如果算出来的 DD 是小数怎么办?#

还记得上一步我们用 log2L\log_2 L 算出了 Mipmap 的层数 DD 吗?

真实的 3D 世界是连续的,所以算出来的 DD 绝大多数情况都是小数!比如 D=1.8D = 1.8

  • 如果我们像最近邻插值那样,直接四舍五入去第 2 层(Level 2)拿颜色。
  • 那当相机稍微往前走一步,哪怕只移动了一毫米,DD 变成了 1.41.4,系统就会瞬间切回第 1 层(Level 1)。
  • 灾难后果:因为不同层级的贴图模糊程度不一样,这种“非黑即白”的硬切,会导致游戏画面里的地面上出现一条极其明显的分辨率断层线 (Seam),也就是玩家常说的“贴图加载线”,非常刺眼!
2. 完美的救赎:三步走逻辑链 (Trilinear)#

为了彻底抹平这条断层线,现代 GPU 决定同时压榨相邻的两层贴图!这就是 PPT 里的核心逻辑:

  • 第一步:向下去找 DD 层 (Level 1)

既然 D=1.8D=1.8,那它的下界就是第 1 层。GPU 会在 Level 1 的贴图里,找到对应的 4 个纹素,做一次双线性插值 (Bilinear),得到一个颜色,我们叫它 ColorDColor_{D}

  • 第二步:向上去找 D+1D+1 层 (Level 2)

它的上界是第 2 层。GPU 会在 Level 2 的贴图里(注意,Level 2 的网格比 Level 1 稀疏了一倍),再次找到对应的 4 个纹素,做第二次双线性插值 (Bilinear),得到另一个颜色,叫它 ColorD+1Color_{D+1}

  • 第三步:层与层之间的羁绊 (Lerp)

拿出 D=1.8D=1.8 的小数部分,也就是 0.80.8。我们在刚刚算出的两个颜色之间,再做最后一次线性插值 (Linear Interpolation)

FinalColor=(10.8)×ColorD+0.8×ColorD+1FinalColor = (1 - 0.8) \times Color_{D} + 0.8 \times Color_{D+1}

3. 为什么叫“三”线性?性能开销如何?#
  • 为什么叫三线性 (Tri-linear):因为它在 UU(水平)、VV(垂直)两个空间维度上做了插值,又在 DD(深度/层级)这个第三维度上做了插值。合起来就是 3D 插值!
  • 可怕的性能开销:算算看,为了求这一个屏幕像素的最终颜色,GPU 读了多少次贴图?Level 1 读了 4 个纹素,Level 2 读了 4 个纹素,一共读取了 8 个纹素!并且做了一堆乘法和加法。所以,三线性插值的画质是顶级的,但开销也是双线性的两倍哦!
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

有关Mipmap算法的笔记
https://misuzu0010.github.io/posts/mipmap/
作者
Misuzu0010
发布于
2026-05-05
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录