news 2026/10/18 11:02:48

渲染中的纹理走样:摩尔纹与毛刺的根因和工程修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
渲染中的纹理走样:摩尔纹与毛刺的根因和工程修复

做图形学的人,应该都见过这种画面:地面上的格子纹理往远处延伸,细密的线条开始扭动,像水波纹一样一层层“游”过去;或者模型边缘的贴图区域出现一亮一暗的闪烁,远看像长了毛刺。第一次遇到这些现象时,很多人会怀疑是显存崩了、贴图导错了、甚至驱动抽风,但把渲染管线调出来看一圈,才发现问题出在纹理映射本身。摩尔纹和毛刺不是显示 bug,它们是离散采样和有限分辨率下必然出现的走样现象。

这篇文章不打算从数学公式堆到三维重建,而是站在一个实际做渲染项目的人的角度,把纹理映射里最常见的两类视觉瑕疵讲清楚:它们是怎么产生的,为什么改了过滤方式还压不住,以及工程上到底有哪些能落地的处理手段。不管你是在写自己的小渲染器、调移动端渲染效果,还是在排查某个跨平台系统的图像异常,这篇内容都可以直接对照排查。

1. 先把问题看明白:摩尔纹和毛刺到底长什么样

1.1 摩尔纹:两套周期性图案“打架”的产物

摩尔纹最经典的场景就是远处的地砖、铁丝网、栅栏,或者任何包含规则高频细节的物体。当你把一台相机慢慢拉远,屏幕像素网格和纹理原本的网格之间开始形成频率差,两套规则图案叠加在一起,肉眼就会看到一种新的、实际上不存在的低频波纹。

用一个容易理解的例子:你拿手机拍电脑屏幕时,屏幕自身的像素点和摄像头感光元件的像素点重合,会出现斜向的彩色条纹,这就是摩尔纹。3D 渲染里的摩尔纹原理完全一样,只不过不是两个真实硬件在互拍,而是屏幕像素的采样点和纹理像素的采样网格发生了“错位叠加”。在渲染公式里,这其实是采样频率低于纹理频率时产生的混叠。

判断一个渲染画面里的花纹是不是摩尔纹,有个很直观的特征:它不会老老实实跟随模型表面,而是会随着视角移动产生漂移、变形,甚至在原地蠕动。因为它是采样产生的副产物,不是模型本身的材质属性。

1.2 毛刺:像素边缘的高频闪烁和高亮抖动

毛刺在画面上表现为贴图区域边缘的锯齿状亮线、高光点跳跃,或者像玻璃反射一样突然冒出来的小白点。它多数出现在纹理被严重缩小、或者纹理坐标梯度变化非常剧烈的区域。

很多人会把毛刺单纯理解为“边缘锯齿”,但它们和几何边缘锯齿并不完全一样。几何锯齿是三角形边界的像素走样,毛刺更多来自纹理内容的局部高频突然和采样率失配。比如一张贴图里有细小的字母、碎石子或高光点,当相机移动时,这些细点在不同的像素位置上被采到或漏采,看起来就像屏幕上跳出一堆小毛刺,远看甚至感觉物体表面一直在“沙沙响”。

如果纹理带有透明通道,毛刺还会表现为半透明边缘的亮边或黑边。这种问题往往不是采样导致,而是 Mipmap 生成时透明像素和边缘像素被混在一起,导致边缘颜色被污染。后面会专门展开。

1.3 摩尔纹和毛刺的同源性

这两类问题经常一起出现,因为它们的根源都是同一个:屏幕像素的采样频率和纹理信号的频率不匹配。摩尔纹偏重“低频伪纹理”,毛刺偏重“高频单点闪烁”,但如果你只是去调颜色、调 UV,不解决采样问题,它们会反复出现。

理清这一点,排查的时候才能顺着同一条主线走,而不是今天改贴图、明天调 LOD,最后越调越乱。

2. 从采样数学看根因:为什么纹理映射这么容易走样

2.1 一个屏幕像素对应的不是纹理上的一个点

纹理映射最基础的想法是给模型表面的每个点找一张颜色。但在光栅化过程中,一个屏幕像素往往覆盖了纹理上一大片区域,而不是精确的一个纹理像素。

试想一个地面平面:离相机近的地方,一个屏幕像素可能只覆盖一两个纹理像素;到了远处,一个屏幕像素可能覆盖几十甚至上百个纹理像素。如果这时候你仍然用屏幕像素中心点去采样一个点,那么远处的纹理细节会被随机跳过,有些细线、亮点就被漏掉了,有些则被放大显示,产生明显的闪烁。

