《Real-Time Rendering》着色基础(二)

锯齿和抗锯齿

想象一个大的黑色三角形沿白色背景缓慢移动。一个单元屏幕网格由三角形覆盖,我们所期望的是移动时该单元的像素值会平滑的变化。然而,在基本的渲染器中,通常发生的情况是网格单元的中心为完全覆盖,像素颜色会直接从白色变为黑色。标准GPU渲染也没有例外,注意下图最左边的图:


三角形的像素只有有颜色和无颜色两种,线的绘制也有类似的问题,边就是因为这种原因有锯齿感,所以这种视觉粗糙感也被称为锯齿,在动画中也被称为“小爬虫”。更加正式的说法是失真/锯齿(aliasing),避免这种效果的技术称为抗锯齿技术,或者说反走样技术。

采样和滤波理论

渲染图像的过程,内部其实是一个采样任务,这么说的原因是由于图像的生成过程其实是对一个三维场景的进行对要生成图像的每个像素进行采样获取颜色进行填充的过程。为了使用纹理映射,纹素必须进行重新采样来在多种不同的情况下获得较好的结果。为了生成动画序列图,动画经常会在一个一个统一的时间间隔中进行采样。这一节是对采样,重建和滤波的介绍,为了简洁性,大多数的材质会表示为一维状态。这些改变也可以很简单地拓展到二维,用来处理二维图像。

下图展示了将一个连续的信号采样为统一间隔的离散信号的过程:


这个采样过程的目的是数字化信息,这么做的话,信息的数量就会减少。然而,采样信号需要被重构来恢复初始的信号,这通过滤波过程来完成。

无论采样何时完成,走样都有可能出现,毕竟这是我们大部分时候不想看到的现象,所以我们需要消除锯齿感来生成看起来不错的图像。一个经典的锯齿感,或者说走样的例子是车轮效应。由于辐条的速度远快于摄像机记录图片的速度,所以轮子可能会看起来旋转的很慢,或者几乎不旋转。这一现象在下图显示:


这种效果出现的原因是轮子的图片是在一系列事件步长中获得的,这被称为时间域走样。

在图形学领域的通常的走样例子就是光栅线或三角形边的锯齿,被称为“萤火虫”现象的闪烁高光,还有当使用棋盘格纹理时,会看起来变小。

走样发生的原因是信号采样频率过低,那么所采样的信号就会比原始信号频率低,这在下图显示了:


为了得到采样合适的信号,采样频率需要是被采样信号的频率的两倍以上,这通常被称为定理,采样频率被称为奈奎斯特频率或奈奎斯特限制。奈奎斯特限制也在车轮效应那张图显示。采样定理使用最大化频率的术语的原因是其表明了信号需要是有限带宽的,这表明任何频率不会超过确切的限制。换句话说,信号需要根据与相邻采样信号的间距进行足够的平滑操作。

一个三维场景通常在使用点采样进行渲染时不会触及带宽限制。三角形边,阴影边界和其它效果会生成一个不连续变化的信号,产生都频率也是无穷大的。同样,无论采样的间距如何小,物体仍会因为很小而无法进行完全采样。因此,当使用点采样来渲染场景时是不可能完全避免走样问题的,而且我们几乎总是使用点采样。然而,当信号是有限带宽的话,有时就有可能完成。一个例子是当纹理被应用于表面时,我们可以根据与像素采样速度的比较计算纹理采样频率,如果这个而频率小于奈奎斯特限制,不需要什么特殊的操作就可以合适地进行纹理采样,如果频率过高,就是用一些算法来对纹理进行带宽限制。

重构

给定一个有限带宽的采样信号,我们现在讨论如何根据一个采样信号来重构出原始的信号。为了完成这一任务,我们需要使用一个滤波器,三个通常使用的滤波器如下:


注意滤波器的面积应该总是1,否则重构后的信号会扩大或缩小。

在下图中,使用一个盒装滤波器来重构一个采样信号:


这是一个最菜的滤波器,结果是一个不连续的阶梯式信号,不过现在仍经常用于图形学中,因为这最简单。就如图表中所看到的,盒状滤波器放置在每个采样点上,然后进行缩放,这样滤波器的最上面的点就和采样点重合。这些信号结合在一起并进行平移,重构信号就在上图右侧显示。

盒装滤波器可以被其它滤波器替代。在下图中,是一个三角滤波器,可以用来重构采样信号。


注意这一滤波器的实现是将相邻的采样点进行线性插值,所以效果比盒状滤波器好,可以看到得到的信号是连续的。

然而,三角滤波器的重构信号的平滑度很差,曲线在采样点处会有坡度的突然变化,不得不承认三角滤波器并不是一个完美的重构滤波器。为了得到完美的重构,我们使用低通滤波器,这个信号的曲线是正弦的:sin(2\pi f),其中f是信号频率。一个低通滤波器会移除所有频率比滤波器定义频率高的组件,直观来说,低通滤波器会移除信号的锐利的部分,即滤波器会模糊信号。理想低通滤波器是一个辛格滤波器:

sinc(x)=\frac{sin(\pi x)} {\pi x}

傅里叶分析的理论解释了为什么辛格滤波器是理想低通滤波器。简要来说,理由如下。理想低通滤波器在频域内是一个盒装滤波器,当它与信号相乘后,可以移除所有超过滤波器宽度的频率组件。将盒状滤波器从频域转换到空间域得到一个辛格函数,同时相乘操作会转换为一个卷积函数,这是我们在本节使用的方法,不需要确切描述该术语。

使用辛格滤波器来重构信号可以得到平滑结果,这在下图显示:


采样过程中信号会产生高频率组件,低通滤波器的任务就是移除这些组件。实际上,辛格滤波器会去除所有频率高于1/2采样率的组件。可以说,在采样频率低于1.0时,辛格滤波器是一个完美的重构滤波器。对于更普遍的情况,假设采样频率为f_s,那么样本间距为1/f_s,对于这种情况,完美的重构滤波器为sin(f_sx),消除所有频率高于f_s/2的部分。这在重采样信号时很有用,然而,辛格滤波器的宽度是无穷大的,所以在部分区域时负的,实际中会很少使用。

在低质量的盒装、三角滤波器和不实际的滤波器间存在一个很有用的中间滤波器,大多数广泛使用的滤波器函数就在这些极端之间,所有这些滤波器函数和辛格函数类似,但对于影响像素的数量有一定的限制。过于近似辛格函数的滤波器会有超过它们作用域的负值,对于应用程序,负滤波器值是不想遇到的,也是不实际的,没有负叶的滤波器通常会被使用(如高斯滤波器)。

在使用这些滤波器后,可以获得连续的信号,然而,在计算机图形学中我们不能直接显示连续的信号,但我们可以将连续信号重新采样为其它的大小,放大或缩小。

重采样

重采样通常用来放大和缩小采样信号,假设原始采样点在整数坐标处(0,1,2,...),即采样点间是一个单位长度。此外,假设在重采样后我们想要新的采样点定位在统一的a单位间隔,对于a>1,使用缩小操作(降采样),对于a<1,使用放大操作(增采样)。

扩大操作比较简单,我们先介绍这个。假设采样信号按照上节的方法重构了,直观来说,由于信号现在被完美重构,而且连续,我们需要做的就是按照想要的间隔对信号进行重采样和重构。这一过程在下图显示:


