如何做一个导航网站百度网页版网址链接
第一章:Mipmap 导论
在图形学与游戏开发中,“纹理”(Texture)是最常用的视觉元素之一。几乎所有的 3D 模型、UI 元素、甚至光照效果,都离不开纹理。
然而,纹理带来的并不只是美观,也隐藏着性能与采样精度的问题。
1.1 为什么需要 Mipmap?
假设我们在 C++/OpenGL 程序中加载了一张 1024×1024 的纹理贴到一个平面上。当相机靠近平面时,效果很好;
但当相机拉远、平面缩小到几十个像素时,问题出现了:
闪烁(Flickering):远处像素要覆盖几十上百个纹理像素(texel),导致采样混乱。
摩尔条纹(Moire Pattern):纹理的高频细节与屏幕像素网格叠加,出现干涉条纹。
性能浪费:即使远处只需要几十像素,却仍然要从 1024×1024 的纹理中采样。
举个形象的比喻:
你要在地图上找一座城市,如果用的是全球 8K 高清卫星地图,眼睛会被海量的像素淹没;
如果换成一张分级缩小的地图集(世界地图、亚洲地图、中国地图、省地图…),找城市就轻松很多。
这就是 Mipmap 的核心思想:
为一张纹理提前生成多个不同分辨率的版本,渲染时根据需要选择合适的级别。
1.2 什么是 Mipmap?
Mipmap,全称 “Multum in Parvo map”(意为“在有限空间内包含更多信息”),最早由 Lance Williams 在 1983 年提出。
它是一个纹理金字塔(Texture Pyramid),从最高分辨率开始,逐级缩小一半,直到 1×1。
示例:一张 1024×1024 纹理的 Mipmap 结构:
Level 0:1024 × 1024
Level 1:512 × 512
Level 2:256 × 256
Level 3:128 × 128
Level 4:64 × 64
Level 5:32 × 32
Level 6:16 × 16
Level 7:8 × 8
Level 8:4 × 4
Level 9:2 × 2
Level 10:1 × 1
每一层存储在 GPU 内存中,形成一组完整的金字塔纹理。

1.3 效果对比
在没有 Mipmap 的情况下(只用 GL_LINEAR 或 GL_NEAREST),远处纹理容易出现闪烁:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
开启 Mipmap 并使用三线性过滤(Trilinear Filtering):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
对比图(示意):
无 Mipmap:远处模糊 + 闪烁
有 Mipmap:平滑过渡 + 无闪烁
1.4 本文将讲解什么?
在这篇文章中,我们将从基础到进阶,逐步揭开 Mipmap 的面纱,涵盖以下内容:
Mipmap 的原理与作用
多种生成方式(CPU、OpenGL 自动、手动、Shader)
GPU 如何判定使用哪一层 Mipmap(LOD 计算)
手动控制采样,甚至自主设计 Mipmap
实际 C++/OpenGL 代码示例
通过这些章节,你将不仅仅“会用 Mipmap”,还可以“理解 Mipmap、玩转 Mipmap”,并在工程项目中根据需求自由定制。
1.5 一个最小的 C++/OpenGL 示例
在进入下一章之前,我们先给出一个最小的 C++ 例子,演示如何在 OpenGL 中启用 Mipmap。
// 加载纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);// 上传原始纹理数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,GL_RGBA, GL_UNSIGNED_BYTE, imageData);// 生成 Mipmap
glGenerateMipmap(GL_TEXTURE_2D);// 设置采样方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
运行效果:
靠近时用高清纹理
远处时自动选择低分辨率版本
整体画面更加稳定、真实
第二章:纹理与采样的基础
要理解 Mipmap,首先必须彻底搞懂“纹理”与“采样”。在 OpenGL 中,纹理是渲染的输入数据,而采样则决定了如何把纹理坐标映射到最终的像素。
这一章,我们将逐步剖析:
OpenGL 纹理对象与绑定机制
采样器(Sampler)的工作流程
基本采样方式(Nearest / Linear)
为什么会出现锯齿与摩尔纹
2.1 OpenGL 中的纹理对象
在 OpenGL 中,纹理(Texture)是一个独立的对象,它存放在 GPU 显存里,用来存储像素数据(Texels)。
我们常见的二维纹理对应 GL_TEXTURE_2D 类型。
加载纹理的一般流程是:
GLuint textureID;
glGenTextures(1, &textureID); // 生成纹理对象
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定到当前上下文// 上传数据(例如一张 RGBA 图片)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
到这里,OpenGL 已经把一张图片存放到 GPU 内存里。
但是!它只是存放了数据,还没告诉 GPU 怎么采样。
2.2 采样器(Sampler)与采样方式
当片段着色器运行时,它需要从纹理中取出一个颜色值,这个过程叫做采样(Sampling)。
采样器就是决定“如何从纹理中取值”的规则集合。
例如:
// 片段着色器中采样
uniform sampler2D myTexture;
in vec2 TexCoords;
out vec4 FragColor;void main() {FragColor = texture(myTexture, TexCoords);
}
这里的 texture() 函数,就是用采样器规则,把 [0,1] 范围的纹理坐标 TexCoords 转换为颜色。
常见的采样设置
在 OpenGL 里,我们用 glTexParameteri 来指定采样方式:
// 当纹理被放大时(像素覆盖多个 texel)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 当纹理被缩小时(多个 texel 映射到一个像素)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
参数解释:
GL_NEAREST(最近邻采样)
直接选择最接近纹理坐标的 texel。速度快,但锯齿明显。GL_LINEAR(线性插值采样)
取周围 4 个 texel,做双线性插值。平滑,但可能模糊。
示例代码(放大时最近邻,缩小时线性):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2.3 最近邻 vs 线性插值
假设一张 8×8 的小纹理被放大显示在一个 256×256 的屏幕区域。
最近邻(Nearest):每个屏幕像素直接对应最近的纹理像素,看起来像“马赛克”。
线性插值(Linear):在两个像素之间做平滑过渡,看起来更柔和。
简单示意(伪图像效果):
最近邻:██████(马赛克块状感)
线性: ░░▒▒▓▓(平滑过渡)
2.4 锯齿与摩尔纹的来源
当物体远离相机时,一个屏幕像素可能对应几十个纹理像素(texels)。
这会产生两个问题:
采样不足(Under-sampling)
例如一个像素需要覆盖 20 个 texels,但只取了 1 个或 4 个 → 信息丢失。
高频信号混叠(Aliasing)
当纹理包含规则线条或网格时,缩小后会和像素网格干涉 → 形成摩尔条纹。

