☰
纹理映射中的摩尔纹与毛刺:从混叠原理到MipMap和各向异性过滤解决方案
2026/10/18 6:42:51 网站建设 项目流程

最近有个做渲染的朋友给我发来一段录屏,问我"为什么远处广场砖地面像水波一样荡来荡去,近处人物衣服边缘又像锯齿一样一跳一跳的"。他说自己已经换了8K贴图,问题不但没解决,波浪纹反而更明显了。这个问题其实很典型,属于纹理映射里最容易被误解的一类现象:摩尔纹和毛刺。它不是"贴图不够清晰"的问题,而是采样方式出了问题,本质上是信号处理里的混叠(aliasing)。这篇文章我会把纹理映射中这两类视觉缺陷的成因拆开讲清楚,再从纹理金字塔、各向异性过滤到各种进阶方案逐一分析,最后分享我排查这些问题时的固定动作和实际经验。适合正在做渲染、游戏客户端、引擎底层的人参考,做贴图美术的同事也可以读一读,知道哪些问题该从源头规避。

1. 摩尔纹和毛刺不是一回事:先分清现象、成因和感受

1.1 一张贴图里同时出现的两种"脏东西"

先把现象分清楚。摩尔纹和毛刺虽然经常一起出现,但看起来完全不同。

摩尔纹是远处规则纹理上出现的波浪状色带,通常是黑白相间或者彩色渐变条纹,会随着相机移动而缓慢滚动变形。典型场景是远距离看砖墙、栅栏、网格地面、百叶窗。你会发现这些场景里的纹理都是高度重复的周期信号,一旦采样不充分,就会产生这种大范围干涉条纹。

毛刺则是像素级别的锯齿和闪烁。最明显的出现在几何边缘——一条斜线变成了阶梯状,转动相机时阶梯位置来回跳动,俗称"shimmering"。另一种毛刺出现在纹理内部,比如高光点、细小字母、树枝缝隙这类高频细节,它们会以随机亮点的方式闪来闪去,特别干扰视线。

很多人把这两者混为一谈,但它们的视觉机制不完全一样。摩尔纹是"全局干涉",是高频信号被折叠后形成的低频长波;毛刺是"局部跳变",是采样点位置微小变化导致相邻帧间颜色大幅变动。明白了这个区别,后面排查方向才不会跑偏。

1.2 一个采样过程,两个不同的失败侧面

纹理映射的直观理解是把一张图"贴"到模型表面。但图形学视角下,这件事的本质是:对二维纹理场做采样,然后用重建滤波器把采样结果还原成像素颜色。每个像素中心是一个采样点,像素覆盖纹理上的一个区域,这个区域越大,意味着采样频率相对纹理频率越低。

摩尔纹的出现,是因为纹理里的周期信号频率超过了当前采样网格能承受的上限。采样定理告诉我们,采样频率至少要达到信号最高频率的两倍,否则高频分量就会"折叠"到低频,和真实低频信号叠在一起,形成莫须有的波浪条纹。这个折叠过程对周期纹理尤其致命,因为折叠后的频率和原始频率会差拍出非常长、非常慢的变化。

毛刺的根源也是混叠,但它更多体现在"边缘"和"瞬态"上。比如三角形边缘的锯齿是几何栅格化的混叠,而纹理内部高光的闪烁则来自高频能量无法被稳定重建。一个像素覆盖的纹理区域里可能有一个很亮的高光点,相机微动一下,采样点落在高光上,这个像素就特别亮;再动一下,采样点偏离高光,像素立刻变暗。这种"全有或全无"的状态就产生了刺眼的闪烁。

1.3 为什么静止截图看不出来,动态看起来特别糟

还有一个容易忽略的层面:摩尔纹和毛刺在动态画面里的危害远大于静态截图。实时渲染每一帧都是重新采样,相机一旦移动,采样点相对纹理的位置每帧都在变化。摩尔纹的干涉条纹会跟着滚动,毛刺会沿着边缘上下跳动。人眼对时间上的高频变化非常敏感,这种滚动和跳动会被大脑自动放大感知。