然而,这一技术在缩小操作时并不适用。原始信号的频率过高,没有办法避免走样,这样就必须用一个使用sinc(x/a)的滤波器来根据采样信号创建连续信号,之后,就可以使用所期望的间隔来进行重采样,这在上图下方显示:

换种方式解释就是,通过使用sinc(x/a)来作为滤波器,低通滤波器的宽度会增加,那么更多的高频率部分会被移除。就如上图显示,滤波器的宽度会变为两倍,然后将采样率减少为其一半。将这一点与数字图像关联的话,就类似于先模糊然后按照低分辨率采样图片。

基于屏幕的抗锯齿

如果没有合适的采样和使用滤波器,那么三角形的边会产生可以看到的粗糙感。阴影边界、高光和其它效果的颜色迅速变化时也会产生类似的问题。在这一节讨论的算法可以帮助提升渲染质量来应对这些问题,并且这些算法都是基于屏幕的,即它们只处理管线的输出采样样本。并不存在一个最好的抗锯齿技术,每一种有不同的质量优点,也有能力获得锐利细节或其它现象,在移动时的表现、内存消耗、GPU要求和速度上也都同。

对于本章开头提到的黑色三角形例子,其中一个问题是采样率低,只在每个像素网格单元的中心获取一个采样点,所以能获得信息只是当前像素是否被三角形覆盖,通过在每个屏幕网格单元使用更多的采样点,并以某种方式混合的话,就可以计算得到比较好的颜色效果,原理在下图描述:


基于屏幕抗锯齿的一般策略对屏幕使用一个采样组件然后按权重将样本累加起来获得一个像素颜色:

p(x,y)=\sum^n_i=1w_ic(i,x,y)

其中n是一个像素使用的采样样本数,函数c(i,x,y)是样本颜色,w_i是权重,范围在[0,1]内。样本位置取决于使用所有样本中的哪一个样本,函数往往使用整数部分的像素位置(x,y)。换句话说,在屏幕网格上获得样本互不相同,每个像素可选的采样组件也不同。在渲染系统中样本通常是点样本。所以,函数c可以被认为是两个函数组成的,首先是函数f(i,n)来获得需要样本点在屏幕上的浮点位置(x_f,y_f)。屏幕上的位置然后被采样,即获得精确点的颜色。采样的模式是可以选择的,渲染管线可以自行配置来计算在特定子像素位置的样本,基于每帧的设置。

抗锯齿中的另一个变量是w_i,即每个样本的权重,这些权重加起来和为1。大多数的实时渲染系统使用的方法对它们的采样点给定统一的权重,即w_i=1/n。图形硬件默认的模式是每个像素有一个中心采样点,这是上述抗锯齿等式最简单的情况。

每个像素使用多个采样点的抗锯齿算法被称为超级采样方法。概念上最简单的是全屏幕抗锯齿(FSAA),也被称为超级采样抗锯齿(MSAA),可以高分辨率的渲染场景,然后滤波邻近的样本点来创建图像。例如,假设想要一幅分辨率为1280x1024的图像,如果离屏渲染一幅2560x2048的图像,然后平均化屏幕上的每个2x2像素区域,那么目标图片就是每个像素4个采样点生成的,通过一个盒装滤波器滤波。这种与2x2网格相关的采样网格在下图显示:


不过这种方式开销较大,因为每个子样本必须被完全着色和填充,每个采样点都有一个深度值。FSAA的主要的优点就是简单。另外,这种采样方式的低质量版本是在一个屏幕轴上采样两侧,也被称为1x2或2x1超级采样。通常,出于简洁考虑,会使用二次方分辨率和盒状滤波器。英伟达的动态超级分辨率特性就是一种超级采样的更高级的组成,场景会以高分辨率渲染,然后有13个采样点的高斯滤波器会用来生成要显示的图片。

一种和超级采样相关的采样方法基于累计缓冲的概念。不再不使用大的离屏缓冲,这个方法使用一个和预期图像分辨率相同的缓冲,但颜色的每个通道的比特数更多。为了得到一个场景的2x2采样,要生成4张图像,同时根据需要在屏幕x或y方向上移动半个像素。每张图片的生成基于网格单元下的一个不同的采样位置。每帧重渲染场景几次的额外的开销以及将结果复制到屏幕让这个算法对于实时渲染系统来说开销较大。当性能并不那么重要时,使用这个方法来生成高质量图像的话是挺有用的,因为放置在任何位置任何数量的采样点可以逐像素使用。累计缓冲过去常是硬件的一个独立部分,它在OpenGLAPI中支持,但在3.0版本中弃用了。在现代GPU中,累计缓冲的概念可以在像素着色器中实现,通过将高精度颜色格式输出到缓冲中。

当使用类似物体边缘、高光和锐利阴影等造成锐利颜色改变的效果时,额外的采样是必须的。阴影可以制作得很柔和,高光也可以平滑来避免锯齿感。特殊的物体类型的大小会因此会增加,例如电线,因此它们需要确保沿其长度分布方向的位置上至少要覆盖一个像素。物体边缘的锯齿仍是一个主要的采样问题,当物体边缘可以在渲染是检测到,并且受因数影响时,可以使用分析的方法来解决,但这样的话开销会很大,而且简单的增加采样数的话,稳定性会很低。然而,GPU的类似保守光栅化和光栅器顺序图表的特性带来的新的可能性。

例如超级采样和累计缓冲的技术通过生成采样点来实现,并且完全由独立计算的光影和深度值指定。整体的优点相对较差,并且开销较大,因为每个采样点都必须调用一次像素着色器。

多重采样抗锯齿(MSAA)减少了高额计算带来的开销,通过每像素计算一次表面着色,并且在采样点间共享结果。假如每个像素片段由四个采样点位置,每个采样点有它自己的颜色和深度值,但对于每个像素的片段,像素着色器只会调用一次。如果所有MASS的位置采样点由一个片段覆盖,那么着色采样点就在像素的中间。反之,如果片段只覆盖少量的位置采样点,着色采样点的位置可以被移动到新的位置来更好的表示位置覆盖。举个例子,这样可以避免着色采样离开纹理的边界。这种位置调整被称为质心采样或质心插值,如果启用的话GPU会自动执行这个过程。质心采样避免了离开三角形的问题,但会导致导数计算出现不正确的值。如下图:


因为片段只需着色一次,所以MSAA比常规的超级采样模式要快。它以更高的速度聚焦于计算采样的覆盖程度,并且共享所计算的着色。通过分离采样和覆盖会有可能节省更多内存,它们轮流执行,可以让抗锯齿更快,即使用更少的内存,渲染速度更快。2006年,英伟达介绍了一种技术,名为覆盖采样抗锯齿(CSAA),AMD紧随其后提出了加强质量抗锯齿(EQAA)技术。这些技术通过以更高的速度只存储片段的覆盖来实现,例如,EQAA的2f4x模式存储两个颜色和深度值,并在四个采样位置间分享。颜色和位置并不只存储于特定的位置,而是存储在一个表中。4个采样点的每一个接着就只需要一个位来指出两个存储值的哪一个于它的位置相绑定。见上面那张图。覆盖采样点指出了每个片段对最终像素颜色的贡献。如果颜色存储的数量溢出,一个已存储的颜色值会弹出,然后它的采样点被标记为未知,然后这些采样点不会为最终颜色做出贡献。对于大多数场景,相对来说只有很少的像素包含3个或更多的互不相同的可见不透明片段,因此这种模式在实际中表现不错。然而,对于更高的质量,《极限竞速 地平线》这款游戏使用了4x的MSAA,尽管EQAA性能更好。