这时,单纯的 GL_LINEAR 插值已经无法解决问题。
因此我们需要 Mipmap —— 预先存储低分辨率的版本,让远处采样更合理。
2.5 一个简单的实验代码
下面的代码演示了最近邻与线性插值的区别:
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);// 上传纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,GL_RGB, GL_UNSIGNED_BYTE, image);// 放大时:最近邻
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);// 缩小时:线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
效果:
近看:放大时马赛克 vs 平滑。
远看:线性采样仍然抖动,需要 Mipmap 来进一步改进。
2.6 小结
这一章我们学习了:
纹理对象是 OpenGL 在 GPU 内存中的一块存储。
采样器决定了如何从纹理中取值。
最近邻采样速度快但锯齿明显,线性采样平滑但模糊。
当纹理缩小时,单纯的采样方式不足以避免锯齿和摩尔纹。
第三章:Mipmap 的核心概念
前两章我们学习了纹理采样的基本原理,也看到了最近邻和线性插值在缩小纹理时的不足。
这一章,我们正式进入 Mipmap 的世界。
3.1 Mipmap 的定义
Mipmap(多级渐远纹理,Multi-level Textures) 是一系列按分辨率逐级缩小的纹理集合,常见于 2D 纹理的金字塔存储结构。
其构建规则是:
Level 0:原始纹理(最高分辨率)
Level 1:宽高缩小为 1/2 的纹理
Level 2:宽高缩小为 1/4 的纹理
Level N:直到缩小为 1×1 的单像素
示例:一张 1024×1024 的纹理,完整 Mipmap 层次如下:
| 层级 (Level) | 尺寸 |
|---|---|
| 0 | 1024 × 1024 |
| 1 | 512 × 512 |
| 2 | 256 × 256 |
| 3 | 128 × 128 |
| 4 | 64 × 64 |
| 5 | 32 × 32 |
| 6 | 16 × 16 |
| 7 | 8 × 8 |
| 8 | 4 × 4 |
| 9 | 2 × 2 |
| 10 | 1 × 1 |
在 GPU 内存中,这些层级是连续存储的,渲染时 GPU 会自动根据 LOD 选择合适的层。
3.2 Mipmap 与 LOD(Level of Detail)
Mipmap 的精髓在于“按需取用”,而这个“按需”就是 LOD(细节层次)。
LOD 的含义
LOD 值是 GPU 根据像素大小与纹理坐标变化率计算的指标,代表当前像素最合适的 Mipmap 层级。
当物体在屏幕上很大时 → LOD 接近 0(使用高分辨率纹理)
当物体远离相机时 → LOD 增大(使用低分辨率纹理)
公式上,LOD 由纹理坐标在屏幕空间的偏导数决定:

虽然看起来复杂,但可以理解为:
LOD 衡量的是 “一个屏幕像素对应多少个纹理像素(texel)”。
3.3 为什么需要 Mipmap?
在缩小纹理时,Mipmap 有三个主要优势:
抗锯齿与去摩尔纹
使用低分辨率版本替代高频纹理,避免采样混叠。
性能优化
GPU 不再需要对全分辨率纹理做高代价的线性插值,而是直接从更小的贴图采样。
缓存友好性
小纹理更容易被 GPU 缓存命中,提高采样效率。

3.4 Mipmap 的内存开销
Mipmap 不是免费的。
它需要额外的显存,因为每一层都要存储。
计算公式:

也就是说,生成完整 Mipmap 后,纹理会比原始纹理大约 33%。
举例:
原始纹理:
1024×1024×4 bytes = 4 MBMipmap 后:≈
5.33 MB
因此在显存紧张的场景下,需要权衡是否开启完整 Mipmap。
3.5 OpenGL 中的 Mipmap 采样方式
在 OpenGL 中,设置 缩小采样方式 时,可以启用 Mipmap:
// 使用最近邻 + 最近 Mipmap 层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);// 使用线性插值 + 最近 Mipmap 层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);// 使用最近邻 + 层间线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);// 使用线性插值 + 层间线性插值(最常用,Trilinear Filtering)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);// 放大采样仍然不使用 Mipmap
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3.6 采样模式对比
以一张棋盘格纹理为例,远处效果对比如下:
GL_NEAREST
清晰但抖动明显(摩尔纹严重)。
GL_LINEAR
平滑一些,但远处仍然闪烁。
GL_NEAREST_MIPMAP_NEAREST
过渡生硬,容易出现跳层感。
GL_LINEAR_MIPMAP_LINEAR
平滑 + 稳定 → 最常用的方式。
3.7 一个最小的 Mipmap 示例
我们来看一段完整 C++ OpenGL 代码,加载一张纹理并启用 Mipmap:
GLuint texID;
glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);// 上传原始纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);// 自动生成 Mipmap
glGenerateMipmap(GL_TEXTURE_2D);// 设置采样方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 包裹方式(可选)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
效果:
靠近时仍然高清
远处时平滑过渡,无摩尔纹
3.8 小结
这一章我们理解了:
Mipmap 是多级分辨率的纹理金字塔
它通过 LOD 自动选择合适的层级
优点:去锯齿、防闪烁、性能更高
缺点:增加 33% 的显存占用
OpenGL 提供了四种 Mipmap 采样方式,其中 GL_LINEAR_MIPMAP_LINEAR 最常用
第四章:Mipmap 的生成方式
在上一章中,我们理解了 Mipmap 的定义与作用。但要真正使用它,我们必须解决一个问题:如何生成 Mipmap?
在 OpenGL 中,有多种方式来生成 Mipmap,我们将从简单到复杂逐一介绍。
4.1 OpenGL 自动生成(最常用)
最常见的方式就是调用 glGenerateMipmap。
基本用法
// 上传最高分辨率的纹理(Level 0)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,GL_RGBA, GL_UNSIGNED_BYTE, imageData);// 自动生成所有 Mipmap 层
glGenerateMipmap(GL_TEXTURE_2D);
OpenGL 会根据 Level 0 的纹理,自动生成所有层级,直到 1×1。
优点
简单,一行代码搞定
GPU 优化过,速度快
缺点
只能生成默认的“缩小版”
无法自定义(比如特殊滤镜效果)
4.2 手动上传每一层
有时我们希望自己准备每一层 Mipmap,比如:
不同层级采用不同的美术风格(远处模糊、近处清晰)
特殊用途(如法线贴图的不同精度版本)
这种情况下,可以逐层调用 glTexImage2D:
// Level 0: 原始纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0,GL_RGBA, GL_UNSIGNED_BYTE, image0);// Level 1: 缩小版
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 512, 512, 0,GL_RGBA, GL_UNSIGNED_BYTE, image1);// Level 2: 更小
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 256, 256, 0,GL_RGBA, GL_UNSIGNED_BYTE, image2);
这样,GPU 就会使用我们提供的图像作为 Mipmap 层。
4.3 CPU 端离线生成
如果不想依赖 GPU,我们也可以在 加载纹理时用图像库生成 Mipmap。例如使用 stb_image_resize:
#include "stb_image.h"
#include "stb_image_resize.h"unsigned char* image = stbi_load("texture.png", &w, &h, &channels, 4);int mipLevel = 0;
while (w > 1 && h > 1) {glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, w, h,0, GL_RGBA, GL_UNSIGNED_BYTE, image);// 生成下一层缩小图像int newW = w / 2;int newH = h / 2;unsigned char* resized = new unsigned char[newW * newH * 4];stbir_resize_uint8(image, w, h, 0,resized, newW, newH, 0, 4);free(image);image = resized;w = newW;h = newH;mipLevel++;
}
这种方式让我们可以 在 CPU 上控制每一层的生成逻辑,比如使用高斯模糊、锐化滤波等。
4.4 FBO 渲染生成
更高级的做法是使用 FrameBuffer Object (FBO),让 GPU 自己渲染出低分辨率的纹理版本。
流程:
创建一个 FBO
将纹理绑定为目标
渲染一个缩小版的场景/图片
保存到对应的 Mipmap 层
代码示意:
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);for (int level = 1; level <= maxLevel; level++) {int mipWidth = baseWidth >> level;int mipHeight = baseHeight >> level;// 设置渲染目标为 mip 层glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, textureID, level);glViewport(0, 0, mipWidth, mipHeight);renderQuad(); // 绘制缩小后的图像
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
这种方式常用于 特殊效果的 Mipmap,比如在实时反射/模糊中。
4.5 着色器生成(Compute Shader)
在现代 OpenGL 中,还可以用 Compute Shader 来直接生成 Mipmap。
这样我们能完全控制每一层的内容,比如:
在缩小过程中加入滤波器
做特殊的分层纹理(模糊、锐化、风格化)
核心思想:
在 Shader 中采样上一层的纹理
平均 4 个像素生成下一层
GLSL 伪代码:
// Compute Shader
layout (binding = 0) uniform sampler2D srcTex;
layout (rgba8, binding = 1) writeonly uniform image2D dstTex;void main() {ivec2 gid = ivec2(gl_GlobalInvocationID.xy);vec4 c1 = texelFetch(srcTex, gid * 2, 0);vec4 c2 = texelFetch(srcTex, gid * 2 + ivec2(1,0), 0);vec4 c3 = texelFetch(srcTex, gid * 2 + ivec2(0,1), 0);vec4 c4 = texelFetch(srcTex, gid * 2 + ivec2(1,1), 0);imageStore(dstTex, gid, (c1+c2+c3+c4)/4.0);
}
这样,我们就能在 Shader 里完全自定义 Mipmap 的生成规则。
4.6 各方式对比总结
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
glGenerateMipmap | 简单快速 | 不可定制 | 常规纹理加载 |
| 手动上传 | 可控性高 | 繁琐,需要准备数据 | 特殊美术效果 |
| CPU 离线生成 | 可使用复杂算法 | 占用 CPU 资源,慢 | 离线工具、贴图打包 |
| FBO 渲染 | GPU 加速,可定制 | 代码复杂 | 实时渲染特效 |
| Compute Shader | 完全可控,最灵活 | 要求 OpenGL 4.3+ | 高级优化、研究项目 |
4.7 示例:用 glGenerateMipmap 生成并采样
一个最小的 Mipmap 使用例子:
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);// 上传纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,0, GL_RGBA, GL_UNSIGNED_BYTE, data);// 自动生成 mipmap
glGenerateMipmap(GL_TEXTURE_2D);// 设置采样方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
渲染时,GPU 会根据像素大小自动选择合适的 mip 层进行采样。
4.8 小结
这一章我们学习了 Mipmap 的四种生成方式:
OpenGL 自动生成(最常用)
手动上传每一层
CPU 离线生成
FBO 渲染 / Compute Shader 自定义
第五章:Mipmap 切换判定与 LOD 控制
前面我们学会了如何生成 Mipmap,但还有一个关键问题:
渲染时,GPU 如何决定该使用哪一层 Mipmap?
这就是所谓的 切换判定(LOD Selection)。
5.1 LOD(Level of Detail)的概念
LOD(细节层次)指的是渲染时所使用的 Mipmap 层级。
GPU 会为每个像素(fragment)计算一个 LOD 值,然后用它来选择合适的 Mipmap 层。
LOD = 0 → 使用最高分辨率(Level 0)
LOD = 1 → 使用
width/2 × height/2的纹理(Level 1)LOD = 2 → 使用
width/4 × height/4的纹理(Level 2)…以此类推
LOD 值越大,纹理分辨率越低。
5.2 GPU 如何计算 LOD?
GPU 的计算公式基于 纹理坐标在屏幕上的变化率。
简单来说:

其中:
u,vu, v 是纹理坐标
x,yx, y 是屏幕坐标
偏导数表示相邻像素之间纹理坐标的差异
直观理解:
如果相邻像素的纹理坐标差异很小 → 说明屏幕像素覆盖的纹理区域大 → 需要高分辨率层(LOD 小)
如果差异很大 → 说明屏幕像素覆盖的纹理区域小 → 用低分辨率层(LOD 大)
5.3 OpenGL 中的采样模式
在 OpenGL 中,设置 缩小采样方式时,GPU 根据 LOD 自动选择 Mipmap 层。
我们常用的几种模式:
// 最近邻 Mipmap 层 + 最近邻采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);// 最近邻 Mipmap 层 + 线性采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);// 插值 Mipmap 层(Trilinear) + 最近邻采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);// 插值 Mipmap 层(Trilinear) + 线性采样(最常用)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
对比说明
NEAREST_MIPMAP_NEAREST
跳跃感强,切换层级时画面突然变化。LINEAR_MIPMAP_NEAREST
平滑一些,但切层仍然突兀。LINEAR_MIPMAP_LINEAR(Trilinear)
最平滑,GPU 在相邻层之间做插值。
5.4 手动 LOD 偏移
有时我们希望“强制”调整 LOD,比如:
渲染阴影贴图时,适当加大 LOD 让阴影更模糊
贴花(Decal)效果中,手动选择低分辨率层
可以通过以下方式设置 LOD 偏移:
// OpenGL 代码
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -0.5f);
负值偏移会倾向于高分辨率(更清晰),正值则倾向于低分辨率(更模糊)。
5.5 在 GLSL 中手动控制 LOD
GLSL 提供了几个函数,让开发者可以直接干预 LOD:
1. texture()
自动计算 LOD(默认方式)。
vec4 color = texture(mySampler, texCoord);
2. textureLod()
手动指定 LOD。
vec4 color = textureLod(mySampler, texCoord, 2.0); // 强制使用 Level 2
3. textureGrad()
手动指定偏导数,用于复杂情况(如自定义投影)。
vec4 color = textureGrad(mySampler, texCoord, dFdx(texCoord), dFdy(texCoord));
4. textureQueryLod()
查询自动计算的 LOD 值。
float lod = textureQueryLod(mySampler, texCoord).x;
5.6 Trilinear Filtering(双线性 + 层间线性)
当 LOD 不是整数时(比如 1.3),GPU 会在两个相邻层之间插值。
这就是 三线性过滤(Trilinear Filtering):