更麻烦的是视频编码。视频压缩算法假设帧间变化是连续的、可预测的,而摩尔纹滚动和毛刺闪烁恰恰是时间上极不稳定的高频模式。编码器在压缩这类画面时往往会丢失大量细节,同时又产生额外的块状失真和振铃效应,导致问题在录屏、直播、推流环节被进一步放大。所以很多游戏在本地跑着还行,一旦录制成视频发给别人看,问题就变得特别明显。

2. 从采样定理看纹理映射:为什么会"碰巧"出现两种现象

2.1 像素不是"取色点",而是"采样+重建"

我经常跟团队里的人说:不要把像素理解成"从纹理上取一个颜色",而要理解成"在纹理场上做一次采样"。采样点落在哪,决定了你从连续信号中取到哪个值;采样之后还需要重建,也就是把离散样本转换为像素的连续色值。

举个生活化的例子:用手机拍一件细格纹衬衫。衬衫的网格每厘米有几十根线,而手机摄像头传感器的采样点(像素)密度可能还赶不上衣服的纹理密度。拍出来的照片上就会出现一圈一圈的彩色波纹,那不是衣服上有的,是传感器采样不足制造出来的假象。纹理映射里的摩尔纹和这个原理完全一样,只是把"传感器像素"替换成了"屏幕像素"。

如果把纹理看成一张连续信号图,那么每个像素覆盖在纹理上是一小块区域,这个区域的宽度决定了局部采样频率的上限。在近处,像素覆盖区域小,能采样到的纹理细节多;在远处,一个像素可能覆盖了好几个纹理单元(texel),细节自然就糊了。糊不可怕,可怕的是糊的过程中产生了不该出现的图案和闪烁。

2.2 奈奎斯特临界点:频率之间的"躲猫猫"

采样定理有个非常实用的描述方式:为了避免混叠,采样频率应该至少是信号最高频率的两倍,这就是奈奎斯特准则。对于纹理映射,一个像素覆盖的纹理区域宽度为w,当前纹理层级的分辨率是N,那频率超过 N/(2w) 的信号分量就必然产生混叠。

这句话直接解释了为什么"提高纹理分辨率"往往适得其反。你从2K换到8K,纹理里的最高频率变高了,但像素覆盖的纹理区域宽度在同一个距离下也变大了,两者同时变化,奈奎斯特临界条件并没有变好,甚至因为高频能量更多,混叠反而更严重。这就是我那个朋友困惑的根源:分辨率越高,摩尔纹越明显,因为你在给混叠提供更多的"高频原料"。

对于周期性纹理,混叠后的低频分量与原信号形成差拍,空间上呈现大尺度渐变,也就是摩尔纹。对于非周期高频细节,比如树叶、砂砾、高光点,混叠后没有明显的长波结构,而是散落成随机的亮点闪烁,看起来就是毛刺。这两者本质是同一枚硬币的两面。

2.3 三种基础过滤方式各自的边界

图形API里常见的纹理过滤方式大致分三类:最近邻采样(Nearest)、双线性过滤(Bilinear)、三线性MipMap过滤(Trilinear)。很多项目直到出问题了才发现自己一直在用错误的过滤方式。

过滤方式采样行为现象适用场景
最近邻只取采样点最近的那个texel放大时严重块状感,缩小时剧烈摩尔纹和闪烁像素风游戏、需要保持硬边缘的UI图标
双线性采样点周围4个texel做线性插值放大平滑,但缩小依旧混叠严重2D UI、放大场景
三线性MipMap预生成分辨率金字塔,按像素足迹选层并跨层插值缩小混叠大幅降低,但斜视角下可能过渡模糊绝大多数3D场景

这几种方式我都会在后续章节展开讲。这里先记住一个最关键的结论:双线性解决的是"放大时的颗粒感",而摩尔纹和缩小时产生的毛刺,只有靠预滤波或者更复杂的采样策略来解决。如果你还在用只开双线性过滤的方式跑3D场景,那画面出现摩尔纹是必然的。

3. 纹理金字塔为什么能压住一大半摩尔纹:层级选择的本质

3.1 MipMap:在采样之前就把高频提前滤掉

