Babylon.js 的 TAA(时间抗锯齿) 简介

在处理计算机生成的图像时,锯齿是最令人头痛的问题之一。锯齿以锯齿状线(几何锯齿)或亮点(镜面/着色锯齿)的形式出现,并且随着移动而变得更严重。
几何锯齿(可以看大部分水平线位置)
着色锯齿(查看按钮周围的小亮点)
由于屏幕(由像素组成)的离散性质以及每个像素只能是一种颜色,因此存在锯齿。每个像素实际上是场景一部分上的一个小窗口,并且应该能够显示从该窗口可见的所有颜色以准确描述场景,但由于它只能是一种颜色,因此我们必须决定使用哪种颜色显示。有很多算法可以处理这个问题,时间抗锯齿(TAA,Temporal Anti-Aliasing)就是其中之一。本文将实现该算法的基本版本(仅适用于静态场景),完整的实现非常复杂,本文先不予实现。

TAA的一般原则

主要思想非常简单:多次渲染场景,每次使用不同的(小)偏移的投影矩阵,以便渲染的场景在一个像素的限制内稍微偏移(抖动)。通过对所有渲染结果进行平均,可以获得相当好的抗锯齿效果。
抖动相机
然而,每帧多次渲染场景会严重影响性能。这就是 TAA 的“temporal”部分的用武之地:我们每帧渲染一次场景(每次使用不同的投影偏移),并对几个连续帧的结果进行平均。这样,计算成本就分摊到了几个帧上。由此可知,获得一张平滑图像需要一些时间(例如如果我们选择 8 个不同的偏移量,那么就需要 8 帧的时间),但如果我们以每秒 60 帧的速度工作,就不太会看出来。

相机偏移

相机需要稍微移动,以便场景与下一帧略有不同,但偏移仍在一个像素的尺寸内。

因此,在投影到 2D 屏幕上之后,我们希望保持在一个像素的边界内。透视相机的投影矩阵如下:
透视相机的投影矩阵
然后投影一个 3D 点(在视图坐标空间中):
投影点(结果位于裁剪空间中)
我们想要做的是将偏移量 (dx, dy) 应用于 2D 坐标 (ax, by),即类似 (ax+dx, by+dy) 的东西,所以需要将 dx 和 dy 添加到投影矩阵中:
添加到投影矩阵的偏移量
从公式中看到在 dx 和 dy 上有一个额外的 z 因子,因为作为正常渲染管道的一部分,GPU 将执行除以 w 坐标的操作,以将裁剪空间转换为 NDC(标准化设备坐标)空间。正如上面式子所示,w=z(w是投影向量的第4个坐标),所以当我们除以w时,z因子将被抵消,这样我们可以得到我们想要的(dx,dy)移位二维坐标:
由 GPU 执行从裁剪空间到 NDC 空间的转换
这里要注意的是,(dx,dy) 应用的坐标位于 NDC 空间中,对于 x/y,该坐标位于 [-1,1] 区间内。因此,如果 dx 和 dy 是区间 [0,1] 中的值,则它们必须乘以 (2/宽度, 2/高度),其中宽度和高度是输出(通常是屏幕)的尺寸。

在代码方面,只需定义投影矩阵中的偏移量,如下所示:
定义投影矩阵内的偏移
在实际代码中,我们将应用平移 (-0.5, -0.5) 到 (dx,dy),因为 2D 像素坐标是像素中心坐标。

生成偏移量

我们可以使用随机值作为偏移量,但通过使用低差异序列可以获得更好的结果。普遍接受的 TAA 方法是使用 Halton 序列,在下面的在线demo中可以找到名为HaltonSequence的类,它生成适合我们的 TAA 实现的数字。

作为说明,以下是使用从起始数字 (2,3) 创建的 Halton 序列生成的 8 个点的位置:
来自 Brian Karis 的高质量时间超级采样(虚幻引擎)
当我们使用 Halton 序列中的所有点作为投影偏移时,要从第一个点重新开始。

这个demo它使用长度为 16 的 Halton 序列(在第 30 行)来每帧抖动相机。由于我们尚未对结果进行平均,所以会看到闪烁(在按钮和天线周围最明显)。

对每帧结果进行平均

我们可以将每帧累积(“加法混合additive blending”)到渲染目标中,然后除以帧数以获得最终平均值。为了避免除法,我们可以在将颜色写入输出之前对其应用权重。例如,如果我们使用 8 个 Halton 样本的序列,我们将为每帧使用 1/8 的权重。然而,这种方法有点麻烦,我们必须在重新启动 Halton 序列之前清除渲染目标,而且它不太适合处理运动。此外,混合对性能的要求更高,因为它需要在过程中读取源目标。

为此,我们使用此公式执行移动平均值(可以证明,当 α 很小时,此公式收敛于 xt 值的平均值 — 请参阅高质量时间超级采样的 15 和 16 页):
image.png
st-1 是给定像素的当前平均值,xt 是当前帧中像素的新颜色,st 是新平均值。 α是一个很小的数,由用户决定。较大的数字会使公式收敛得更快,但与选择较小的值相比,对于一个数字来说,与真实平均值的偏差更大,但在这种情况下,它会收敛得更慢…通常,[0.05, 0.1]范围将给出比较好的结果。

我们将使用后处理来实现此平均,以及两个渲染目标的切换:一个渲染目标将用作源 (s(t-1)),另一个将存储对于当前帧的计算结果 (s(t))。渲染目标将在计算结束时交换,以便在下一帧中交换它们的角色。

这是该算法的简单实现
image.png
“启用 MSAA”复选框可启用/禁用 MSAA,可以看到 MSAA 对着色锯齿没有帮助(看一下按钮和天线,会发现即使启用了 MSAA,锯齿仍然存在)。 MSAA 仅对边缘有用(看下手柄的位置)。现在,如果激活 PTAA,将会看到显着的改善!我们还会发现启用 PTAA 时 MSAA 没有任何区别,因此最好禁用它以提高性能。

这里我们使用了额外的后处理通道,它只是将渲染目标 s(t) 复制到屏幕。之所以用名称“PTAA”而不是“TAA”(P 表示“部分”),因为这还不是 TAA 的完整实现,只是易于实现的部分。

缺点

当尝试移动/旋转相机(激活 PTAA)时,会看到一些严重的重影:
image.png
这是因为在公式中:
image.png
st-1不应该是我们当前正在处理的像素的累加,而是我们应该找出该像素在前一幅图像中的位置,以获得该像素的累加。这就是最复杂的地方。如果想了解更多详细信息,这里有一些参考资料:

减少重影的方法是增加“PTAA 系数”的值。但在某些时候,我们会开始看到闪烁,因此不能将其增加太多。

我们还可以在相机运动时简单地禁用 PTAA。在这期间我们不会进行抗锯齿,重影就会被抑制:
Babylon.js Playground

结论

时间抗锯齿可能不再是最成功的抗锯齿技术,但它仍然是一项非常好的技术。尽管我们只触及了表面,但起码了解它的工作原理以及如何改进可能会更好。即使采用此处介绍的简单形式,我们也可以使用它来增强静态图像的渲染(例如屏幕截图)。

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搞GIS图形的sky.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值