图形学里用微分几何来描述这个过程,通常会算dTdx和dTdy,也就是纹理坐标在屏幕 x 方向和 y 方向的变化率。这两个值放大了看,就是屏幕像素在纹理空间里的“足迹”。足迹越大,说明当前屏幕像素覆盖的纹理区域越大,越需要做预过滤,而不是单点采样。

2.2 不滤波就是暴力抽签

假设一张地板贴图是 1024x1024,分辨率相当高。当它在屏幕上只占 100 个像素时,纹理里 99% 的信息根本无法显示。如果渲染器不做任何过滤,每个屏幕像素就只抽签抽一个纹理像素,抽中哪个显示哪个。

从频率角度说,这就是采样率低于纹理信号的奈奎斯特频率,高频信号混叠成了低频假信号。摩尔纹就是你看到的“假低频信号”,毛刺就是“漏采和偶采出来的高频点”。

很多开发者在排查时想到的第一个方法是“把贴图画小一点”。这确实能缓解高频混叠,因为贴图小了,高频能量就少了,但这是治标不治本,还会让近距离的贴图模糊。正确的思路不是去掉纹理里的高频,而是根据屏幕像素的足迹大小,把纹理预先过滤成不同的分辨率层级,按需取用。

2.3 像素足迹的形态还会变化

屏幕上不同地方的像素足迹大小并不均匀。比如俯视地面时,近处足迹接近正方形,远处却可能拉得很长,像一条细长的椭圆。只看足迹面积还不够,还得看它的长宽比例和方向。

这也解释了为什么有时候你已经开了 Mipmap,远处不闪了,但斜向的地面还是花。因为普通 Mipmap 用一个正方形区域去近似椭圆形的像素足迹,误差被直接保留下来。要处理这种场景,就得用各向异性过滤,后面会讲。

3. 工程上的主流解法:从过滤状态到纹理内容

3.1 纹理过滤状态是第一步,也是最容易被忽略的一步

很多图形 API 默认把纹理过滤设为Nearest或者Bilinear。Nearest就是前面说的抽签,放大时马赛克、缩小时摩尔纹,效果最差。Bilinear在放大时比Nearest好很多,但在缩小时依然会触发走样,因为它仍然只做最近 2x2 纹理像素的插值,没有覆盖远距离采样的大片足迹。

至少要做到线性过滤,但“线性过滤开完了还是会有摩尔纹”是很正常的,因为线性过滤只解决了相邻纹理像素的平滑,没有解决“采样足迹过大”的问题。要让远处不出现摩尔纹,必须搭配分级纹理,也就是 Mipmap。

3.2 Mipmap:把纹理预先“压扁”,按需取用

Mipmap 的原理简单说,就是提前把一张大纹理逐级缩小成一半、四分之一、八分之一……直到 1x1,形成一组分辨率递减的纹理链。渲染时根据屏幕像素的足迹大小,选一个合适的层级来采样,保证每个屏幕像素采到的纹理信息刚好匹配它的显示能力。

选择层级的核心是求纹理坐标在屏幕方向上的变化率,也就是前面说的dTdx、dTdy。常用方法是用两者的长度比算出一个粗糙度值,再取对数得到 Mipmap 层级:

  • 足迹小,就用高分辨率层级;
  • 足迹大,就用低分辨率层级;
  • 足迹介于两层之间,就在两层之间做三线性插值。

三线性过滤的意思是:先在同一层级内做双线性插值,再在相邻两级之间做混合。这样相机移动时,纹理清晰度的跳变会被平滑掉,不会出现“一格一格”突然变糊的效果。

这里要提醒一个关键点:Mipmap 只能解决“缩小后信息溢出的问题”,它不能解决放大后贴图模糊的问题。放大时应该用GL_LINEAR_MAG_FILTER,并且接受纹理分辨率本身的限制,不要指望 Mipmap 能把放大后的画面救回来。

3.3 各向异性过滤:处理斜面和掠射角的关键

地面、墙面、马路这类大平面,往往与相机成很斜的角度,屏幕像素在纹理空间里的足迹是一个很扁的椭圆。普通 Mipmap 用正方形足迹近似这种椭圆,导致近处那层贴图被过度模糊,远处又残留摩尔纹。

各向异性过滤的思路,是对这个椭圆足迹沿着长轴方向做多次采样,再把结果加权平均。实现上通常用一个比率表示最大各向异性程度,比如 4x、8x、16x。数值越大,斜视角下的清晰度越好,但采样次数和带宽压力也越大。

各向异性不是越高越好。很多项目在 8x 和 16x 之间几乎看不出区别,性能开销却翻倍。移动端甚至 4x 就够用。更多时候,解决斜视角摩尔纹的关键不是把比率拉到最高,而是要保证 Mipmap 和三线性过滤先开对,因为各向异性过滤是建立在此基础上的。