MipMap,也叫纹理金字塔,是当前解决缩小混叠的主流方案。思路很朴素:预先为一张纹理生成一系列分辨率逐级减半的副本,每一层都是上一层经过box滤波缩小的结果。GPU在采样时根据像素覆盖的纹理区域大小,自动选择合适层级的副本。

这为什么能治本?关键在于:MipMap的预生成过程相当于对纹理做了一次低通滤波。上一层已经把超过该层级分辨率的细节抹掉了,GPU从合适的层级采样,相当于在采样之前就对信号做了频带限制,保证采样频率和信号最大频率匹配。这属于"源头抑制"而不是"事后修补"。

很多不明白原理的人会觉得MipMap只是"让远处的纹理糊一点",其实它的核心价值是消除混叠。糊只是表象,如果只有糊而没有摩尔纹和闪烁,那说明滤波是在正确工作的。MipMap的内存开销也常被高估——完整金字塔从原始分辨率开始逐层对半,全部层级加起来大约是原图的1.33倍,多出1/3显存而已。

3.2 GPU如何决定取哪一层:屏幕空间导数与足迹

GPU选择MipMap层级的依据,是像素在纹理空间覆盖区域的近似大小,这个覆盖区域叫像素足迹(pixel footprint)。要计算足迹,GPU会对纹理坐标在屏幕空间求偏导数,也就是计算纹理坐标U和V相对于屏幕坐标X和Y的变化速度。

简化后的对应关系是:如果一个像素覆盖了大约2×2个texel,那就选LOD 1层;覆盖4×4个texel,选LOD 2层;覆盖8×8个texel,选LOD 3层。用公式表示就是 LOD = 0.5 × log2(足迹面积),其中足迹面积可以用两个导数向量围成的平行四边形面积近似。

这里放一段我在调试时经常手算的伪代码,帮你直观理解层级选择逻辑:

// 伪代码:从屏幕空间导数估算足迹并选LOD vec2 dUVdx = dFdx(uv); // uv对屏幕x的偏导 vec2 dUVdy = dFdy(uv); // uv对屏幕y的偏导 // 足迹面积 = 两个导数向量围成的平行四边形面积 float footprint = abs(dUVdx.x * dUVdy.y - dUVdx.y * dUVdy.x); // LOD = log2(足迹边长的平方)的一半 float lod = 0.5 * log2(footprint); // 应用LOD偏移和钳制 lod = clamp(lod + lodBias, lodMin, lodMax);

三线性过滤还会在LOD相邻两层之间按小数部分做一次线性插值,避免层级切换时出现明显的清晰度跳变线。这个插值对于动态画面非常重要,否则相机后退时你会看到一条"清晰分界线"在地面上一层层往外推。

3.3 那些"纹理糊但摩尔纹还在闪"的特殊情况

MipMap不是万能药,我在实际项目里遇到过几种失效情况,非常值得记录。

第一种是纹理图集(texture atlas)场景。一张大图集里排列着许多不同的UV岛,岛与岛之间的纹理内容完全不同。GPU计算屏幕空间导数时,如果采样点恰好跨过图集内两个不同区域的边界,导数会出现大幅跳变,导致LOD算出错误层级,边界附近就会闪出摩尔纹或毛刺。这个问题的处理方案通常是启用"每页MipMap偏移"或者让UV岛之间留出足够边距。

第二种是程序化纹理。有些项目在着色器里动态生成纹理图案,比如网格线、地形等高线、分形噪声。这类纹理如果不走预生成MipMap,而是直接按原始公式采样,每一个像素都会命中极高频率的细节,摩尔纹浮现得很厉害。所以程序化纹理通常需要单独的解析滤波方案,不能简单套用MipMap思路。

第三种情况是光照烘焙图。当光照贴图内容包含高频AO、高光细节时,MipMap会把这些细节模糊掉。远处画面虽然不再闪烁,但原本的明暗层次信息被抹平,整个场景看起来"糊且平",也容易给人一种问题没解决的错觉。这种情况下需要通过调整LOD偏置或者增加额外的方向性存储来缓解。

4. 各向异性过滤:对斜视角的那点补偿逻辑

4.1 MipMap的"正方形假设"为什么不够