一旦所有的几何体被渲染到多重采样缓冲,接下来会执行一个解析操作。这个过程会平均所有的样本颜色,用来决定像素的颜色。值得注意的是当使用高动态颜色范围时,使用多重采样会躁症问题,在这些情况下,我们需要在解析前进行色调映射来解决问题,这个操作可能会开销较大,所以会使用其它更简单的色调映射函数近似或其它方法。

默认的,MSAA使用一个盒状滤波器来解析。在2007年,ATI介绍了一种名为自定义滤波器抗锯齿的技术(CFAA),使用狭窄且宽广的三角滤波器来扩展到其它的像素单元。这个模式曾用来代替EQAA。在现代GPU中,像素或计算着色器可以获取MSAA的样本,可以使用任何重构滤波器,包括从临近像素样本获取采样的方法。一个更宽广的滤波器可以减少锯齿,但会损失尖锐细节。Pettineo发现三次方平滑插值和拥有2个或3个像素宽度的B样条滤波器可以整体获得很好的效果。不过同样存在性能消耗,因为使用一个自定义的着色器来模拟默认的盒状滤波器解析会花费更长的时间,且更宽的滤波器核就意味着增加样本的访问消耗。

英伟达内置的TXAA提供一种类似的方法,使用一种覆盖更广范围的重构滤波器,相比单个像素的,可以获得更好的效果。TXAA和更新的MFAA(多帧抗锯齿)模式都使用暂存抗锯齿(TAA),这是一种通用的使用来自前一帧的结果来优化图像的技术。某种程度上这种技术可以实现的前提在于程序员可以每帧设置MSAA的采样模式。这样的技术可以解决类似车轮效应的走样问题,也可以提升物体边的渲染质量。

想象以下通过生成一系列图片来手动执行一个采样模式,其中的图片是每个渲染器使用每个采样点获取的像素下的一个不同位置生成的。这种偏移是通过向投影矩阵中添加一个微小的平移量完成的。生成和平均在一起的图片越多,结果就越好。多重偏移图片的理念在暂存抗锯齿算法中使用。一张图片生成,可能使用MSAA或其它方法,然后与上一张图片混合在一起。通常只有两帧或四帧会被使用。之前的图片可能会以指数级别降低权重,尽管当观察者和场景不移动时,会造成帧闪烁的效果,所以会让前一帧和当前帧的权重设为相等。通过使用在一个不同的子像素位置上的帧采样,这些采样的加权和可以得到比单帧操作更好的平均边估计效果。所以一个使用前两帧来进行平均的系统可以得到更好的结果。每帧不需要额外的采样点,这是让这种类型的方法更吸引人的原因。甚至可能使用暂存采样来允许高于显示器分辨率的低分辨率图片的生成。另外,光照算法或其它需要更多采样数才获取更好结果的技术可以取而代之的每帧使用很少的采样点,这是因为结果可以在几帧上混合。

当为没有额外采样消耗的静态场景提供抗锯齿时,对于暂存抗锯齿,这种算法会带来一些问题。如果每帧的权重不相等,静态场景中的物体会闪烁显示。快速移动物体或快速移动摄像机会躁症幽灵效果,即物体会有拖尾轨迹,这是前一帧混合的结果。一种解决幽灵效果的解决方法是只在缓慢移动的物体上使用这种抗锯齿。另一种重要的方法是使用重投影来更好地将前一帧和当前帧物体关联在一起。在这种模式下,物体生成一些运动矢量,被存储在一个独立的速度缓冲中,这些矢量被当前像素位置减去来找出物体表面位置的前一帧颜色像素。不是当前帧的表面的一部分的样本会被剔除。因为没有额外的采样,所以对于暂存抗锯齿来说几乎不需要相关的额外工作。过去几年行业内常专注于这种类型算法的研究,一些原因在于延迟渲染并不兼容MASS和其它多重采样算法。由于方法的不同,以及应用程序使用的不同,和目标的不同,目前由大量的技术用来避免人工感问题以及提升质量。例如,Wihlidal的报告,展示了将EQAA、暂存抗锯齿和不同的滤波器技术应用到一个棋盘格采样模块会如何降低像素着色器调用数量的同时保持质量。Iglesias-Guitian等人总结了之前的工作并展示了他们使用像素历史和预测来减少滤波人工错误的计划。Patney等人扩展了Karis和Lottes在虚幻4实现的TAA工作,用来在虚拟现实程序上使用,添加了基于视觉移动补偿的变量大小采样。

采样模式

高效的采样模式是减少走样现象的关键元素,使用暂存或其它。Naiman展示了人眼会察觉到近水平层处和近垂直层处边的锯齿。靠近45度坡度的边是最容易被看出是否由锯齿的。旋转网格超级采样(RGSS)使用一个旋转四边形模式来获得在水平和垂直方向上更大的分辨率的像素。下图显示了该图的例子:


RGSS模式是拉丁超立方或N-rooks采样的一种组成,其中n个样本被放置在一个n \times n的网格中,每个样本使用1行1列。通过使用RGSS,4个采样点的每一个在4 \times 4的子像素网格的每个单独的行和列上。这样的模式相比传统的2\times 2采样模式在捕捉近水平和垂直边上得到的效果更好,其中这样的边会覆盖偶数个样本,所以得出更少的效果级别。

N-rooks是创建一个好的采样模式的开始,但还不足够。举个例子,样本会沿着子像素网格的对角线排列,这样对于几乎平行于这个对角线的边来说会得到很差的结果,见下图:


为了更好的采样,我们想要避免两个采样点靠近彼此,我们也想要得到同一的分布,让样本覆盖整个区域。为了组成这样的模式,例如拉丁超立方采样的分层采样技术会与其它类似抖动、霍尔顿序列和泊松盘采样的技术结合在一起。

实际中GPU架构通常硬线连接这样的采样模式到硬件中,用来进行多重采样抗锯齿。下图展示了一些实际中使用的MSAA模式:


对于暂存抗锯齿,平均模式是程序员所期望的,其中采样位置可以帧与帧之间变化。举个例子,Karis发现一个基本的霍尔顿序列比任何GPU提供的MSAA模式工作得要好。一个霍尔顿序列在空间中生成采样点,无序但偏差低,即他们在空间中分布均匀,并没有聚集在一起。

当一个子像素网格模式得到一个三角形如何覆盖网格得更好的估计时,它并不是理想的。一个场景可以由屏幕上多个任意小的物体组成,意味着没有一个恰当地采样率可以完美的步捕捉它们。如果这些微小物体组成一个模式,以常量间隔采样可以得到莫尔条纹或其它干涉模式结果,在超级采样中使用网格模式和这个很像。

一种解决方案是使用随机采样,这能给出一个更加乱序的模式。例如上面的图中显示的模式就符合这一点。想象以下在远处的细齿梳,少量梳齿覆盖每个像素。一个常规的模式会造成严重的伪造感,因为采样模式随着细齿梳的频率相位不断变化。使用一个低排序的采样模式可以打破这些模式。乱序趋向于使用噪声来替代重复的走样效果,这可以让人感觉不出来那么严重的伪造感。一个使用更少架构的模式可以帮助缓解问题,但当像素级别重复时仍会出现走样问题。一个解决方法时在每个像素上使用不同的采样模式,或者随时间改变每次采样的位置。交错采样,即每个像素有不同的采样模式,这一技术在过去几十年不定期的在硬件中支持者。例如,ATI的抗失真引擎允许每个像素有至多16个采样点,并且有至多16个不同的用户定义采样模式,可以以一种重复模式进行混合。Molnar、Keller和Heidrich,发现使用交错随机采样可以在每个像素使用相同的模式时减少走样粗糙感。

