最近涉及到了网格绘制,其中有要求是必须绘制宽度为1个像素的线条。网上学习中了解到了DDXY相关用法。研究后,遂拙笔记录个人理解。
关于DDXY功能的简短介绍
DDXY又称fwidth。
DDXY返回相邻像素的“偏导数”:即在片段着色器中,此片段(理想状态下为像素)与相邻片段的某个属性的差值。
DDXY的操作对象实际上是
片段
,像素≠片段,某些操作会打破两者一一对应的关系,例如多重采样会导致片段数量>像素数量
在本文章中,我假设两者是相等、对应的。
可以用来计算法线:
normalize(cross(ddy(worldPos),ddx(worldPos)));
可以用来勾边(加强边缘)
color += ddx(color)*2 + ddy(color)*2;//color是最终输出的颜色,2为倍率,只需凭感觉设定
计算法线的原理很容易理解,因为坐标顶点相减的结果是向量,而向量叉乘即可求得法线。
加强边缘的算法也符合直觉,因为边缘之处差值大,直接乘法即可扩大这种差异。
但是:
偏导数为什么能够抗锯齿,确定lod?
“偏导数”不过是两个数据的差值,其蕴含了什么信息,使程序可以做到这些功能?
解释 “偏导数”凭什么能“抗锯齿”
首先锯齿产生的原因:
当摄像机正视表面时,表面的像素和屏幕像素处于一一对应的关系。
当摄像机斜视表面时,摄像机能看到更大的面积,此时表面的像素远多于屏幕像素。由于屏幕像素有限,物体表面一些的像素被跳过采样。产生高频纹理锯齿。
如果你学习过PBR渲染,那么用一种能量守恒的解释可以用于理解这个关系。
当物体远离相机,物体表面在屏幕上的面积减小,也会导致迫舍弃像素而产生高频纹理锯齿。

为了能够抵抗由于跳过像素而产生的锯齿,首先需要得到一个关键信息:
跳过了多少个像素?
一旦我们知道跳过了多少个像素,则我们根据此数据来调整纹理细节层次(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的应用案例,用于绘制单像素网格。
本文章中一直假设片段
=像素
,实际上两者并非总是相同的。
至此本文完结,任何想法或建议,欢迎在评论区留言探讨。如果文章中有任何疏漏或不准确之处,恳请指正。