MipMap有一个隐藏的假设:像素在纹理空间的覆盖区域近似是正方形。这个假设在正视平面时基本成立,但一旦表面和视线方向形成锐角,比如看远处的地面、墙面、路面,像素覆盖在纹理空间里会变成一个非常扁长的椭圆——平行于表面的方向覆盖很长,垂直于表面的方向覆盖很短。

举个具体场景:你站在马路边看远处的地砖,地砖接缝在竖直方向随着透视急剧缩小,一个像素在纹理空间里沿竖直方向可能跨越好几个砖缝,而水平方向只跨了一小块。各向同性MipMap按面积估算LOD,会选择一个中等分辨率的层级,然后对整块正方形区域做模糊。结果就是:本该在水平方向保留的砖缝细节被抹掉了,而竖直方向的高频接缝又没被充分滤波,远处地面既糊又闪。

这种"既糊又闪"同时出现的情况,在传统MipMap下非常常见,因为正方形足迹和椭圆足迹不匹配。想要好效果,就需要在垂直方向多滤波一些,水平方向少滤波一些,也就是各向异性过滤。

4.2 各向异性过滤在做什么

各向异性过滤的思路,是放弃"一个像素对应一个正方形区域"的假设,改为计算像素在纹理空间的椭圆足迹,然后沿着椭圆的长轴方向做多次采样,最后把多次采样结果加权平均。数学上这相当于用椭圆核做卷积滤波,比正方形核更贴合真实的投影关系。

实际操作中,GPU具体做法是:先用屏幕空间导数算出椭圆主轴的方向和长度,然后沿主轴方向以固定步长采样多个位置,每个位置再做一次双线性或三线性采样,最后把结果混合。采样次数越多,对椭圆的逼近越准确,这就是我们常说的各向异性档位2x、4x、8x、16x的含义——档位数字基本对应沿主轴的最大采样次数。

各向异性过滤的价值主要体现在斜视角上的"定向细节保留"。地面砖缝、长条栅栏、铁轨这类强方向性纹理是最大受益者。它能做到:在视线平行的方向上保留清晰的结构线,在视线垂直的方向上充分滤波消除混叠,两者兼顾。

4.3 档位与性能的真实体验

各向异性的档位不是越高越好,需要结合实际场景来判断。我整理了大致效果对比,方便你做取舍:

档位等效采样次数视觉收益性能代价
关闭1次采样斜视角地面糊、远处闪无额外开销
2x最多2次斜视角有轻微改善可忽略
4x最多4次地面砖缝明显更清晰部分中低端设备有压力
8x最多8次视觉质量基本满足大多数场景性能消耗明显但通常可接受
16x最多16次与8x差异较难肉眼分辨性能开销大,仅在高端平台值得开

我在项目里的经验是:桌面平台默认开到8x,高端场景可以16x,移动端控制在4x以内。很多时候16x和8x在最终画面上的差异非常小,但带宽压力会实打实翻倍。各向异性过滤还有一个不算缺点的缺点:并不能完全消除极斜视角下的"夹缝毛刺",想要彻底干净,还需要配合LOD偏置和纹理内容本身的控制。

5. 换了MipMap之后毛刺反而变多了:LOD、精度与锐度的三笔账

5.1 负LOD偏移:为了锐利付出的混叠代价

这个坑我踩过不止一次。很多团队觉得MipMap把纹理搞得太糊,于是给采样器加了负的LOD偏置,强制取更接近原始分辨率的层级。这样画面确实锐利了,远处细节看起来也更清楚。但代价是:原本被MipMap滤掉的高频分量又被请了回来,它们再次越过采样率的边界,摩尔纹和闪烁随之复发。

负LOD偏移合理的场景只有一个:源纹理分辨率确实低于当前显示需求。比如你有一张贴图只有512×512,但模型在近景需要撑到4K级别的细节,此时给一点负偏置可以让MipMap不至于过早下降,算是"从低分辨率里多捞一点信息"。但如果你的纹理本来是2K,负偏移反而会让画面更脏。

我建议负偏置控制在 -0.5 到 -1.0 之间,超过 -1.0 基本就是在制造混叠垃圾了。而且偏置应当配合相机距离动态调整,而不是全局写死。比如地面可以使用"近略微负、远略微正"的曲线,让近处保持锐利,远处优先保证稳定。