一些其它的GPU支持的算法也值得注意。一种让采样点可以影响多于一个像素的实时抗锯齿方法师英伟达的过去的五点形方法。五点形即5个物体的组合,其中4个位于四边形上,第5个位于中心,例如骰子的5点。五点形多重采样抗锯齿使用这种模式,将4个采样点放置在像素的角上,见下图:


每个角采样点的值贡献给它的4邻像素。不再让每个采样点的权重相等,中心的采样点的权重设为1/2,每个角采样点的权重为1/8.由于这种采样点的共享,每个像素只需要两个采样点的平均,结果远好于双采样的FSAA方法。这种模式近似于一个2维三角滤波器,这在之前的小节谈过,比盒状滤波器高级。

五点形采样也可以被用于暂存抗锯齿,通过让每个像素使用一个采样点。每一帧再每个轴上沿着前一帧偏移半个像素,偏移方向在帧之间交替。前一帧提供像素角采样点,并且二次线性插值用来快速计算每个像素的贡献。结果会与当前帧平均混合。每帧相同的权重意味着对于一个静态场景没有闪烁伪造感。移动物体时的视觉问题仍然存在,但这个方法很容易去用代码表示,而且当每帧的每像素只是用一个采样点时能得到很好的效果。

当在单帧中使用时,五点形因为通过采样点共享而只需要使用两个采样点,所以说开销少。RGSS模式在近水平和垂直边上能够获得更好的分级颜色。第一个对于移动设备的模式FLIPQUAD,它结合了上面这两种模式的特点,它的优点是每个像素只使用两个采样点,并且质量类似于RGSS(每像素使用了4个采样点)。这种采样模式在下图展示:


类似于五点形,两个采样点的FLIPQUAD模式也可以用于暂存抗锯齿,并且覆盖两帧。Drobot的混合重构抗锯齿(HRAA)让两采样点模式表现得更好。棋盘格模式也可以用在暂存抗锯齿中。El Mansouri讨论了使用两个采样点的MSAA来创建棋盘格渲染,解决走样问题的同时减少着色器开销。Jimenez使用SMAA、暂存抗锯齿和一些不同的技术来提供一个解决方案,用来应对抗锯齿质量会在渲染引擎加载时变化的问题。Carpentier和Ishiyama在边上采样,旋转采样网格45°,它们将FXAA和这种暂存抗锯齿方法来在高分辨率显示器上进行高效的渲染。

形态学方法

走样问题常出现在边上,往往来自几何体、硬边缘阴影或高光。利用和走样出现原因架构相关的知识可以帮助我们来得到更好的抗锯齿结果。在2009年,Reshetov发表了一种算法,称为形态学抗锯齿(MLAA)。形态学即和结构或形状相关的意思。该领域的工作很早就已经完成了,可以追溯到1983年的Bloomenthal。Reshetov的论文重新让该研究出现在人们眼前,用来代替多重采样方法,强调搜寻和重构边。

这种抗锯齿方式是当作后期处理来执行的,即,渲染是按照一般的方式完成,然后结果用来进行抗锯齿处理。大量的技术自2009年开始出现,它们基于额外的缓冲完成,例如深度和法线缓冲,可以得到更好的结果,例如子像素重构抗锯齿(SRAA),但只适合对几何体的边进行抗锯齿。分析性方法,例如几何缓冲抗锯齿(GBAA)和边距抗锯齿(DEAA),它们让渲染器计算一些额外的信息:三角形的边定位在哪,即边离中心像素的距离。

最常见的模式只需要颜色缓冲,意味着它们也可以提升来自阴影、高光或如剪影边渲染的不同后期处理技术渲染效果的边的质量,例如,方向性定位抗锯齿(DLAA),基于一个理念,即近垂直边应该进行水平模糊,同样,近水平边应该进行垂直模糊。

高级的边缘检测致力于寻找某一角度边的像素,并决定它的平均值。在可能边周围的像素被进行检测,尽可能地去重构出原始边。像素上的边效果然后可以用来和周围的像素混合,下图展示了这一过程:


Iourcha等人通过检查像素的MSAA采样点来寻找边界,得到比较好的结果。注意边缘预测和混合相比基于采样点算法,可以得到更高精度的结果。例如,一种使用每像素4个采样点的技术智能得出物体边的5等级的混合效果:没有采样点覆盖、一个覆盖、两个、三个和四个。预测边缘位置的方法可以得到更多的定位,所以也就能给出更好的结果。

对于基于图像的算法,如果使用不当会得到不当的结果。首先,当两个物体间的颜色区别小于算法阈值时会无法检测到边界。位于三个或更多可以观察到的表面重合处的像素会很难进行描述。高对比度的表面或高频率的元素,它们的颜色会在像素间急剧变化,可能会让算法错误识别边缘。特别地,当使用形态学抗锯齿时,文本的质量通常得不到保证。物体的角也是一个问题,一些算法可能会得到圆角效果。对曲线使用算法时,也会因为预测为直线而带来不好的效果。单像素变化会在边缘重构时带来较大的偏移,这样的话会造成帧到帧的伪造感。改善这一问题的方法时使用MSAA掩码来提高边缘检测正确率。

形态学抗锯齿模式只使用提供的信息。例如,如果一个物体比像素还细,例如电线,这样屏幕上会出现沟壑,因为物体不太可能恰好覆盖在像素的中心位置上。在这种情况下,使用更多的采样点会提升质量,但基于图像的抗锯齿单独使用就不行。额外地,执行时间会基于看到的内容而变化,例如,观察一片草地时抗锯齿的时间是观察天空时的3倍。

总之,基于图像的方法可以为现代内存提供抗锯齿支持,并且消耗较少,所以它们被许多应用程序采用。只有颜色的方法可以从渲染管线中分离出来,让它们便于修改或禁用,甚至可以作为GPU驱动选项暴露出来。最受欢迎的两种算法是快速近似抗锯齿(FXAA),和子像素形态学抗锯齿(SMAA),某种程度上是因为两种方法对多种机器都提供了开源代码实现。两种算法只是用颜色输入,同时SMAA的优点在于可以使用MSAA采样点。每种方法有它自己的不同的可获得的设置,在速度和质量间权衡。开销通常是每帧在1至2毫秒,这通常是电子游戏所消耗的时间。最后,两种算法都有暂存抗锯齿的优点。Jimenez发表了一种SMAA的优化实现,快于FXAA,并且描述了一种暂存抗锯齿的模式。

透明、透明度和合成

有多种方法能让光穿过半透明物体。对于渲染算法,它们可以被粗略地分为基于灯光的和基于观察的效果。基于灯光的效果即物体会让灯光衰减或分散,照亮其它物体,渲染出不一样的效果。基于观察的效果是渲染半透明物体。

在这一节我们会介绍最简单的基于观察的透明的组成,即半透明物体会显示后面物体的衰减颜色。更高级的基于观察和灯光的效果,例如磨砂玻璃,光的弯曲,由于透明物体的厚度产生的光的衰减,以及观察角度的变化带来的反射会穿透效果的变化,这些效果会在之后的章节讲解。