这样可以避免 层级切换的突兀感。
5.7 各向异性过滤(Anisotropic Filtering)
在斜视角(比如地面铺开的砖块)时,普通 Mipmap 会过度模糊。
此时可以启用 各向异性过滤(AF):
GLfloat maxAF;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAF);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAF);
AF 能根据视角调整采样区域,大幅提升远处纹理清晰度。
现代游戏几乎都会开启。
5.8 一个完整示例:手动 LOD 控制
下面的例子展示了如何在 Shader 中手动指定 LOD:
C++ 部分:
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);// 上传 + 自动生成 mipmap
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0,GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);// 设置三线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLSL 部分:
uniform sampler2D tex;
in vec2 uv;
out vec4 FragColor;void main() {float lod = 2.0; // 强制使用 mipmap level 2FragColor = textureLod(tex, uv, lod);
}
渲染结果:整个画面都使用 第 2 层 的纹理,即分辨率为 width/4 × height/4。
5.9 小结
这一章我们学习了 Mipmap 切换判定与 LOD 控制:
GPU 根据纹理坐标变化率自动计算 LOD
OpenGL 提供了 4 种 Mipmap 采样方式(最近邻、线性、Trilinear)
可以通过 LOD 偏移 或 Shader 手动采样来控制层级
各向异性过滤 提升了斜视角下的纹理清晰度
第六章:Mipmap 手动设计与特殊用途
在前几章中,我们主要把 Mipmap 当作 抗锯齿与优化工具来介绍。
但在现代图形学中,Mipmap 远不止于此 —— 它还可以作为 多分辨率金字塔,用于各种图像处理和特效。
6.1 为什么要手动设计 Mipmap?
默认的 Mipmap 是通过 GPU 自动生成的,它的算法很简单:
通常只是对上一层做一次 Box Filter(均值滤波)。
优点:快、通用
缺点:不够灵活