5.2 法线贴图过滤后的重归一化问题

法线贴图的毛刺属于隐蔽性很强的一种问题。法线贴图在MipMap降采样时,如果只是简单平均两个法线方向,得到的法线长度不再是1。光栅化阶段采样出来的法线如果没做归一化,光照计算结果会出现偏色、高光偏移,在动态光照下表现为表面"隐隐发闪"。

正确做法是两件事:第一,MipMap生成时对法线贴图层做"法线方向平均"而不是普通颜色平均,很多纹理压缩工具默认就这么干,但如果你用自定义管线生成MipMap,务必记得处理;第二,采样法线贴图之后必须重新normalize,即使原始贴图保存时都是单位法线,经过插值和压缩后长度早就变了。

我见过一个项目,所有地面纹理在移动端都有明显的高光毛刺,查了很久发现是法线贴图在某一级Mip中出现了大量长度接近0.05的"烂法线",导致高光计算完全失去了方向性。归一化之后,所有闪烁瞬间消失。

5.3 高光毛刺的独立解法

高光毛刺的另一个难点是:即使纹理过滤完全正确,高光项本身也容易闪。原因是高光对微表面法线方向极其敏感,微小的法线抖动在高光区域会放大成剧烈亮度变化。

这个问题在日常渲染中很常遇:远处瓷砖地面的反光在转动视角时像碎玻璃一样跳。单纯靠MipMap无法根除,因为你滤波的是"法线贴图本身",但高光计算中法线方向对反射向量的微小扰动会在像素输出上形成很大变化。实际解决思路有几个:一是对高光项做额外的屏幕空间平滑;二是对粗糙度贴图做更积极的上限钳制,避免极端镜面区域;三是在采样环境贴图时配合更积极的LOD选择,等效于对反射信号做低通滤波。

5.4 纹理精度与色带毛刺

低精度纹理和低精度插值还会带来另一种"毛刺":色带(banding)。典型表现是光照渐变的墙上出现一道道清晰的分层,像等高线一样,转动视角时分层边缘还会轻微跳动。这个问题严格说不算采样混叠,而是量化精度的伪影。

解决色带通常用两个手段:一是提高中间纹理格式的量化精度,能上12位或16位浮点就不要用8位固定;二是给像素输出加一点点时间抖动和空间抖动,把固定的分界线打散为人眼不易察觉的噪声。很多做影视离线渲染的项目会为了这个细节专门上高比特率输出,实时渲染里用抖动是最经济有效的妥协。

6. 非传统路线:解析过滤、压缩纹理与流送方案中的取舍

6.1 解析反走样:在像素内直接积掉摩尔纹

对于少数可以用数学公式描述的"程序化纹理",还有一种比MipMap更彻底的方案——解析反走样。思路是:不采样纹理函数的具体值,而是直接对像素覆盖区域内的纹理函数做解析积分,得到精确的滤波后颜色。

比如棋盘格纹理,你不用在像素中心取一个点,而是计算该像素覆盖了几个黑色格子、几个白色格子,然后按面积比例混合颜色。这样无论相机拉多远,永远不会出现摩尔纹,因为信号在被采样前就已经被数学上正确地滤波了。

解析反走样的局限性也很明显:只适用于能写出解析表达式的纹理。砖纹、棋盘格、条纹、网格线这类规则纹理可行,但照片级贴图毫无办法。我在某个跨平台渲染Demo里给地面网格线用过这种方法,效果非常好——无限缩小都不会闪。如果你的项目里恰好有类似的网格线、参考线纹理,非常建议尝试。

6.2 纹理压缩格式与摩尔纹的关系

纹理压缩格式的选择会影响摩尔纹的严重程度。常见的块压缩算法大多基于"块内颜色插值"的思路,对边缘和细节的保真度有限。当纹理经过压缩后,块状伪影本身会引入额外的高频分量,这些伪影在缩小采样时同样会混叠,表现为边缘附近的彩色颗粒或额外闪烁。