一种给出透明度光照的方法是纱门透明度,该方法的理念是使用一个与像素对齐的棋盘格填充模式来渲染透明三角形,即每隔一个三角形像素被渲染后,从而让其后的物体部分可见。通常屏幕上的像素挨得足够近,棋盘格本身不可见。该方法主要的缺点是通常只有一个透明物体可以被渲染在屏幕上的对应区域内。例如,如果一个透明红色物体和一个透明绿色物体在一个蓝色物体上渲染,在棋盘格模式上只有三种颜色中的两种可以显示。同时,50%的棋盘格限制大。其它更大的的像素掩码可以被用来给定其它的百分比,但这会创建出容易察觉的模式。

也就是说,该技术的一个优点就是它的简单性。透明物体会在任何时候被渲染,以任何的顺序,并且不需要任何的特殊硬件。解决透明度问题有一个方法,是将所有物体覆盖的像素变得不透明。同样的理念用于抗锯齿剪贴纹理的边中,但在一个子像素的级别上,使用一个称为alpha平均的特性。

Enderton等人提出了随机半透明的的方法,使用子像素纱门掩码结合随机采样。一张通过使用随机网点模式来显示透明度片段混合的图片显示如下:


对于这种模式,每个像素必须要使用大量的采样点,这样才能让结果看起来可信,同时对所有的子像素采样点需要相当数量的内存。该方法比较吸引人的是不需要进行混合,同时抗锯齿、透明度和其它创建部分覆盖像素的效果只需要单一机制就可以实现。

大多数的透明度算法会混合在其后物体的颜色,为此,就需要透明度混合的概念。当一个物体渲染在屏幕上时,每个像素会绑定一个RGB颜色和一个深度值。额外的组件,称为alpha(\alpha),可以对覆盖物体的像素顶底。Alpha是一个形容不透明度的值,是一个对于给定像素的物体片段的覆盖程度。Alpha值为1.0意味着物体时不透明的,完全覆盖像素的感兴趣区域,0.0意味着像素完全不可见,机片段是完全透明的。

一个像素的alpha值可以代表不透明度或覆盖程度,或者两者都可以,取决于使用情况。举个例子,一个肥皂泡的边可能覆盖像素的3/4,0.75,可能会是几乎透明的,让90%的光线进入眼睛,所以它是0.1不透明度的,它的alpha值可以是0.75 \times 0.1 = 0.075。然而,如果我们使用MSAA或类似的抗锯齿模式,覆盖情况可能会被采样点自身考虑在内。3/4的采样点会被肥皂泡影响,每个采样点我们会将0.1不透明度作为alpha值。

混合顺序

为了让物体渲染为透明状,它需要渲染在已有的场景上面,同时alpha值设为小于1的值。每个被物体覆盖的像素会从像素着色器获得RGBA颜色值。将该片段的值和原始像素的颜色混合通常使用覆盖操作,即:

c_0=\alpha_sc_s+(1-\alpha_s)c_d

其中c_s是透明物体的颜色,可称为源颜色,\alpha_s是物体的alpha值,c_d是混合前的像素颜色,可称为目标颜色,c_0是透明物体覆盖已存在场景的结果颜色。在渲染管线中,传入c_s\alpha_s,接着会用计算的结果颜色替换元数像素颜色c_d。如果传入的RGBA的不透明度值为1.0,该等式就会简化为物体颜色直接替换像素颜色。

例子:一个红色的半透明物体在一个蓝色地面上渲染。假设物体的RGB值为(0.9,0.2,0.1),蓝色地板为(0.1,0.1,0.9),物体的不透明度为0.6,那么这两个颜色混合为:

0.6(0.9,0.2,0.1)+(1-0.6)(0.1,0.1,0.9)

会得到一个颜色(0.58,0.16,0.42)

覆盖操作会给要渲染物体一个半透明效果。这种方式完成的透明度起了效果,一种透过物体看到后面物体的感觉。使用覆盖模拟了一种真实世界中的薄布料效果。在该布料后的物体的表现为部分可见——布料的线是不透明的。实际中,宽松的布料有一种覆盖全角度的alpha平均效果。这里的要点是alpha模拟了材质覆盖像素的程度。

覆盖操作在模拟其它透明效果时不太可信,尤其是观察有颜色的玻璃或塑料时。现实世界中一个在蓝色物体前的红色过滤器通常会让蓝色物体看起来更暗,因为该过滤器反射了一些穿过的光。见下图:


当覆盖操作用于混合时,结果是部分红色和蓝色相加在一起。如果将两颜色相乘效果会更好,同时添加所有被透明物体反射掉的光。这种物理透光效果在之后的章节讲解。

作为混合的基本操作,覆盖操作是透明效果最常使用的。另一个有时使用的操作是相加混合,像素值被简单地叠加:

c_0=\alpha_sc_s+c_d

这种混合模式可以较好地实现辉光效果,例如闪电或火花这些不会衰减像素值,只是增亮的效果。然而,这个模式在透明效果上实现得并不好,不透明表面看起来并不通透。对于一些多层的半透明表面,例如烟雾或火焰,相加混合会增加效果颜色的饱和度。

为了正确地渲染透明物体,我们需要在绘制完不透明物体后再渲染。这通过先关闭混合渲染所有不透明物体,然后打开覆盖操作渲染透明物体。理论上我们可以一直让覆盖操作开启,因为1.0不透明alpha值会给出源颜色,并隐藏目标颜色,但这么做开销较大,并且没有实际的用处。

深度缓冲的限制是只有一个物体会逐像素存储。如果一些透明物体重合了部分像素,深度缓冲本身就很难对所有可见物体进行处理。当在给定像素使用覆盖透明表面时,通常需要按照从后往前的顺序渲染。如果不这么做的话就会得到不正确的视觉效果。一种实现这种顺序的方法是逐一对物体进行排序,按照它们的中心沿观察方向的距离。这种粗略地排序能得到不错的结果,但存在许多潜在地问题。首先,这个顺序只是粗略的,所以判断为远处地物体实际上可能在判断为近处地物体前。同时交错大的物体时不可能对所有观察角度进行逐网格的分解的,很难将网格分离为一小片。见下图左侧:


甚至有凹面的网格也会出现排序错误问题,比如当它与屏幕重合时。

尽管如此,由于该方法的简单和快捷,同时不需要额外的内存或特殊的GPU支持,这种粗略的透明度排序还是经常使用。如果要实现的话,通常最好在渲染透明物体时关闭深度缓冲替换操作,即深度测试仍进行,但测试通过的表面不改变存储在深度缓冲中的值,最近的不透明表面的深度值仍完全保留。使用这种方法的话,所有的透明物体至少会以某种形式显示,与之对应的是当旋转摄像机时改变排序而让透明物体突然出现或消失。其它的技术可以帮助改善效果,例如绘制每个透明网格两次,首先渲染背面,然后是正面。

覆盖操作也可以被修改,从前往后混合也可以得到相同的效果,这种混合模式为叠底操作:

c_=\alpha_dc_d+(1-\alpha_d)\alpha_sc_s\\a_0=\alpha_s(1-\alpha_d)+\alpha_d

