Unity Shader:DDXY/fwidth凭什么能“抗锯齿” 单像素抗锯齿网格绘制方案

最近涉及到了网格绘制,其中有要求是必须绘制宽度为1个像素的线条。网上学习中了解到了DDXY相关用法。研究后,遂拙笔记录个人理解。

关于DDXY功能的简短介绍

DDXY又称fwidth。
DDXY返回相邻像素的“偏导数”:即在片段着色器中,此片段(理想状态下为像素)与相邻片段的某个属性的差值。

DDXY的操作对象实际上是片段,像素≠片段,某些操作会打破两者一一对应的关系,例如多重采样会导致片段数量>像素数量
在本文章中,我假设两者是相等、对应的。

可以用来计算法线:

 normalize(cross(ddy(worldPos),ddx(worldPos)));

可以用来勾边(加强边缘)

color += ddx(color)*2 + ddy(color)*2;//color是最终输出的颜色,2为倍率,只需凭感觉设定

计算法线的原理很容易理解,因为坐标顶点相减的结果是向量,而向量叉乘即可求得法线。
加强边缘的算法也符合直觉,因为边缘之处差值大,直接乘法即可扩大这种差异。

但是:
偏导数为什么能够抗锯齿,确定lod?
“偏导数”不过是两个数据的差值,其蕴含了什么信息,使程序可以做到这些功能?

解释 “偏导数”凭什么能“抗锯齿”

首先锯齿产生的原因:
当摄像机正视表面时,表面的像素和屏幕像素处于一一对应的关系。
当摄像机斜视表面时,摄像机能看到更大的面积,此时表面的像素远多于屏幕像素。由于屏幕像素有限,物体表面一些的像素被跳过采样。产生高频纹理锯齿。
在这里插入图片描述
如果你学习过PBR渲染,那么用一种能量守恒的解释可以用于理解这个关系。

当物体远离相机,物体表面在屏幕上的面积减小,也会导致迫舍弃像素而产生高频纹理锯齿。
在这里插入图片描述

扫描二维码关注公众号,回复: 17525905 查看本文章

为了能够抵抗由于跳过像素而产生的锯齿,首先需要得到一个关键信息:
跳过了多少个像素?
一旦我们知道跳过了多少个像素,则我们根据此数据来调整纹理细节层次(LOD)或滤波器,就可以“抗锯齿”,所以用DDXY实现的“抗锯齿”实际上是提供一个参考依据,以便选择正确的LOD来消除高频纹理锯齿。

而DDXY的作用,就是取得跳过了多少个像素?这个信息。

我们只需要对UV计算相邻像素之间的差值,就能知道相邻像素之间相差了多少UV距离,换而言之,获悉跳过了多少个像素。

ddxy(uv)

如果直接输出对UV的偏导的结果,效果如下(为了使效果明显,我对差值进行了十倍放大)
效果
远处的像素明显亮于近处的像素(因为远处的大面积UV被迫分享有限的屏幕面积,而近处的小部分UV却拥有非常大的屏幕面积)。

之后对UV像素偏导的范围影射,计算出采样LOD级别,实现抗锯齿的效果:

启用后(通过DDXY确定LOD 。图中近距离的像素误采样了高级别的LOD) 启用前(强制使用LOD0级别纹理采样 。默认情况下你不会看到图中的锯齿,因为Unity自动处理LOD)
在这里插入图片描述 在这里插入图片描述

结论

因此,通过DDXY偏导实现所谓的的抗锯齿,实际上是计算相邻像素之间的UV距离,根据这个距离使用对应的LOD采样。只能用于消除高频纹理造成的锯齿,无法克服纹理图片资源本身的锯齿,也无法消除几何锯齿。

现代渲染管线在纹理过滤(如各向异性过滤)和 mipmap 生成上已经非常成熟,不需要通过手动使用 ddx 和 ddy 来调整 LOD 或优化纹理采样,除非你确信你的游戏需要自定义这个效果来实现新的视效,不要使用DDXY对纹理进行抗锯齿。这样既低效且效果难以控制。

DDXY应用:绘制单像素宽度网格

虽然不建议使用DDXY对纹理进行抗锯齿,但是某些效果却必须靠DDXY才能实现,例如单像素宽度的线条。
单像素无锯齿线条

问题

默认情况下,如果想要绘制极细的线条,由于某些问题,会造成严重的线条失真。

透视图 正交视图
在这里插入图片描述 在这里插入图片描述

调宽线条粗度虽然可以缓解问题,但如果缩放到一定程度,原始问题依然会出现。

正常大小 缩小视图
在这里插入图片描述 在这里插入图片描述

原因

在片段着色器中我们根据UV坐标来决定是否绘制网格,理想状态下如图所示:
理想状态
但如前文提到的,如果相机距离较远,则会抛弃部分像素。如果较近,则会渲染更多像素。导致在片段着色器中的UV分布无法确定(是否均匀分布无法确定,图中仅考虑X变化,通常这只可能在正交视图中发生)。

(与摄像机距离较远)目标范围内像素不存在 (与摄像机距离较近)目标范围内存在多个像素
在这里插入图片描述 在这里插入图片描述

在正交视图中,通常UV坐标会被整列整行的抛弃,这也就是为什么正交视图下线段会整条的消失。而透视视图下会发生数个像素消失

应用DDXY解决问题

需要绘制固定单像素宽度的网格,前提是获取单个像素的UV宽度。如何获取宽度?
只需要 下一个像素的UV坐标 - 当前像素的UV坐标来计算,这正是DDXY的计算方式。
计算逻辑
为了能够取消明显的锯齿,我们选择从目标位置(0.5)左右各延伸一个像素宽度,之后使用smoothstep进行平滑着色以消除单像素的锯齿。

左右各延伸一个像素UV宽度可以确保至少有一个像素在UV范围之内。加上smoothstep的作用即可做到单像素的视觉效果。

逻辑较为简单,因此在这里我使用ShaderGraph实现这个功能:
绘制逻辑

注意:
在UV范围计算中使用了DDXY计算单个像素UV的宽度

如此,无论面积在屏幕占比如何,其宽度始终是1个像素宽度。

小面积视觉效果 大面积视觉效果
小图 大图

结论

在本次应用中,我们用到了DDXY来计算像素UV宽度,虽然做到了抗锯齿的效果,但我更倾向于将其视为smoothstep的必然结果,而非DDXY的特有功能。

结束

本文章给出了一个DDXY的应用案例,用于绘制单像素网格。

本文章中一直假设片段=像素,实际上两者并非总是相同的。

至此本文完结,任何想法或建议,欢迎在评论区留言探讨。如果文章中有任何疏漏或不准确之处,恳请指正。

猜你喜欢

转载自blog.csdn.net/qq_36288357/article/details/143090460