压缩格式的抗混叠能力大致与它的保真度成正比。高保真格式在渐变和边缘上更干净,摩尔纹相对轻;低比特率格式伪影明显,摩尔纹也更难压制。实际操作层面,建议对高频纹理使用更高压缩质量的格式档位,并且在后处理环节对压缩后的MipMap做肉眼检查——重点看砖缝、网格线这类边缘有没有出现"马赛克状毛刺"。

还有一个被经常忽略的坑:有些引擎在生成MipMap时用的是未压缩的源图,但MipMap上传到显存后会被独立压缩,不同层级的压缩伪影叠加,会导致LOD切换时出现明显的"清晰度抖动"。遇到这种情况,最好是人为对比相邻两层Mip的视觉差异,必要时人为调整LOD偏置,把切换点移到不易察觉的位置。

6.3 流送纹理和虚拟纹理中的特殊"层流失"毛刺

显存有限时,很多项目会使用纹理流送或者虚拟纹理系统,只上传当前需要的MipMap层级。这种方案有一个独特的毛刺问题:当相机快速拉近时,高细节层级还没有上传完成,画面先用低层级撑场,等高层级到位后突然"啪"地一下变清晰。这种瞬间清晰度跳变在动态画面里非常扎眼。

处理方式主要有三种:一是动态调整LOD偏置,让偏置值随纹理加载进度从高往低走,画面过渡更平缓;二是设置常驻层级,保证距离相机一定范围内的层级预上传;三是给流送系统加预判逻辑,提前在相机移动方向加载高细节层。我建议至少加第一层"加载进度偏置",实现简单,效果立竿见影。

6.4 抖动:把规律闪烁换成噪声

最后一个思路比较"剑走偏锋":既然摩尔纹的本质是规律性混叠,那能不能在采样过程中加入微小抖动,打散规律性?答案是可以的。在像素输出前加入幅度极小的噪声,能把大范围规律的波浪纹拆解成类白噪声颗粒。人眼对均匀噪声的容忍度远高于规律闪烁,噪声看起来像"胶片颗粒",而摩尔纹看起来像"画面坏了"。

这个手段我在阴影贴图采样时用过,效果不错。当时是阴影边缘有一圈快速抖动的摩尔纹,靠提高采样精度都压不掉,最后在阴影采样坐标上引入了幅度约0.5个texel的抖动,边缘就变成了一种动态的细小噪声,主观观感反而稳定了。

需要注意抖动幅度一定要小。幅度太大就成了明显的噪点,观众会觉得你贴图脏。一般控制在1到2个灰度级内,既能打散规律模式,又不会引入可感知的颗粒。

7. 排查纹理毛刺问题的固定动作:我现在不纠结分辨率了

这章算是我个人实践的小总结。每次有新项目出现"远处闪、地面波浪、边缘跳"这些问题,我不再像以前一样先怀疑纹理分辨率,而是按下面这套顺序排查。

第一步,静止截图。先拍一张静态图,判断摩尔纹是"全局规则干涉"还是"局部随机闪烁"。全局规则干涉通常说明LOD选择整体偏低,局部闪烁则可能涉及导数异常、法线归一化或者高光通路。

第二步,开屏幕空间导数可视化。很多调试工具能直接显示LOD值或者导数大小,检查地面上是否存在异常的导数跳变线,重点看图集分界、UV接缝附近。导数跳变的区域往往就是毛刺重灾区。

第三步,切换各向异性过滤档位。把各向异性从关闭到16x逐个试,观察斜视角地面是否改善。如果改善明显,说明主要瓶颈在方向性混叠;如果没什么变化,问题可能出在LOD偏置或者纹理内容。

第四步,检查法线和高光通路。关掉法线贴图,或者把高光强度降为零,看闪烁是否消失。如果消失,问题大概率出在法线归一化或高光敏感度上,而不是基础纹理过滤。

第五步,最后才动源纹理。如果前面都查完还没解决,我会去检查纹理本身的最高频率是否过高,比如细密网格、高速噪点这类贴图,它们天生就容易混叠,需要在美术资源阶段就做适当的频带限制。

这一套动作走下来,大多数问题都能定位。坦白讲,最终结论常常是"MipMap设置错了"或者"LOD偏置加了太多",而不是"分辨率不够"。纹理分辨率这个参数,真的不是解决混叠问题的第一杠杆。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询