3.4 贴图内容层面的辅助手段

采样手段能解决掉大部分问题,但有些材质的特性决定了它很容易产生视觉噪点,比如高频噪波纹理、小字标签、密集的栅栏花纹。这类纹理即便用了 Mipmap,某个层级仍然可能残留强烈的高频信号,导致运行时出现随机闪烁。

比较实用的做法是在美术层面做预过滤。例如用带低通滤波的缩图工具生成 Mipmap,或者直接在原始贴图上做模糊降频。很多老项目会把珠宝、金属刮痕、纸张纤维这类高频贴图单独处理,在保留近景细节的同时,控制中远距离的视觉噪点。

另一种思路是把高频细节从颜色贴图里拆出来,改成法线贴图或者粗糙度贴图。颜色通道负责大色块,法线通道负责高频细节。这样即使细节在远处无法逐像素显示,也能通过光照方向的低频变化表现出材质质感,而不是直接变成摩尔纹或闪烁。

4. 实操:在模拟项目里修复地面摩尔纹和边缘毛刺

4.1 先检查渲染状态配置

我在一个模拟项目里做过一次典型修复。当时症状非常明显:地面网格远处全是蠕动的彩色条纹,墙面高光处有小亮点跳跃。后来检查发现,纹理的滤波参数根本没有被正确设置,Mipmap甚至没有生成。

以 OpenGL 为例,最基本的配置是这样的:

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); glGenerateMipmap(GL_TEXTURE_2D);

注意MIN_FILTER和MAG_FILTER要区分开。很多人的习惯是把两个过滤方式都设置成GL_LINEAR,但MAG_FILTER没有 Mipmap 层级概念,不能设置成GL_LINEAR_MIPMAP_LINEAR,否则部分驱动会直接报错或者忽略设置。

设置完不是立刻生效,还得确认当前采样的着色器没有手动绕过过滤状态。有些自定义 shader 会直接用textureLod强制指定某一层 Mipmap,如果这里写死了层级,上面配置全部白费。

4.2 处理 UV 接缝和纹理边缘的毛刺

开了 Mipmap 后,另一个高频问题会浮现出来:贴图接缝处出现一条亮边或黑边。

这通常发生在纹理图集或者重复纹理的边界。当 Mipmap 层级变低时,低分辨率贴图会把相邻区域的像素混在一起,于是本应透明的接缝位置混入了旁边的颜色,或者重复铺贴时边界采样到了 UV 范围之外的数据。

最简单的处理方式是给纹理边缘加 Padding,也叫出血边。也就是在纹理图集里,每个子图周围复制一圈相邻像素,保证任何插值操作都不会采样到图集外像素。对于单张连续纹理,要确保WRAP模式正确,Repeat 模式下边缘会自动循环,Clamp 模式下边缘会拉伸,视觉表现完全不同。

排查时有个技巧:贴一张黑白棋盘格的 UV 检查纹理,把模型的 UV 拉伸、方向、采样范围全部显示出来。纹理接缝处的毛刺如果和棋盘格条纹重叠,基本可以确定是 UV 边界问题,而不是单纯采样问题。

4.3 用各向异性过滤压制斜视角

平铺地板出现远处摩尔纹,而 Mipmap 已经开启,下一个要调的就是各向异性过滤。在 OpenGL 里可以这样开:

GLfloat maxAniso = 0.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);

直接把最大值拉满不是好做法。先设成 4x,看远处斜面还有没有明显“磨砂感”,不行再提到 8x。很多硬件上 16x 和 8x 的视觉差异极小,但带宽消耗却相差很大,特别是在移动端。

还有一个容易被忽略的细节:各向异性过滤获取的是纹理坐标的偏导数,如果使用了程序化纹理或者经过复杂 UV 变换,驱动算出来的偏导数可能不准。这时候需要手动在 shader 里用dFdx、dFdy计算并传递给采样函数,否则过滤方向会选错,开了等于没开。

4.4 验证修复效果的对比方法

修完以后,不要只看“好像顺眼了一点”。最可靠的方法是把相机放到倾斜角度,用连续移动的方式观察地面远处。摩尔纹和毛刺都是动态现象,单帧截图容易骗人。

还可以用 Debug 工具把当前选中的 Mipmap 层级可视化出来。比如在 shader 里输出textureQueryLod的结果,就能看到每一块屏幕区域实际在使用第几层贴图。如果某个区域长期停留在最模糊的层级,但画面里仍有摩尔纹,说明层级选择逻辑出了问题,而不是过滤没开。