注意叠底操作要求目标颜色的alpha值,覆盖操作不需要。换句话说,目标——即较近的透明表面与下方的表面混合,正因为是不透明的,所以要求一个alpha值。叠底的公式类似于覆盖,只是交换了源颜色和目标颜色。同样,注意计算alpha的公式仍是要求顺序的,因此源颜色和目标颜色的alpha值才能被交换,结果的alpha值保持不变。

alpha值的等式来源于对片段alpha值覆盖的考虑。Proter和Duff注意到由于我们不知道每个片段覆盖区域的形状,我们可以假设每个片段根据alpha值部分覆盖片段,例如,如果\alpha_s=0.7,像素会以某种方式分为两个区域,0.7部分会被源片段覆盖,0.3部分不会。不考虑其他情况,目标颜色的片段覆盖,假设\alpha_d=0.6,会与源片段部分重叠。该公式有一个几何解释,见下图:

与顺序无关的透明

叠底操作是将所有透明物体渲染进独立的颜色缓冲中,然后将该颜色缓冲叠在不透明场景上,使用覆盖操作。另一个叠底操作的使用是用来执行一个顺序无关的透明算法(OIT),常被称为深度剥离。渲染无关意味着应用程序不需要执行排序。深度剥离的理念是是使用两个深度缓冲和多个渲染过程。首先,第一个渲染过程由所有表面的深度值构成,包括透明表面的深度值,存储在第一个深度缓冲。在第二个渲染过程中,所有的透明物体被渲染。如果一个物体的深度值匹配第一个深度缓冲中的值,我们就可以知道这个是最近的透明物体,然后将其的RGBA值存储进一个独立的颜色缓冲。我们同样将透明物体的深度值从该层剥离开,如果存在的话,它就在第一个深度值后,也是最近的。该深度值是第二近的透明物体的距离。接下来的过程继续使用叠底操作剥离和添加透明层,在几个过程后我们停下,然后在不透明物体图像上混合透明物体图像。见下图:


该方法的一些变体也被提出。例如,Thibieroz给出了一个从后往前实现的算法,它有着可以立即混合透明值得优点,意味着不再需要独立得alpha通道。深度剥离的一个问题是需要得知对于捕捉所有的透明层,花费多少的渲染过程是足够的。一个硬件方案是提供一个像素绘制计数器,这告诉我们在渲染期间有多少像素被写入。当一个过程没有渲染像素时,渲染就结束了。使用叠底操作的优点是最重要的透明层,这些眼睛最先观察到的,是最早被渲染的。每个透明表面总是增加它所覆盖的像素的alpha值。如果一个像素的alpha值接近1.0,对像素的混合操作会让其接近不透明,因此更多的远处物体会由一个很难察觉的透明效果。从前往后的剥离会减少,当被一个过程渲染的像素数量小于某一最小值时,或者配置一个固定数量的过程。这些方法没法和从后往前的剥离相比,因为最近的层往往是最后绘制的,所以可能会丢失一些信息导致提前失败。

深度剥离是很有效的,不过在每个独立的渲染过程逐一分离所有的透明物体时,会变得很慢。Bavoil和Myers提出了双深度剥离,其中最近和最远的深度剥离层会在每个渲染过程中被剥离,这样会让渲染过程的数量缩减为原来的一半。Liu等人发现一个了桶排序,可以在单个渲染过程中至多捕捉到32个层。这种类型方法的一个缺点是需要相当数量的内存来对所有的层排序。通过MSAA的抗锯齿或类似的方法会极大地增加开销。

以一种交互的比率将透明物体恰当混合的问题并不是缺少算法,而是要将这些算法有效率地映射到GPU上。在1984年,Carpenter提出了A缓冲,是多重采样的另一种形式。在A缓冲中,每个渲染的三角形会对每个屏幕网格单元创建一个覆盖掩码,完全覆盖或部分覆盖。每个像素存储所有相关片段的列表。不透明片段可以丢弃在其后的片段,类似于Z缓冲。所有的片段都是为透明表面存储的。一旦所有的列表构成了,会通过遍历所有的片段并处理每个采样点来得到最终的结果。

在GPU上创建链接片段列表的理念在DirectX11的新功能上得以体现。使用的特性包括无序访问试图(UAVs)和原子操作。使用MSAA的抗锯齿通过访问覆盖验码得能力开启,并在每个采样点上使用像素着色器。这个算法通过光栅化每个透明表面和在一个长数组中插入生成的片段来实现。结合颜色和深度值,会生成一个独立指针结构,它会为每个像素链接当前片段和前一个片段。一个独立的渲染过程接着被执行,其中一个充满屏幕的四边形被渲染,这样在每个像素上调用像素着色器。这个着色器会使用接下来的链接搜寻每个像素上的不透明的片段。每个搜寻到的片段会和之前的片段一起按顺序排序。因为混合操作是由像素着色器实现的,每个像素可以使用不同的混合模式。随着GPU和API的不断发展,已经通过减少原子操作的消耗来提升了性能。

A缓冲的优势是只会存储每个像素需要的片段,因为实在GPU上实现的链接列表。但这也会成为一个缺点,因为需要的存储空间大小在渲染前是不得而知的。有毛发、烟雾或其它有可能重叠透明表面的效果的场景,会产生大量的片段。Andersson提出,对于复杂的游戏场景,多达50个的透明网格,例如叶子,以及多大200个的半透明粒子可能会重叠

GPU上通常由许多内存资源,例如缓冲和数组会提前存储,链接列表方法也不例外。用户需要决定多少内存是足够的,用尽内存会造成可见的伪造感。Salvi和Vaidyanathan提出一个方法来解决这一问题,称为多层alpha混合,使用了因特尔的像素同步的GPU特性。见下图:


这种能力提供了一种可编程混合,使用的开销比原子操作少。他们的方法会重新组成存储和混合,这样就可以很好地在内存耗尽时减少内存。一个粗略的排序顺序可以优化他们的方法。DirectX11.3介绍了一种光栅器顺序视图,这是一种允许透明方法在所有支持该特性的GPU上实现的缓冲。移动设备有着类似的技术,称为拼贴本地存储,允许他们实现多层alpha混合。这种结构的性能不错,然而,也就意味算法的开销会很大。

有种方法基于k缓冲的理论,由Bavoil等人提出,其中首次少量可见的层被保存并尽量存储尽可能起来,同时丢弃和结合深层的层。Maule等人使用k缓冲,并且使用权重覆盖来解释这些远一些层。按权重的累加和按权重平均透明度的技术是与顺序无关的,可以在单个渲染过程中完成,可以在几乎所有的GPU上运行。问题在于它们并没有考虑物体的顺序。所以,举个例子,使用alpha来代表覆盖程度,一个红色薄纱围巾位于薄纱蓝色围巾上,会呈现紫色,反之亦然。当接近不透明的物体得到很弱的结果时,这一类的才发对于高透明度表面和粒子的视觉表现会很有用,见下图:


按权重累加的透明度公式为:

c_0=\sum^n_{i=1}(\alpha_ic_i)+c_d(1-\sum^n_{i=1}\alpha_i)

其中n是透明表面的数量,c_i\alpha_i代表透明度值,c_d是场景中不透明部分的颜色。两个和累积在一起,在透明表面渲染时会分离存储,在透明渲染过程的最后,每个像素都会调用该等式。这种方法的问题在于第一个累加和饱和了,即生成的颜色值会高于(1.0,1.0,1.0),背景颜色会有反相效果,因为alpha的值会高于1.0。

按权重平均通常是我们想要的方式,因为它避免了这些问题:

c_{sum} = \sum^n_{i=1}(\alpha_ic_i), \alpha_{sum}=\sum^n_{i=1}\alpha_i

c_{wavg}=\frac{c_{sum} } {\alpha_{sum} },\alpha_{avg}=\frac{\alpha_{sum} } {n}

u=(1-\alpha_{avg})^n

c_0=(1-u)c_{wavg} + uc_d

第一行代表两个在透明渲染过程中生成的独立缓冲中的结果,每个表面会使用其alpha值进行权重和。近乎不透明的表面贡献的颜色更多,接近透明的表面影响较小。通过使用c_{sum}除以\alpha_{sum}我们可以得到一个权重平均透明颜色。\alpha_{avg}值是所有alpha值的平均。值u只在平均alpha值n次后目标颜色的可见性估计。最后一行是一个覆盖操作,(1-u)代表源颜色alpha值。

使用权重平均的一个限制是,对于相同的alpha值,它会同等地进行颜色混合,不考虑顺序。McGuire和Bavoil介绍了按权重混合的顺序无关透明度方法来给出更可信的结果。在他们的公式中,到表面的距离同样影响权重,较近的表面影响更大,同样,不再平均alpha值,u值是通过与$(1-\alpha_i)部分相乘同时从一减去计算得到的,得到了真正的表面alpha值的平均。该方法得到的结果在下图显示:

缺点在于,在一个大的场景中,物体彼此靠近的话,从距离上看,物体会有几乎相同的权重,这会让结果相比权重平均来说有一些不同。同样,当摄像机到透明物体的距离改变了,深度权重实际上也会变化,但变化是逐级的。

McGuire和Mara拓展了这个方法,包含了一个看似可信的穿透颜色效果。在之前提到过,所有本节讨论的透明算法是混合不同颜色,而不是过滤它们,来模仿像素平均。为了得到一个颜色过滤效果,不透明场景会被像素着色器读取,并且每个透明表面与覆盖像素颜色相乘,将结果存储在第三个缓冲中。这个缓冲中,不透明物体被透明物体染色,然后这个缓冲会在处理透明缓冲时替代不透明场景。这个方法能成功的原因是,不同于覆盖的透明,颜色穿透是与顺序无关的。

目前为止还有一些其它的算法,使用我们目前介绍过的技术中的一部分元素。例如,Wyman通过内存要求、插入和几何方法、alpha值或几何覆盖是否被使用以及片段是否被丢弃来将之前的相关工作进行了分类。他通过寻找之前研究的不足指出提出了两种新的方法。他的随机层alpha混合方法使用了k缓冲,按权重平均和随机透明度。他的其它算法是Salvi和Vaidyanathan的方法的一种变体,使用覆盖掩码替代alpha值。

虽然有大量的方法来应对透明度问题,但并没有一个完美的解决方案。我们推荐感兴趣的读者看一下Wyman的论文和Maule等人的更细节的关于交错透明度算法的调查。

预乘Alpha和合成

覆盖操作也用于混合图片或合成的渲染物体。这个过程被称为合成。在这种情况下,每个像素的alpha值和RGB值一起存储在物体中。由alpha通道构成的图像有时被称为遮罩,它显示了物体的剪影形状。这个RGBA图像可以用来和类似的元素或背景混合。

一种使用合成RGBA数据的方法是使用预乘alpha,即RGB值在使用前会与alpha值相乘,这可以让合成覆盖操作更有效率:

c_0=c'_s+(1-\alpha_s)c_d

其中c_'s是预乘源通道。预乘的alpha值可以不需要改变混合状态来使用覆盖操作和相加操作,因为源颜色会在混合期间加上。注意,使用预乘RGBA值,RGB组件通常不会大于alpha值,尽管它们可以用来创建过亮的半透明值。

渲染合成图片可以非常自然地和预乘alpha相结合。一个抗锯齿的不透明物体渲染在一个黑色背景上的话,会默认提供预乘值。假设一个白色三角形沿边覆盖某些像素的40%,通过使用抗锯齿,像素值可能会被设为0.4,即我们会为这个像素存储(0.4,0.4,0.4)的值。如果要存储alpha值的话,就会是0.4。RGBA值就会是(0.4,0.4,0.4,0.4),这是一个预乘值。

另一个存储图片的方式是使用非乘alpha值,或者称为非预乘alpha,即RGB值不会和alpha值相乘。对于白色三角形的粒子,非乘颜色会被设为(1,1,1,0.4)。这种表示方法的优点是会保留三角形的原始颜色,但在显示前需要和存储的alpha值相乘。最好在进行过滤和混合操作时使用预乘值,因为例如线性插值的操作使用非乘alpha值的话会不正确,例如黑边的伪造感也会出现。

对于图像操作应用程序,在使用不影响底层的原始图片数据的图像遮罩时使用非乘alpha值会很有用。同时,一个非乘alpha值意味着可以使用高精度范围的颜色通道,即便如此,在非乘RBGA值和线性空间颜色值之间进行转化时还是要多加注意。例如,没有浏览器能够恰当地做到这一点。支持alpha值得图像格式包括PNG(只支持非乘alpha值),OpenEXR(只支持预乘alpha值)、TIFF(两种都支持)。

一种与alpha通道相关得概念时色度键控。这是影视领域的术语,即演员在绿幕或蓝幕背景前演出,之后进行合成。基本理念是一个特定颜色的色相或精确的颜色值被设计用来代表透明。这允许图片可以只通过RGB值来给出外轮廓形状,不需要存储alpha值。该方法的一个缺点是物体在某一像素上要么是不透明要么是透明的,即alpha值只有1.0或0.0。

显示编码

当计算光照、纹理或其它操作时,值通常假设是线性的。这样的话,意味着加操作和乘操作可以按预期实现。然而,为了避免不同的视觉伪造感,我们必须将显示缓冲和纹理使用的非线性编码考虑在内。粗略地来说就是:着色器输出颜色范围在[0,1]内,我们将其通过使用1/2.2来提升,这一操作称为伽马矫正,对输入的纹理和颜色执行相反的操作。在大多数情况下我们可以让GPU执行这一操作。

我们先介绍阴极射线管(CRT)。在早些年显示数字图像时,常使用CRT。这些设备在输入电压和显示辐射率间有一个幂关系。当赋予一个像素的能量等级上升时,发出的辐射率并不是线性增长的,而是按照指数大于1的幂函数增长。例如,假设指数为2,当一个像素值为1.0时,一个设为50%的像素会发出1/4的光,0.5^2=0.25。尽管LCDS和其它显示技术有不同于CRT的固有色调曲线,它们有一些间接转换来模仿CRT的显示。

这个幂函数恰好与人的对光视觉感知相反。这种巧合的结果让编码粗略的称为视觉同意。即,在编码值为N和N+1之间的可察觉到的差距在显示范围上基本为常量。使用阈值对比度测量,我们可以在大范围的情况下检测到亮度的1%差距。这种接近最佳的值分布降低了条带伪造感,当颜色存储在精度限制的显示缓冲中。同样的效果也可以作用于纹理,通常使用同样的编码。

显示转移函数描述了在显示缓冲中的数字值和显示发出的辐射率级别之间的关系。因为这个原因,它也被称为电子光学转移函数(EOTF)。显示转移函数是硬件的一部分,对于不同的电脑显示器、电视和投影仪有不同的标准。对于整个过程的另一端,如图片和视频拍摄设备来说也有一个标准转移函数,被称为光学电子转移函数(OETF)。