有些场景下,我们希望 Mipmap 不只是“缩小”,而是带有特殊的风格:
模糊金字塔(Gaussian Pyramid) → 用于模糊效果、阴影柔化
锐化金字塔(Sharpen Pyramid) → 保持远处清晰度
边缘增强金字塔 → 保持远处轮廓
特殊艺术风格(金属光泽、卡通化)
这些都需要我们 手动生成 Mipmap。
6.2 模糊金字塔(Gaussian Mipmap)
在计算机视觉中,常用 高斯金字塔 来进行多分辨率图像处理。
同样的思路也可以用在 OpenGL 的 Mipmap。
原理
在生成下一层 Mipmap 时,先对上一层做高斯模糊,再缩小一半。
这样可以避免 aliasing,并得到更平滑的结果。
示例代码(CPU 端生成)
unsigned char* image = stbi_load("texture.png", &w, &h, &channels, 4);int level = 0;
while (w > 1 && h > 1) {// 上传到 GPUglTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, w, h, 0,GL_RGBA, GL_UNSIGNED_BYTE, image);// 对图像做高斯模糊(伪代码)unsigned char* blurred = gaussianBlur(image, w, h);// 缩小一半int newW = w / 2;int newH = h / 2;unsigned char* resized = resize(blurred, w, h, newW, newH);free(image);free(blurred);image = resized;w = newW;h = newH;level++;
}
应用场景:
阴影贴图(Shadow Map)模糊
景深效果(Depth of Field)
6.3 锐化金字塔(Sharpen Mipmap)
有些时候,自动生成的 Mipmap 会让远处的纹理显得过于模糊。
比如游戏场景里的墙壁砖块、地面材质,远处看上去一片灰。
此时可以人为地在生成 Mipmap 时加入 锐化滤波器:
原理
在缩小前,先对图像应用锐化卷积核:
[0−10−15−10−10]\begin{bmatrix} 0 & -1 & 0 \\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{bmatrix}
示例(GLSL Shader 实现)
vec4 sharpen(sampler2D tex, vec2 uv, vec2 texelSize) {vec4 c = texture(tex, uv);vec4 sum = -texture(tex, uv + vec2(-texelSize.x, 0.0))-texture(tex, uv + vec2(texelSize.x, 0.0))-texture(tex, uv + vec2(0.0, -texelSize.y))-texture(tex, uv + vec2(0.0, texelSize.y))+ c * 5.0;return sum;
}
应用场景:
远处材质保持清晰(如地砖、墙面)
强调物体轮廓
6.4 特殊艺术风格 Mipmap
Mipmap 还可以被设计为 远处与近处不一样的纹理。
Level 0:正常高清贴图
Level 1:轻度模糊
Level 2:卡通化(颜色减少)
Level 3:改为灰度
Level 4 以后:直接用单一颜色
这样,当物体逐渐远离时,会逐步变成另一种风格,实现“渐变式艺术效果”。
例如在 科幻游戏 HUD 中,可以通过 Mipmap 实现“远处物体变成简化符号”的效果。
6.5 Mipmap 在特效中的应用
除了普通采样,Mipmap 还可以用作 数据结构,被用于各种图形学特效。
1. Bloom 效果
高亮部分 → 生成模糊 Mipmap
再叠加到原图 → 形成光晕
2. SSAO(屏幕空间环境光遮蔽)
用深度图生成 Mipmap
远处用低分辨率采样 → 减少计算量
3. 实时反射
用 CubeMap 的 Mipmap 生成模糊反射
高 LOD → 模糊反射(粗糙材质)
低 LOD → 清晰反射(光滑材质)
4. 阴影柔化
Shadow Map 生成 Mipmap
在采样时选择更高 LOD → 模糊阴影边缘
6.6 一个 Shader 示例:LOD 混合模糊
我们可以在片段着色器里,利用 textureLod() 手动取不同层的 Mipmap,再混合成模糊效果。
uniform sampler2D tex;
in vec2 uv;
out vec4 FragColor;void main() {vec4 c0 = textureLod(tex, uv, 0.0); // 高清vec4 c2 = textureLod(tex, uv, 2.0); // 模糊版vec4 c4 = textureLod(tex, uv, 4.0); // 更模糊FragColor = (c0 * 0.6 + c2 * 0.3 + c4 * 0.1);
}
效果:
保持近处清晰
远处逐渐模糊
模拟景深 / 后处理模糊效果
6.7 小结
这一章我们学习了 Mipmap 的手动设计与特殊用途:
默认 Mipmap 只是均值缩小 → 可扩展成模糊/锐化金字塔
可以制作 艺术化 Mipmap,远近不同风格
Mipmap 还被广泛用于 Bloom、反射、阴影、SSAO 等特效
在 Shader 中手动控制 LOD,可以用 Mipmap 做后处理
第七章:OpenGL 实践案例
前面六章我们从理论、原理到手动设计都讲过了。
现在是时候动手写一些实战代码,让 Mipmap 真正跑起来。
这一章我会提供几个 逐步升级的案例:
标准纹理加载 + 自动生成 Mipmap
手动上传每一层 Mipmap
着色器中手动控制 LOD
各向异性过滤(AF)效果对比
使用 Mipmap 做特效(模糊/反射)
7.1 案例一:标准纹理加载 + 自动生成 Mipmap
这是最常见的方式,也是入门时最实用的。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <iostream>int main() {// 初始化 GLFWglfwInit();GLFWwindow* window = glfwCreateWindow(800, 600, "Mipmap Test", NULL, NULL);glfwMakeContextCurrent(window);gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);// 加载纹理int w, h, c;unsigned char* data = stbi_load("texture.png", &w, &h, &c, 4);GLuint tex;glGenTextures(1, &tex);glBindTexture(GL_TEXTURE_2D, tex);// 上传最高分辨率纹理glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0,GL_RGBA, GL_UNSIGNED_BYTE, data);// 自动生成 MipmapglGenerateMipmap(GL_TEXTURE_2D);// 设置采样方式(Trilinear Filtering)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 设置包裹方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);stbi_image_free(data);// 主循环(伪代码)while (!glfwWindowShouldClose(window)) {glClear(GL_COLOR_BUFFER_BIT);// drawScene(tex);glfwSwapBuffers(window);glfwPollEvents();}glfwTerminate();
}
效果:当你放大/缩小时,Mipmap 会自动选择合适的分辨率,避免闪烁。
7.2 案例二:手动上传每一层 Mipmap
如果我们想自己提供每一层 Mipmap,可以这样写:
glBindTexture(GL_TEXTURE_2D, tex);// Level 0
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w0, h0, 0,GL_RGBA, GL_UNSIGNED_BYTE, img0);// Level 1
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, w1, h1, 0,GL_RGBA, GL_UNSIGNED_BYTE, img1);// Level 2
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, w2, h2, 0,GL_RGBA, GL_UNSIGNED_BYTE, img2);
这样可以让不同层级显示不同内容,例如:
Level 0:高精细砖块纹理
Level 2:模糊版砖块
Level 4:单色灰度版
远处物体就会自动显示模糊或简化的风格。
7.3 案例三:Shader 中手动控制 LOD
有时候我们希望完全掌控使用哪一层 Mipmap,可以用 textureLod()。
GLSL 片段着色器:
#version 330 core
uniform sampler2D tex;
in vec2 uv;
out vec4 FragColor;void main() {float lod = 2.0; // 强制使用 Mipmap Level 2FragColor = textureLod(tex, uv, lod);
}
效果:
整个画面都会用 Level 2 的低分辨率纹理,看起来更模糊。
如果把 lod 换成 textureQueryLod(tex, uv).x,就能看到 GPU 自动选择的结果。
7.4 案例四:各向异性过滤(AF)对比
在斜视角时,普通 Mipmap 会让远处纹理过于模糊。
各向异性过滤(AF)能显著改善清晰度。
GLfloat maxAF;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAF);// 设置为最大 AF 值
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAF);
效果对比:
无 AF:地面砖块远处模糊一片
AF=16x:远处砖块依然清晰,几乎没有模糊感
7.5 案例五:使用 Mipmap 做模糊特效
Mipmap 不仅仅用来抗锯齿,还能做特效。
比如:在 Shader 里混合不同层,实现模糊效果。
GLSL 片段着色器:
#version 330 core
uniform sampler2D tex;
in vec2 uv;
out vec4 FragColor;void main() {vec4 c0 = textureLod(tex, uv, 0.0); // 高清vec4 c2 = textureLod(tex, uv, 2.0); // 模糊vec4 c4 = textureLod(tex, uv, 4.0); // 更模糊FragColor = c0 * 0.6 + c2 * 0.3 + c4 * 0.1;
}
效果:
近处仍然清晰
远处逐渐模糊
可以模拟 景深效果(Depth of Field)
7.6 小结
这一章我们写了 5 个 OpenGL 实战案例:
标准加载 + 自动 Mipmap(最常见用法)
手动上传层级(特殊美术效果)
Shader 中控制 LOD(强制采样某层)
各向异性过滤 AF(提升斜视角清晰度)
Mipmap 特效(模糊/景深/反射)
通过这些例子,你不仅能正确使用 Mipmap,还能把它变成 创作工具。
第八章:总结与展望
经过前面七章的学习,我们已经从基础到进阶,完整掌握了 Mipmap 的理论与实践。
在这一章,我们将对全篇做一次总结,并展望 Mipmap 在现代和未来图形学中的地位。
8.1 本文总结
我们从最初的问题出发:为什么单纯的纹理采样会出现闪烁和摩尔纹?
然后一步步引出 Mipmap(金字塔纹理) 的解决方案。
核心要点回顾:
Mipmap 的定义
一组逐级缩小的纹理集合
GPU 根据 LOD 自动选择合适层级
生成方式
OpenGL 自动生成(
glGenerateMipmap)手动上传每一层
CPU 离线生成(可控性高)
FBO/Compute Shader 自定义生成
切换判定与 LOD 控制
GPU 根据纹理坐标偏导数计算 LOD
Trilinear Filtering(层间插值)提升平滑性
各向异性过滤(AF)改善斜视角模糊
可以在 Shader 中手动控制 LOD(
textureLod、textureGrad)
手动设计与特殊用途
模糊金字塔(景深、阴影柔化)
锐化金字塔(保持远处清晰度)
艺术化 Mipmap(近清晰、远简化)
作为多分辨率图像结构,支持 Bloom、SSAO、反射等特效
OpenGL 实践案例
标准加载与自动 Mipmap
手动上传特殊层级
Shader 中强制 LOD
启用 AF 提升画质
基于 Mipmap 的模糊特效
一句话总结:
👉 Mipmap 既是抗锯齿的利器,也是强大的多分辨率图像工具。
8.2 Mipmap 的优缺点
优点:
有效解决缩小时的锯齿与摩尔纹
提高采样效率,减少 GPU 带宽压力
支持多种采样方式(最近邻、线性、三线性)
可扩展到各种特效(模糊、反射、Bloom)
缺点:
占用额外显存(约 33% 开销)
默认的 Box Filter 可能过度模糊
对于非均匀缩放(如斜视角),效果有限(需 AF 补充)
8.3 Mipmap 在现代渲染引擎中的应用
Unity
Unity 默认对所有导入的纹理生成 Mipmap
在材质设置中可以选择过滤模式(Point/Linear/Trilinear)
支持 AF,可在 Quality Settings 中全局开启
Unreal Engine
Unreal 提供 Mip Gen Settings,可以选择不同的生成方式(Sharpen、Blur、NoMipMaps)
在材质中可以手动查询 LOD,并用于特效(如景深模糊)
广泛用于反射、阴影、全局光照缓存
8.4 Mipmap 与未来图形学技术
随着硬件的发展,Mipmap 的理念正在延伸到更广阔的领域:
虚拟纹理(Virtual Texturing / Sparse Texture)
把巨大的纹理分块存储,只加载可见部分
Mipmap 是虚拟纹理分页的重要基础
深度学习与超分辨率
用 AI 替代传统的 Box Filter,生成更“智能”的 Mipmap
根据内容自动优化清晰度与模糊度
实时全局光照
使用 Mipmap 存储光照贴图的多级信息
在不同尺度下做光照混合
跨平台渲染标准
Vulkan、Metal、DirectX 都支持 Mipmap
API 越来越一致,方便跨引擎移植
8.5 学习与实践建议
如果你是图形学初学者:
从 案例一:自动生成 + Trilinear Filtering 入手,先体验效果差异
逐步尝试 手动 LOD 控制,理解 GPU 的计算逻辑
如果你是进阶开发者:
尝试 手动设计 Mipmap,比如模糊金字塔
在特效中用
textureLod(),比如 Bloom 或反射模糊
如果你是研究者:
关注 Compute Shader 生成 Mipmap,实现自定义滤波
研究 Mipmap 在虚拟纹理和光照缓存中的扩展
8.6 全文总结
Mipmap 最早只是为了解决纹理缩小的采样问题,但经过几十年的发展,它已经演变成:
渲染质量优化工具:减少锯齿、闪烁
性能优化手段:降低 GPU 带宽压力
特效构建模块:支持模糊、反射、Bloom、SSAO
图形学通用数据结构:作为多分辨率金字塔,用于计算机视觉与图像处理
因此,掌握 Mipmap,不仅能让你的 C++/OpenGL 项目更高效稳定,也能让你对 现代图形学的核心思想——多分辨率处理有更深理解。