5. 常见问题排查与避坑记录

5.1 快速对照表

现象可能原因优先处理手段
远处规则纹理出现蠕动波纹未开 Mipmap 或 MIN_FILTER 不对设置三线性过滤并生成 Mipmap
贴图放大后满屏马赛克MAG_FILTER 用了 Nearest改为线性过滤
斜面远处又糊又花各向异性过滤未开或层级不足调高各向异性滤过比率
边缘毛刺闪烁图集接缝采样到未覆盖像素加出血边或调整 UV Padding
相机移动时有明显贴图层级跳变使用了双线性 Mipmap 而非三线性改用 LINEAR_MIPMAP_LINEAR
透明贴图边缘发亮Mipmap 生成污染衰减通道在生成 Mipmap 时保留透明通道,或使用预乘 Alpha

5.2 实战中容易踩的坑

第一,不要在贴图导入流程里直接丢弃 mipmap。很多美术规范喜欢“为了节省包体”把所有贴图改成无 mipmap,导致运行时所有远处纹理都是摩尔纹。节省包体可以用纹理压缩,但压缩纹理也要在压缩前生成好 mipmap 链。

第二,谨慎使用负方向的 LOD Bias。把 Mipmap 层级往高分辨率方向偏移能让画面更清晰,但同时会把大量高频信号带回采样管线,毛刺和摩尔纹会卷土重来。我在项目里见过美术为了追求锐度把 LOD Bias 设成 -2,结果整个场景远看都在闪。正确的做法是用各向异性过滤去补清晰度,而不是靠负 Bias 硬拉。

第三,纹理图集的排序会影响采样质量。如果把高对比度纹理和纯色纹理放在同一张图集里,低 Mipmap 层级时颜色会互相渗透,边缘出现彩色晕染。制作图集时要充分考虑低分辨率下的表现,必要时隔离高频贴图。

6. 最后分享一点实际经验

纹理映射里的摩尔纹和毛刺,说到底是采样频率不足的问题,理解了这一点,排查就会变得很有条理:先看过滤状态,再看 mipmap 生成,最后看各向异性设置。多数场景下,这三步按顺序走完,画面问题能解决八成以上。

剩下那两成,往往是美术资源和运行时参数打架的问题。比如贴图颜色对比度过高、UV 摆放过于密集、LOD Bias 设置不合理。这类问题没法靠单一设置修复,需要渲染程序员和美术一起把资源参数重新梳理一遍。

我个人在实际项目里还有一个土办法:在正式制作整套贴图前,先给一块带大量规则花纹的测试地面调好过滤参数,之后所有材质都以这个地面作为基准。这样每次新材质进来,只需要看它和基准地面的差距,就能快速判断是资源问题还是管线问题,不用每次从头排查。这个方法虽然简单,但在我处理跨平台渲染适配时省了非常多的时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 11:01:24

SSM餐饮管理系统实战:从零搭建可商用点餐后台

简介:本资源是一套基于SSM框架(SpringSpringMVCMyBatisPlus)与Vue前端技术实现的完整餐饮管理系统源码,面向Java初学者、Web全栈学习者及课程设计/毕业设计学生,解决餐饮业务场景下的点餐管理、用户信息维护、菜品展示…

作者头像 李华
网站建设 2026/10/18 11:00:20

CNN、DNN、RNN做猫狗分类:架构选择如何决定准确率

简介:基于Python的猫狗图像分类实战项目,完整覆盖CNN、DNN、RNN三种神经网络模型,面向深度学习入门者及计算机视觉学习者,帮助理解不同网络结构在图像分类任务中的适用性与实现差异。压缩包共9个文件,包含3个Python脚本…

作者头像 李华
网站建设 2026/10/18 10:59:35

IDA Pro 7.2 专业版:逆向工具选型、ARM固件分析与避坑指南

简介:面向软件逆向工程与漏洞研究场景,IDA Pro 7.2 专业版是一套被广泛视为行业标准的交互式反汇编与调试工具,适合 Windows 平台下从事恶意代码分析、固件审计、漏洞挖掘的逆向工程师和安全研究员使用。资源压缩包共 1006 个文件、约 172.71…

作者头像 李华
网站建设 2026/10/18 10:59:21

七脚五位数码管驱动详解:从引脚图到中断扫描的实现与调试

简介:面向嵌入式开发与电子爱好者的七脚五位数码管驱动资源,重点解决七个引脚控制41个灯的多段复用与显示刷新问题。压缩包共3个文件,包含C语言驱动源码、对应头文件及引脚图PNG;源码涉及GPIO初始化、段码编码、时序控制、显示更新…

作者头像 李华