当在显示时进行线性颜色值编码时,我们的目标是抵消显示转移函数的效果,这样的话无论我们怎么计算的值都会得到相对应的辐射率等级。例如,如果我们计算的值是两倍的,那么输出的辐射率也想是两倍的。为了保持这种连接,我们使用显示转移函数的逆来抵消它的非线性效果。这种无效化显示响应曲线的过程也被称为伽马矫正。当解码纹理值时,我们需要应用显示转移函数来生成在着色中使用的线性值。下图展示了这种解码和编码的过程:


个人电脑显示使用的标准转移函数由一个特殊的颜色空间定义,即sRGB。大多数的控制GPU的API可以设置为自动地应用合适地sRGB转换,当值从纹理中读取时或写入颜色缓冲时。之后的章节会讲到,mipmap生成也会将sRGB编码考虑在内。沿纹理值双线性插值会得到正确的结果,首先转换到线性值然后执行插值。alpha混合通过解码存储的值到线性值来得到正确结果,和新值混合,然后编码结果。

在渲染的最终阶段应用转换是非常重要的,当值是写入帧缓冲来显示时。如果后期处理是在显示编码后应用的话,这些效果就会使用非线性值计算,这通常是不正确的效果。显示编码可以认为是压缩的一种组成,是一种能够最好保持住值的感知效果的组成。一种理解方式是我们是使用线性值来进行物理计算,当我们想要显示结果或获得显示纹理图像时,我们就需要移动数据到显示编码形式,或者从显示编码形式移动回来,使用恰当的编码或解码转换。

如果我们需要手动应用sRGB,我们可以使用一个简单的转换公式。实际中,显示是通过每个颜色通道使用多比特值来表示的,例如消费级显示器是8,颜色范围是[0,255]。这里我们将显示编码的等级简化为[0.0,1.0],忽略比特数。线性值也可以位于[0.0,1.0]的范围,以浮点数的形式。我们将线性值标记为x,存储在帧缓冲中的非线性值的标记为y。为了将线性值转换到sRGB的非线性编码值,我们应用sRGB的显示转移函数的逆:

y=f^{-1}_{sRGB}(x)=\begin{cases}1.055x^{1/2.4}-0.055,where\quad x > 0.0031308,\\12.92x,where\quad x\leq0.0031308,\end{cases}

x代表RGB线性元组的一个通道。这个等式应用于每个通道,这些生成的三个值进行显示。在手动应用转换函数的时候要小心。一种错误来源是使用了编码颜色代替线性显示,另一种错误是解码或编码了两次颜色。

上面的两个等式中的下面一个是一个很简单的乘积,出于数字硬件需要来让转换可以完美的逆转。上方的表达式,包含对一个值进行幂计算,对所有[0.0,1.0]内的输入值x使用。将偏移和缩放考虑在内,这种函数可以近似表示为下面的简洁形式:

y=f^{-1}_{display}(x)=x^{1/\gamma}, \quad 5.31

其中\gamma=2.2

正因为计算得知必须为显示进行编码,图像必须在进行计算前转换为线性空间中的值。任何我们在显示器或电视上看到的颜色是一些显示编码获得的RGB元组,我们可以从屏幕捕捉或颜色采集器哪里获得颜色。这些值存储在一些文件格式中,例如PNG,JEPG和GIF,它们不需要进行额外的转换就可以送往帧缓冲中并显示在屏幕上。换句话说,我们在屏幕上看到的都是经过显示编码的数据。在着色计算中使用这些颜色值前,我们必须将这种编码形式转换回线性值。我们需要的从sRGB编码值转换到线性空间值得转换公式是:

x=f_{sRGB}(y)=\begin{cases}(\frac{y+0.055} {1.055})^2.4,\quad where\quad y > 0.04045\\\frac{y} {12.92},\quad where \quad y\leq 0.04045,\end{cases}

其中y代表一个标准化显示通道值,即存储在一张图片或帧缓冲中的值,被表示为[0.0,1.0]范围的值。这个解码函数是我们之前sRGB函数的逆。这意味着如果一张纹理被一个着色器访问,冰洁不经改变输出,它会和处理前保持一致。解码函数和显示转移函数一致,因为存储在纹理中的值已经编码过了。与其进行转换来得到线性反应显示,我们进行转换来得到线性值。

更简单的伽马显示转移函数是5.31等式的逆:

x=f_{display}(y)=y^{\gamma}

有时我们会看见一对更简单的转换,尤其是在手机和浏览器的app上:

y=f^{-1}_{simpl}(x)=\sqrt{x}

x=f_{simpl}(y)=y^2

如果我们不关注伽马,较小的线性值在屏幕上会显得很暗。一个相关的错误是如果不执行伽马矫正的话一些颜色的色相会偏移。假设\gamma=2.2,我们想要从显示像素发出一个正比于线性计算值的辐射率,这意味着我们必须将线性值进行1/2.2幂运算。一个0.1线性值会得到0.351,0.2线性值会得到0.481,0.5会得到0.730。如果没有编码的话,使用这些值的话会发出必须要更少的辐射率。注意0.0和1.0通常不会被改变。在伽马校正使用前,暗表面颜色会因为人为的构建而变得更深。

另一个忽视伽马矫正的问题是对于线性辐射率值物理正确的着色计算在非线性值上进行。一个例子见下图:


忽略伽马校正也会影响抗锯齿边的质量。例如,假设三角形边覆盖四个屏幕网格单元,见下图:


三角形的标准辐射率为1(白色),背景为0(黑色)。从左到右,单元被覆盖1/8,3/8,5/8,7/8。所以,如果我们使用一个盒装滤波器,我们想要将像素的标准线性辐射率表示为0.125,0.375,0.625,0.875。正确的方法是在线性值上执行抗锯齿,对四个结果值应用编码函数。如果没有执行的话,每个像素表示的辐射率会很暗,结果见上图右侧。这种伪造感为条痕感,因为边看起来有点像扭曲的线。下图显示了这种效果:

sRGB标准在1996年发明,现已在大多数的显示器上使用。然而,显示技术已经从那时起得到了发展。可以显示更亮和更广范围的颜色的显示器得到了发展。

相关书籍和资源

  • Pharr等人更深入地讨论了采样模式和抗锯齿。
  • Techner的课程笔记展示了不同采样模式生成方法。
  • Drobot总结了之前的实时领域的抗锯齿研究,解释了不同技术的属性和性能。
  • 大部分形态学抗锯齿方法可以在相关SIGGRAPH课程的笔记上得到。
  • Reshetov和Jimenez提供了一些游戏中使用的形态学和暂存抗锯齿技术的回顾。
  • 对于透明度的研究可以看一下McGuire的展示和Wyman的工作。
  • Blinn的文章,“What Is a Pixel?”,提供了图形学不同领域的相关定义。
  • Blinn的《Dirty Pixel and Notation, Notation, Notation》书籍包括了一些滤波和抗锯齿技术的介绍性文章,在alpha、合成和伽马矫正方面的文章也有。
  • Jimenez的展示给出了抗锯齿使用的一些最先进的技术。
  • Grritz和d'Eon对于伽马研究矫正有不错的。
  • Poynton的书给出了在不同的媒介上的伽马校正立体图,以及其它颜色相关的课题。
  • Selan的白皮书,解释了显示编码和它在电影制作中的使用,以及其它相关的信息。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343