RTR——Chapter5 下

news2024/9/19 16:36:24

锯齿和抗锯齿

  想象现在有一个巨大的黑色三角形,正在白色的背景上缓缓移动。由于屏幕上的网格单元被三角形所覆盖,因此其像素值的强度应该会平滑的下降。但是通常在各种基础渲染器中发生的情况是,一旦网格单元的中心被三角形所覆盖,这个单元的像素颜色就会立即从白色变为黑色。标准的GPU渲染器也不例外,如图5.14最左侧一列所示。

在这里插入图片描述

图5.14 第一行图像展示了三个具有不同抗锯齿级别的三角形、线和点。第二行图像是上面图像放大数倍之后的结果。最左边一列图像在每个像素中仅使用了一个样本,即没有使用抗锯齿技术;中间一列图像在每个像素上使用了4个样本(网格模式);最右边一列图像在每个像素上使用了8个样本( 4 × 4 4 \times 4 4×4棋盘格模式,采样了正方形区域中的一半)。

  三角形会以像素的形式显示出来,一个网格像素要么被覆盖,要么不被覆盖,绘制出来的线也有类似的问题。由于这个原因,因此三角形和线段的边界会呈现出锯齿状,这个视觉瑕疵被称作为“锯齿(the jaggies)”,当物体运动起来的时候就会变成“爬虫(the crawlies)”。更正式地说,这个问题被称作为“走样(aliasing)”,避免这个问题地技术被称作“反走样(antialiasing)”。(图像领域的走样和反走样,我们会统一翻译为锯齿和抗锯齿)

  采样理论(sampling theory)和数字滤波(digital filtering)的话题内容非常巨大,大到已经足够来写另一本书了。由于这是渲染中的一个关键领域,因此下面将会介绍有关采样和滤波的基本理论。然后会关注当前在实时渲染中能够做些什么来缓解锯齿现象。

采样和滤波理论

  渲染图像的过程本质上是一个采样任务。这是因为图像的生成实际上就是对三维场景进行采样的过程,从而获得图像(离散像素数组)中每个像素的颜色值。为了能够使用纹理映射技术,纹素(texel,texture pixel的缩写,和像素的概念类似,是纹理中的基本单元)也必须进行重新采样,从而在不同的条件下都获得良好的效果。为了在动画中生成一系列图像,通常会以一定的时间间隔来进行采样。本小节将会介绍有关采样(sample)、重建(reconstruction)和过滤(filter)的内容。为了简单起见,大部分资料都会以一维形式呈现,这些概念可以很自然的扩展到二维图像上,用于对二维图像进行处理。

  图5.15展示了一段连续信号如何以均匀间隔进行采样的过程,这个过程也被叫做离散化(discretized)。采样过程的目标是使用数字方式来表示信息,但是这样做会减少信息量,因此还需要对采样后的信号进行重建,这个重建的过程是通过对采样信号进行滤波实现的。

在这里插入图片描述

图5.15 对一个连续信号(左图)进行采样(中图),然后通过重建方法来将其恢复成原始信号(右图)。

  每当进行采样的时候,都可能会发生走样现象,这是不希望出现的瑕疵,因此需要努力对抗走样,从而生成令人愉悦的图像。在老西部片中有一个经典的走样案例,就是使用电影摄像机来拍摄旋转的马车轮子。由于车轮辐条的旋转速度要比相机记录图像的速度快得多,因此车轮可能会看起来旋转得很慢(向前旋转或者向后旋转),甚至可能会看起来根本没有发生旋转,如图5.16所示。之所以会出现这种现象,是因为车轮图像是在一系列时间步长内进行拍摄的,这种现象被称作时域走样(temporal aliasing)。

在这里插入图片描述

图5.16 第一行展示了一个旋转的车轮(原始信号)。第二行对信号进行了采样,但是采样并不充分,使得车轮看起来向着相反的方向旋转,这是由于采样率(sample rate)过低而导致走样的一个例子。第三行的采样率正好是每旋转一周采样两次,因此我们无法确定车轮的旋转方向,这就是Nyquist极限。在第四行中,采样率大于第三行的采样率,我们可以看到车轮向着正确的方向旋转。

  计算机图形学中常见的走样例子,是光栅化线段或者三角形边界上出现“锯齿(jaggies)”;以及当一个具有格子图案的纹理被缩小显示时,会出现被称为“萤火虫(firefly)”的高亮闪烁(摩尔纹)。

  当一个信号的采样率过低时,就会出现走样现象(在光栅化游戏中,屏幕的分辨率大体决定了采样率,因此屏幕分辨率越高,走样和锯齿现象就越少)。此时采样信号的频率会比原始信号低,如图5.17所示。为了对一个信号进行正确的采样(即可以从采样出来的样本中,重建原始信号),采样率必须要在采样信号最大频率的两倍以上。这通常被称作采样定理(sampling theorem),对应的采样率叫做Nyquist率或者Nyquist极限,它由瑞典科学家Harry Nyquist (1889–1976)在1928发现的。图5.16中同样展示了Nyquist极限的例子。事实上,该定理使用了术语“最大频率”,这意味着原始信号必须是有限频宽(band-limited)的,即没有任何频率会超过这个“最大频率”上限。换而言之,相对于相邻样本之间的间距,信号必须要足够平滑。

在这里插入图片描述

图5.17 图中的蓝色实线代表了原始信号,红点代表了均匀间隔的采样点,绿色虚线代表了重建后的信号。第一行展示了采样率过低时的情况,重建后的信号看起来频率较低,即原始信号出现了走样。第二行的采样率是原始信号频率的两倍,重建后的信号是一条水平线。这可以证明,如果采样率稍微增加一点点,可能就可以对原始信号进行完美重建。

  当使用点样本(像素点渲染)对三维场景进行采样的时候,正常情况下是不会有频宽限制的,这是因为三角形的边界,阴影的边界以及其他会产生不连续信号的现象,会导致三维场景中的频率是没有上限的。此外,无论采样样本排列的有多么紧密,场景中的物体都可以被继续缩小,使得它们根本无法被采样。因此,在使用点样本来渲染场景的时候,是无法完全避免走样现象的,而且通常都会使用点采样进行采样。但是有时候,也可以知道一个信号是有限频宽的,其中一个例子就是在将纹理应用到曲面上的时候。因为此时知道了像素的采样率,同时也可以计算出纹理采样的频率,如果这二者之比小于Nyquist极限的话,那么就不需要采取什么特殊手段,直接就可以进行正确的纹理采样;但是如果这个比例太高的话,就需要利用各种算法,来对纹理采样进行频宽限制。

重建

  给定一个有限频宽的采样信号后,现在我们将讨论如何从采样信号中重建原始信号。为了实现这个目的,我们必须使用一个滤波器,图5.18展示了三种常见的滤波器。这里需要注意的是,滤波函数的积分面积应当始终为1,否则重建后的信号就会被放大或者缩小。

在这里插入图片描述

图5.18 左上角为box滤波器,右上角是tent滤波器,下方是sinc滤波器(即 sin ⁡ x / x \frac{}{}\sin x/x sinx/x)。

  在图5.19中,使用了box滤波器(box意味盒子或者方框,这里就不对形状进行翻译了)来对一个采样信号进行重建。这是一种最糟糕的滤波器,因为它重建出来的信号是一段不连续的阶梯状。尽管如此,由于box滤波器非常简单,因此在计算机图形学中也被经常使用。如图5.19所示,将box滤波器放置在每个采样点上,然后对其进行缩放,使得滤波器的顶端和采样点重合(因为box滤波器最大值为1),所有这些缩放和平移过后的box函数之和,就是右图中重建出的信号。

在这里插入图片描述

图5.19 使用box滤波器对采样信号(左)进行重建。这是通过将box滤波器放置在每个采样点上得到的,并在y轴方向上对其进行缩放,使得滤波器的高度与采样点相一致。对这些变换后的box函数求和,即可获得重建后的信号(右)。

  box滤波器也可以使用其他任意的滤波器来进行替换,在图5.20中,使用了tent滤波器(tent意为帐篷,tent滤波器也叫做三角滤波器或者帐篷滤波器)来对采样信号进行重建。请注意,由于tent滤波器实现了相邻采样点之间的线性插值,因此它比box滤波器更好,因为重建之后的信号是连续的。

在这里插入图片描述

图5.20 使用tent滤波器来对采样信号(左)进行重建。右图展示了重建后的连续信号。

  但是,tent滤波器仍然有不足的地方,即重建后信号的平滑性较差,信号会在采样点的位置处发生突变。这与tent滤波器并不是一个完美的重建滤波器有关,为了获得较为完美的重建信号,必须使用一个理想的低通滤波器(low-pass filter)。将一个信号进行分解,最终可以表示为若干正弦波( sin ⁡ ( 2 π f ) \sin (2\pi f) sin(2πf))的组合,其中 f f f是该分量的频率。低通滤波器有一个特点,它会移除所有高于某个特定频率的分量,这个特定频率是由低通滤波器本身所决定的。从直观上来看,低通滤波器消除了信号的尖锐特征,即滤波器对信号进行了模糊处理。sinc滤波器是一个理想的低通滤波器(如图5.18底部所示),其数学表达式为:

sinc ⁡ ( x ) = sin ⁡ ( π x ) π x (5.22) \operatorname{sinc}(x)=\frac{\sin (\pi x)}{\pi x} \tag{5.22} sinc(x)=πxsin(πx)(5.22)

  傅里叶分析理论解释了为什么sinc滤波器是一个理想的低通滤波器。简单来说,原因如下:理想的低通滤波器是频域(frequency domain)中的box滤波器,当它与信号相乘的时候,会移除所有超出滤波器宽度的频率。将这个box滤波器从频域转换到空间域(spatial domain),便可以得到一个sinc滤波器。同时频域中的乘法运算也会被转换为卷积(convolution)运算,这是在本小节中一直在使用的概念,只是并没有实际描述卷积这个术语。

在这里插入图片描述

图5.21 这里使用了sinc滤波器来重建采样信号,sinc滤波器是一个理想的低通滤波器。

  使用sinc滤波器进行信号重建,可以获得更加平滑的结果,如图5.21所示。采样过程会在信号中引入高频分量(在函数图像中则体现为突变点),低通滤波器的任务就是移除这些高频分量。事实上,sinc滤波器消除了所有频率高于采样率 1 / 2 1/2 1/2的正弦波。当采样率为1的时候(即采样信号的最大频率必须小于 1 / 2 1/2 1/2),方程5.22所描述的sinc滤波器便是一个完美的重建滤波器。更加一般的描述是,假设采样率为 f s / 2 f_s /2 fs/2(即意味着相邻样本之间的间距为 1 / f s 1 / f_s 1/fs),对于这种情况,完美的重建滤波器就是sinc ( f s x ) (f_s x) (fsx),它会消除所有高于 f s / 2 f_s /2 fs/2的频率,这对于信号的重采样十分有用(下一小节的内容)。但是由于sinc滤波器的宽度是无限的,而且在某些区域为负,因此在实际中很少会进行使用。

  在低质量的box滤波器,tent滤波器和不实用的sinc滤波器之间,还有一个可用的中间区域,最广泛使用的滤波函数介于这两个极端之间,所有这些滤波函数都与sinc函数有一些类似,但是不同的是,这些滤波函数所能够影响的像素数量是有限的。与sinc函数类似,这些滤波函数在其部分区域上为负,但是对于应用程序而言,负值滤波器通常是不可取且不实用的,因此一般会使用非负波瓣的滤波器(它们通常称为高斯滤波器,因为这些滤波函数要么来自于高斯曲线,要么类似于高斯曲线)。

  在使用任意符合要求的滤波器之后,便可以得到一个连续的信号;然而,在计算机图形学中,并不能直接显示这样的连续信号,因为最终显示在屏幕上的都是离散的点。但是可以使用这些重建后的连续信号,即对连续信号进行重采样,将其离散化,即放大或者缩小信号。将在下一小节中讨论这个话题。

重采样

  重采样(resampling)用于放大或者缩小采样信号。假设原始的采样点位于整数坐标上(0,1,2……),即样本之间具有相同的单位间隔。此外,假设在重采样之后,希望新的样本点能够以 a a a为间隔均匀分布。当 a > 1 a > 1 a>1时,则缩小(minification)了采样信号(降采样,dowmsampling),即增大采样间隔,使用了更少的采样点;当 a < 1 a < 1 a<1时,则放大(magnification)了采样信号(上采样,upsampling),即减小采样间隔,使用了更多的采样点。

  信号放大(上采样)是这两种情况中较为简单的一种,因此先从它开始讨论。假设采样信号按照上一小节的方式进行重建,从直观上来看,现在的信号已经被完美重建,并且是平滑连续的。现在所要做的就是,以期望的间隔对重建后的信号进行重新采样,这个过程如图5.22所示。

在这里插入图片描述

图5.22 左图展示的是采样信号(红点)以及重建后的连续信号(绿色曲线)。右图中,重建后的信号会以两倍的采样率进行重采样,即进行了放大(上采样)。

  但是当发生信号缩小(下采样)的时候,这种技术就不起作用了,因为原始信号的采样率过高,直接降低采样率导致无法避免走样。相反,应当使用一个 s i n c ( x / a ) sinc(x/a) sinc(x/a)滤波器,来从被采样的信号中创建一个连续信号,然后再按照所需的间隔进行重采样,这个过程如图5.23所示。换而言之,通过在这里使用 s i n c ( x / a ) sinc(x/a) sinc(x/a)来作为滤波器,降低了低通滤波器的采样率,从而去除了更多的高频信号。如图5.23所示,滤波器的重采样率降低为原始采样率的一半。将这个方法与数字图像联系起来,类似于先对图像进行模糊(去除高频内容),然后再以一个较低的分辨率对图像重新采样。

在这里插入图片描述

图5.23 左图展示的是采样信号(红点)以及重建后的连续信号(绿色曲线)。右图中的采样率减半,sinc滤波器被水平缩放为原来的两倍。

基于屏幕的抗锯齿

  三角形的边缘如果没有被很好地采样和过滤,就会产生明显的视觉瑕疵。同理,阴影边界、镜面高光以及其他颜色剧烈变化的现象,也会导致类似的问题。本小节所讨论的算法有助于提高这些情况下的渲染质量,这些算法有一个共同点,它们都是基于屏幕的(screen based),即这些算法只会对渲染管线输出的样本进行操作处理。有一点需要注意,不存在最好的抗锯齿算法,每种算法在质量,捕捉尖锐细节(或者其他现象),处理运动物体,内存开销,GPU开销以及速度等方面,都具有不同的优势。

  在图5.14的黑色三角形例子中,存在的一个问题就是较低的采样率,即只在每个像素网格单元的中心,采样了一个单独的样本,因此只能了解到这个像素的中心是否被三角形所覆盖。可以在每个屏幕像素网格中使用更多的样本,并以某种方式将这些样本的结果混合起来,这样可以计算出更好的像素颜色。这个过程如图5.24所示。

在这里插入图片描述

图5.24 左图中渲染了一个红色的三角形,在像素的中心有一个样本。虽然这个像素的很大一部分都被三角形所覆盖,但是由于这个三角形并没有覆盖到像素中心的样本,因此像素最终的颜色值是还是白色的。右图中,每个像素内有四个采样点,其中有两个被三角形所覆盖,在对样本进行混合之后,最终输出了一个粉色的像素值。

  基于屏幕的抗锯齿算法,其通用策略是使用一个针对屏幕的采样模式(即多个采样点),然后对这些样本进行加权求和,最终生成像素的颜色 p \mathbf{p} p,其数学表达如下所示:

p ( x , y ) = ∑ i = 1 n w i c ( i , x , y ) (5.23) \mathbf{p}(x, y)=\sum_{i=1}^{n} w_{i} \mathbf{c}(i, x, y) \tag{5.23} p(x,y)=i=1nwic(i,x,y)(5.23)

  其中 n n n是一个像素内的样本数量; c ( i , x , y ) \mathbf{c}(i, x, y) c(i,x,y)是一个采样颜色; w i w_{i} wi是一个权重,代表了该样本对像素整体颜色的贡献值,范围是 [ 0 , 1 ] [0,1] [0,1]。样本点的位置可以通过该样本在样本序列 1 , … … , n 1,……,n 1,……,n中的序号获得,也可以使用整数标注的亚像素位置 ( x , y ) (x,y) (x,y)来进行表示。换句话说,对于每个像素网格单元而言,其采样点的位置都是不同的,采样的模式也可以因像素而异。实时渲染系统(以及其他大部分的渲染系统)中的样本通常都是点样本,因此函数 c \mathbf{c} c可以看作是两个函数的组合:首先是用一个函数 f ( i , n ) \mathbf{f}(i,n) f(i,n)用于检索屏幕上需要采样的位置 ( x f , y f ) (x_f,y_f) (xf,yf);然后再对屏幕上的这个位置进行采样,即精确检索这个样本点所对应的颜色值。为了计算在特定亚像素位置上的样本,需要事先选择采样方案,并对渲染管线进行配置,这通常是在逐帧(或者是逐程序)进行设置的。

  抗锯齿过程中的另一个变量是代表每个样本权重的 w i w_{i} wi,这些权重的和应当为1。实时渲染系统中所使用的大部分抗锯齿方法,都会给每个样本赋予相同的权重,即 w i = 1 n w_{i} = \frac{1}{n} wi=n1。图形硬件的默认模式是只对像素中心进行一次采样,这是上面抗锯齿方程中最简单的情况。在这种情况下,只有一个样本,该样本对应的权重为1,采样函数 f \mathbf{f} f总是会返回被采样像素的中心位置。

  对每个像素计算多个完整样本的抗锯齿方法被称为“超采样(supersampling)”方法。在概念上最简单的超采样方法是全屏抗锯齿(full-scene antialiasing,FSAA),通常也被称为超采样抗锯齿(supersampling antialiasing ,SSAA)。这个方法会以更高的屏幕分辨率来渲染整个场景,然后再通过对相邻像素样本进行滤波(卷积),从而生成一个图像。例如:假设现在需要一张分辨率为 1280 × 1024 1280 \times 1024 1280×1024的图像,使用该方法时,首先需要离屏渲染一张分辨率为 2560 × 2048 2560 \times 2048 2560×2048的图像,然后将屏幕上每 2 × 2 2 \times 2 2×2像素区域内的颜色值进行平均,然后再显示到屏幕上;对于最终生成的图像而言,每个像素都对应了四个采样点,并使用一个box滤波器进行过滤。请注意,这个过程对应了图5.25中的 2 × 2 2 \times 2 2×2网格采样。这个方法的开销很大,因为每个子样本都有一个z-buffer深度,它们都需要进行完整的着色和填充,FSAA最大的优点就是实现起来很简单。该方法还有一个低质量的版本,即只在一个屏幕轴向上,以两倍的采样率进行采样,也被称为 1 × 2 1 \times 2 1×2超采样或者 2 × 1 2 \times 1 2×1超采样,但是通常为了简单起见,都会使用2的幂次分辨率和box滤波器。NVIDIA的动态超分辨率(dynamic super resolution)功能是一种更加精细的超采样方法,该方法会以更高的分辨率来渲染场景,并使用包含13个样本的高斯滤波器来生成最终的显示图像。

在这里插入图片描述

图5.25 一些采样方案的对比,按照每个像素采样次数从少到多进行排列。其中Quincunx方法包含五个采样点,其中四个和其他像素共享拐角,中心采样点贡献了最终颜色的一半权重。 2 × 2 2 \times 2 2×2RGSS可以比 2 × 2 2 \times 2 2×2grid在接近水平的边缘处,捕获更多的灰度等级。同样的,虽然8 rooks方法使用的样本数量更少,但是它可以比 4 × 4 4 \times 4 4×4grid捕获更多的灰度等级。

  一种与超采样相关的采样方法利用了累积缓冲区(accumulation buffer)的思想,这种方法并不会使用一个巨大的离屏缓冲区,而是使用了一个与所需图像相同分辨率的缓冲区,不同之处在于,这个缓冲区的每个通道中包含了更多的颜色比特。为了对场景进行 2 × 2 2 \times 2 2×2的采样,该方法每帧会生成四张图像,每张图像都是让视图在 x , y x,y x,y方向上分别移动了半个像素距离生成的,这样四张图像便分别对应了像素网格中不同的采样点。这个方法需要每帧预渲染四张图像,并将最终处理好的图像再复制到屏幕上,这些巨大的额外成本使得该方法难以应用于实时渲染系统中。如果不考虑性能的话,这种方法可以用于生成高质量的图像,因为具体累计多少张图像是没有限制的,每个像素中都可以使用任意数量的样本,这些样本也可以位于亚像素的任意位置。在过去,累积缓冲区是一个单独的硬件模块,被OpenGL API直接支持,但是在OpenGL 3.0中被弃用了。在现代的GPU中,可以通过在输出缓冲区中使用更高精度的颜色格式,从而在像素着色器中实现这个累积缓冲区的概念。

  当物体边缘,镜面高光以及尖锐阴影等现象导致颜色发生突变的时候,就需要使用额外的样本来进行处理。通常会对这些情况进行一些额外处理来避免锯齿,例如:让阴影变得更加柔和,让高光变得更加光滑从而避免锯齿;一些特殊的物体类型(例如电线)可以通过增加尺寸,从而保证它们在沿着其长度的方向上的每个位置,都至少占据一个像素。物体边缘的锯齿仍然是一个主要的采样问题,可以采用一些分析方法,在渲染的过程中对物体的边缘进行检测并考虑它带来的影响,但是与直接增加采样点相比,这些检测方法的开销会更大,健壮性也更差。但是,GPU一些新特性打开了抗锯齿的新思路,例如保守光栅化(conservative rasterization)和光栅器有序视图(rasterizer order view)。

  诸如超采样和累积缓冲区等技术,它们生成的新样本与普通像素一样,都会独立完整地进行着色计算并维护深度信息。由于每个样本都需要通过像素着色器,因此这样做的总体收益相对较低,成本相对较高。

  多重采样抗锯齿(Multisampling antialiasing,MSAA)通过只进行一次逐像素的表面着色计算,并在多个样本之间共享结果,从而大大降低了计算成本。假设每个屏幕像素的每个片元(fragment)上有四个 ( x , y ) (x,y) (x,y)子样本位置,每个样本都具有独立的颜色信息和深度信息(z-depth),但是对于该像素的每个片元,像素着色器只会进行一次计算。如果所有的样本位置都被这个片元所覆盖,那么则在像素的中心位置来计算这个着色样本;而如果这个片元只覆盖了部分样本,那么着色样本的选择可以进行移动,从而更好地表示所覆盖的位置。这样做有一些好处,例如可以避免对纹理边缘的着色采样。这个位置调整被叫做质心采样(centroid sampling)或者质心插值(centroid interpolation),如果MSAA的功能被启用的话,那么GPU会自动对样本的位置进行调整。质心采样避免了非三角形的问题,但是可能会导致梯度计算返回不正确的结果。如图5.26所示。

在这里插入图片描述

图5.26 中间的图展示了一个像素被两个物体重叠的情况,其中红色物体覆盖了三个样本,蓝色物体覆盖了一个样本。图中的绿色点代表了像素着色器计算的位置,由于红色三角形覆盖了像素的中心,因此这个位置被用于着色器计算;而蓝色物体的像素着色器将在1号样本的位置上进行计算。对于MSAA而言,所有的四个样本位置都存储了单独的颜色信息和深度信息,右图展示了EQAA的2f4x模式,四个样本对应了四个ID值,这个ID值用于在另一个只包含颜色信息和深度信息的表格中进行检索。

  MSAA的速度要比纯超采样的方案快,因为每个片元只会进行一次着色计算,它专注于以更高的速率来对片元覆盖的像素范围进行采样,并共享计算出的着色结果。通过对采样和覆盖的进一步解耦,从而可以节省更多的内存,这反过来也可以使得对抗锯齿进行加速——对内存的访问越少,渲染的速度就越快。NVIDIA在2006年引入了覆盖采样抗锯齿(coverage sampling antialiasing ,CSAA),AMD随后也提出了增强质量抗锯齿(enhanced quality antialiasing ,EQAA)。这些技术以更高的采样率,同时只存储片元所覆盖的范围来进行实现,例如:EQAA的“2f4x”模式只存储了两组颜色和深度信息,这些信息会在四个样本位置之间进行共享。并且这些信息也不再和具体的样本位置相绑定,而是存储在一张额外的表格中,四个样本仅需要各自使用一个bit,来指定哪组颜色深度信息与该位置相关联即可,如图5.26所示。片元所覆盖的样本数量最终决定了该片元对像素颜色的贡献权重。如果此时存储的颜色数量超出了存储上限(两组),那么就会丢弃一个已存储的颜色信息,并将其所关联的样本标记为未知,这些被标记为未知的样本最终不会对像素颜色产生贡献。对于大多数场景而言,一个像素同时被三个不透明的片元所覆盖,并且这些片元还是以完全不同的方式进行着色的,这种情况发生的概率非常低,因此这个方案在实际应用中表现良好。虽然EQAA有着一定的性能优势,但是为了获得最高质量的画面效果,《极限竞速:地平线2(Forza Horizon 2)》还是采用了 4 × 4 \times 4×MSAA 。

  当场景中所有的几何体都被渲染到一个多样本缓冲区(multiple-sample buffer)之后,会进行一个解析(resolve)操作。在这个过程中,会对样本的颜色进行平均化,并最终决定像素的颜色。值得注意的是,当使用高动态范围颜色值(HDR)的时候,使用多重采样可能会导致一些问题,一般在这种情况下,为了避免出现视觉瑕疵,都会在解析之前对颜色值进行色调映射。色调映射的开销可能会很大,因此可以使用一个更简单的色调映射近似函数或者其他方法。

  在默认情况下,MSAA会使用box滤波器进行解析。ATI于2007年引入了自定义滤波器抗锯齿(custom filter antialiasing ,CFAA)技术,该项技术允许使用一个更宽或者更窄的tent(帐篷形)滤波器来进行解析,从而可以稍微扩展到周围的像素单元格上。EQAA后续也支持了这种滤波器,CFAA因此被取代了。在现代的GPU上,像素着色器或者计算着色器可以访问MSAA的样本,并使用任何所需要的滤波器来进行重建,甚至可以访问周围像素的样本。一个范围更大的滤波器可以减少锯齿和走样,但是它也会失去尖锐的细节。Pettineo发现,使用具有2到3个像素宽度的三次平滑滤波器和B样条滤波器,可以获得最好的整体效果。使用其他类型的滤波器还会引入性能问题,因为在一个自定义着色器中,即使使用默认的box滤波器来进行解析,也需要花费很长的时间,并且使用范围更大的滤波器核(卷积核),意味着需要访问更多的样本,这也会增加开销。

  NVIDIA内置的TXAA同样支持使用一个更大范围(超过一个像素范围)的重建滤波器,从而获得更好的抗锯齿效果。TXAA和比较新的MFAA(多帧采样抗锯齿multi-frame antialiasing)都使用了时域抗锯齿(temporal antialiasing ,TAA),TAA是一种通过之前几帧的结果来对当前帧进行优化的抗锯齿技术。从某种程度上来看,这种技术之所以成为可能,是因为它允许程序员设置每帧的MSAA采样模式[1406]。这种基于时域的抗锯齿技术,可以用于解决旋转车轮的走样问题,也可以提高边缘的渲染质量。

  想象一下可以通过“自定义”的采样模式来生成一系列图片,其中每一帧都使用了不同的像素内样本位置来进行采样。这种偏移(offset)是通过在投影矩阵上附加一个微小位移来实现的。生成和用于平均的图像数量越多,渲染结果就越好。这种使用多重偏移图像的思路也被用于时域抗锯齿的算法中,可以通过MSAA或者其他方法来生成一张图像,并和之前几帧(一般是2-4帧)的图像进行混合。一般越老的图像被赋予的权重就越少,如果当前的相机和场景没有发生移动的话,这种方法可能会导致闪烁的现象,因此通常只对当前帧和前一帧赋予相同的权重。由于每一帧中的样本都位于不同的像素子位置,因此这些样本的加权和,相较于比单帧加权而言,可以获得更好的边缘覆盖估计。因为上述原因,在一个渲染系统中,使用最新生成的两帧在一起进行平均,可以获得更好的结果,而且最吸引人的是,不需要对每一帧进行额外的处理。甚至可以使用时域采样来生成一个较低分辨率的图像,然后再将其放大到显示器的分辨率。除此之外,一些光照方法或者其他技术往往需要许多样本才能获得一个较好的结果,可以通过将当前结果和之前几帧进行混合,从而在一帧中降低样本的使用数量。

  虽然基于时域的抗锯齿技术可以在没有增加额外采样成本的情况下,为静态场景提供抗锯齿,但是这类算法也会存在一些问题。例如:如果每一帧的权重不同的话,那么会导致静态场景中的物体出现闪烁现象(shimmer);场景中快速移动的物体,或者是相机的快速移动,会导致画面出现鬼影(ghosting),即由于之前帧的贡献,会导致物体后面出现拖曳的痕迹。一种解决鬼影现象的方法是,只对缓慢移动的物体使用时域抗锯齿。另一个重要的方法是使用重投影(reprojection)来更好地将前一帧中的物体与当前帧中的物体关联起来。在这种方案中,物体会将生成的运动向量(motion vector)存储在“速度缓冲区(velocity buffer)”中。这些运动向量用于将前一帧和当前帧关联起来,即从当前的像素位置减去对应的向量,从而找到前一帧中该表面位置所对应的彩色像素。当然,在当前帧中不太可能成为表面一部分的样本将会被丢弃。由于这类基于时域的抗锯齿算法,并不会引入额外的样本,并且开销相对较小,因此近年来人们对这类算法产生了强烈的兴趣和广泛的使用。其中一部分的人关注时域抗锯齿的原因,是因为延迟渲染技术并不兼容MSAA和其他的多重采样抗锯齿技术。时域抗锯齿的实现方法也各不相同,并且根据应用程序的内容和目标的不同,已经有一系列用于避免瑕疵和改善质量的技术被开发出来了。例如:Wihlidal 展示了如何将EQAA、时域抗锯齿和各种其他采用棋盘格采样模式的滤波技术组合在一起,在保证渲染质量的同时,降低像素着色器调用的次数。 Iglesias-Guitian等人总结了之前的工作,并提出了他们自己的方案,使用像素的历史信息并对其进行预测从而最小化滤波瑕疵。Patney等人扩展了 Karis和Lottes在虚幻4中使用的TAA,将其用于虚拟现实(VR)应用,并增加了可变大小的采样以及对眼睛的运动补偿。

采样模式

  高效的采样模式(sampling pattern)是减少瑕疵和时间开销等方面的关键因素。Naiman指出,在水平边缘与垂直边缘的附近的锯齿,对人类的视觉影响最大,其次便是倾角接近45度的边缘。旋转网格超采样(rotated grid supersampling ,RGSS)使用了一个旋转后的正方形采样模式来进行采样,可以在像素内提供更多的水平分辨率和垂直分辨率,图5.25展示了这个采样模式的一个例子。

  RGSS是一个类似拉丁超立方体(Latin hypercube)和N-rooks采样的模式,其中 n n n个采样点被放置在一个 n × n n \times n n×n的网格内,每行和每列都各有一个样本点。在RGSS采样模式中,这四个采样点分别位于 4 × 4 4 \times 4 4×4子像素网格的单独行和单独列中。与常规的 2 × 2 2 \times 2 2×2采样模式相比,这种旋转采样的方式尤其适合捕获接近水平或者接近垂直的边缘;而在常规的采样模式中,这些边缘很可能会覆盖偶数个的样本,因此其提供的有效程度较低。

  N-rooks采样模式只是创建良好采样模式的基础,其本身还不够好,例如:当N-rooks的采样点集中在亚像素网格的对角线上时,如果此时覆盖像素的边缘几乎平行于对角线,那么N-rooks会给出一个很差的采样结果,如图5.27所示。

在这里插入图片描述

图5.27 N-rooks采样。左侧是一个合法的N-rooks采样模式,但是对于平行于对角线的三角形边界,它的捕获效果会很差,因为当这个三角形稍微移动一点, 那么所有的样本位置就会全部位于三角形内部或者全部位于三角形之外。右图也是一个合法的N-rooks采样模式,它可以更加有效地捕获这种边缘以及其他类型的边缘。

  为了更好的进行采样,避免将两个样本放在一起。同时将这些样本均匀分布在这个像素区域中。为了获得这样的采样模式,我们可以将例如拉丁超立方体(Latin hypercube)采样的分层抽样(stratified sampling)技术与其他方法相结合,例如抖动(jittering)采样,Halton序列以及泊松圆盘采样(Poisson disk sampling)。

  在实践中,GPU生产制造商通常会将这种采样模式进行硬件实现,从而进行多重采样抗锯齿。图5.28展示了一些在实践中使用的MSAA采样模式。对于时域抗锯齿而言,由于每帧之间的样本位置可以发生变化,因此具体的采样覆盖模式是程序员按照自己的设计想法来进行实现的,例如:Karis 发现一个基础的Halton序列,就可以比任何GPU提供的MSAA模式表现得更好。Halton序列在空间中生成的样本是随机的,并且样本之间的差异很小,也就是说,生成的这些样本,它们在空间中分布得很均匀,并且没有聚类(clustered)的现象。

在这里插入图片描述

图5.28 AMD和NVIDIA图形硬件加速器(显卡)所采用的MSAA采样模式,其中绿色的点代表了着色样本的位置,红色的点计算并保存了采样点的位置。从左到右分别是 2 × , 4 × , 6 × 2 \times,4 \times,6 \times 2×4×6×(AMD)和 8 × 8 \times 8×(NVIDIA)MSAA的采样模式。

  虽然使用亚像素网格模式,可以更好地近似每个三角形是如何覆盖像素网格的,但是它并不是最理想的方法。一个场景可以由屏幕上任意小的物体组成,这意味着没有任何一个采样率可以完美地捕获它们。如果这些微小的物体或者特征形成了某种图案,那么此时以恒定的间隔对其进行采样,就会产生摩尔纹(Moire fringes)和其他的干涉图案。在这种情况下,超采样所使用的网格模式尤其容易产生走样现象。

  一种解决方案是使用随机采样(stochastic sampling)(译者注:这里的stochastic 也是随机的意思,一般用于形容一个过程是随机的,而random一般用于形容一个变量是随机的),这种方法可以生成一个更加随机的采样模式,例如图5.28中的所使用的模式。想象现在远处有一把细齿梳子,屏幕上的每个像素都包含了好几个梳齿。若此时采用的采样模式是规则不变的,并且它正好和梳齿出现的频率相对应,那么就会产生严重的瑕疵。而如果我们拥有一个不那么有序的采样模式,就可以避免这种情况发生。使用这种随机化的采样模式,虽然可以避免瑕疵的出现,但是也会引入图像噪声的问题,但是庆幸的是,人类的眼睛对于噪声更加宽容。使用几种不同结构的采样模式可以缓解瑕疵,但是如果当采样模式在像素之间重复的话,还是没法避免瑕疵的出现。一种解决方案是在每个屏幕像素上,都使用完全不同的采样模式;或者随着时间的推移,对每个采样位置进行修改。在过去的几十年中,交错采样(interleaved sampling),索引采样(index sampling)偶尔会得到硬件的支持,其中每组像素都可以使用不同的采样方式。例如:ATI的SMOOTHVISION允许每个像素包含16个样本,并且支持多达16种用户自定义的采样模式,这些采样模式可以混合在一个重复出现的模式(例如 4 × 4 4 \times 4 4×4像素块)中。Molnar 以及Keller、Heidrich发现,当对每个像素使用相同的模式时,使用交错随机采样(interleaved stochastic sampling)可以最大程度上减少形成的锯齿瑕疵。

  其它一些由GPU支持的算法也值得关注。NVIDIA早期提出了一个五点型(Quincunx)方法,该方法是一种实时抗锯齿方案,它可以让一个样本同时影响多个像素。“Quincunx”指的是五个物体的排列方式,其中四个位于正方形内部,第五个位于正方形的中心,例如六面骰子中五个点的图案。五点型多重采样抗锯齿便使用了这种模式,它将四个外部样本放置在正方形像素的四个角落上,如图5.25所示。每个角落上的样本值,都会被分发给其相邻的四个像素。不同于对每个样本进行平均加权(其他大多数实时方案都采用了这种策略),该方案的中心样本权重为 1 2 \frac{1}{2} 21,每个角落样本的权重为 1 8 \frac{1}{8} 81。由于这种共享策略,平均每个像素只需要两个样本,而且其结果要比双样本的FSAA方法要好得多。这种五点型的采样策略,优点类似于二维的tent滤波器,如上一小节所描述的那样,它是优于box滤波器的。

  通过对每个像素仅使用一个样本,五点采样的策略也可以用于时域抗锯齿。每一帧都在前一帧的基础上,在每个轴上偏移半个像素,偏移的方向会在两帧之间交替进行(抖动)。前一帧用于提供角落样本,并使用双线性插值来快速计算每个像素的贡献值,并将结果与当前帧进行平均。每一帧的总权重都是相等的,这意味着在静态视角下也不会出现闪烁瑕疵。但是沿着轴向进行移动的物体仍然会出现一些问题,但是这个方案本身的代码实现十分简单,并且由于一帧中的每个像素仅使用了一个样本,因此可以提供更好的外观表现。

  在一帧中,五点型方法通过在像素边界共享样本信息,实际上每个像素只有两个样本的较低开销。RGSS模式可以捕捉到更多接近水平边界和垂直边界的层次信息。首次为移动端开发的FLIPQUAD(字面意思是翻转四边形)采样模式,结合了这两个方法的特点,其优势在于,每个像素仅进行了两次采样,同时可以获得类似于RGSS(每个像素四次采样)的质量。该采样模式如图5.29所示。Hasselgren等人探索了其他使用共享样本思路的低开销采样模式。

在这里插入图片描述

图5.29 左图是现实的RGSS采样模式,每个像素采样四次。通过将这些样本移动到像素边缘,便可以与周围像素共享样本。但是为了实现样本共享,周围相邻像素都需要将样本模式进行对称处理,如右图所示。这种采样模式叫做FLIPQUAD,每个像素只需要采样两次。

  与五点型采样类似,双样本的FLIPQUAD方法也可以用于时域抗锯齿,在两帧之间共享样本。Drobot 在其混合重建抗锯齿方法(hybrid reconstruction antialiasing,HRAA)中解决了使用哪种双样本采样模式才是最好的问题,他研究了用于时域抗锯齿的不同采样策略,他发现在测试的五种采样策略中,FLIPQUAD是最好的。棋盘格模式也可以用于时域抗锯齿,El Mansouri 讨论了如何使用双样本的MSAA来创建一个棋盘格渲染,从而降低着色器的开销,同时解决瑕疵问题。Jimenez 提供了一种混合式的解决方案,该方案使用了SMAA,时域抗锯齿和一系列其他技术;在该方案中,可以根据引擎的渲染负载,来实时改变抗锯齿的质量。Carpentier和Ishiyama 对边缘进行采样,并将采样网格旋转了45度,他们将这种时域抗锯齿策略和FXAA(稍后会进行讨论)相结合,来提高在高分辨率显示器上的渲染效率。

形态学方法

  锯齿通常会在边缘处产生,例如几何图形,尖锐阴影,或者高亮所形成的边缘处,可以利用锯齿产生的原因和结构,对其进行针对性的优化,从而获得更好的抗锯齿效果。Reshetov 于2009年提出了一种类似的算法,称之为形态学抗锯齿(morphological antialiasing ,MLAA)。其中“形态(morphological )”意味着它与物体的结构或者形状有关。早在1983年,Bloomenthal 就完成了这个领域的早期工作。Reshetov的论文重新激发了大家对多重采样方法的替代方法的研究,其核心思路是对边缘的搜索和重建。

  这种形式的抗锯齿是作为一个后处理(post-process)来执行的。也就是说,以通常的方式来渲染一张图像,然后将这个渲染结果输入到一个专门进行抗锯齿处理的过程中。自2009年以来已经发展了多种此类型的技术,这些技术依赖于一些额外的缓冲区(例如深度缓冲和法线缓冲)来生成更好的结果,例如亚像素重建抗锯齿(subpixel reconstruction antialiasing ,SRAA),但是它只适用于几何边缘的抗锯齿处理。一些分析方法,例如几何缓冲区抗锯齿(geometry buffer antialiasing,GBAA)和距离边缘抗锯齿(distance-to-edge antialiasing,DEAA)等技术,让渲染器在渲染的过程中计算一些有关三角形边缘的额外信息,例如边缘到像素中心的距离。

  最普通的方法只需要使用颜色缓冲,这意味还可以使用阴影,高亮,以及各种之前应用于后处理阶段的技术(例如描边渲染 ,silhouette edge rendering),来对边缘效果进行改善,例如:定向局部抗锯齿(directionally localized antialiasing ,DLAA)基于以下的观察进行实现:接近垂直的边缘应当被水平模糊,同样地,接近水平的边缘应当与其相邻像素进行垂直模糊。

  一些更加复杂的边缘检测方法,尝试找到包含任意角度边缘的像素,并确定其覆盖范围。对潜在边缘的相邻像素进行检查,从而尽可能重建原始边缘所在的位置。然后通过考虑边缘对像素的影响,来融合相邻像素的颜色,图5.30展示了这个流程的原理图。

在这里插入图片描述

图5.30 形态学抗锯齿。左边是一副带有锯齿的图像,我们的目标是找出潜在边缘的方向。在中间,该算法会对相邻像素进行检查,来标记一条可能存在的边缘,图中展示了两条可能的边缘位置。右图中,一个最佳猜测(best-guess)的边缘会根据像素的覆盖率,来将相邻像素的颜色混合到中心像素中,从而实现抗锯齿效果。

  Iourcha等人通过检查像素中MSAA的样本,来计算更好的结果,从而改进了边缘检测的效果。值得注意的是,基于边缘预测和混合的算法,其结果的精度要比基于样本的算法更高,例如:一种每个像素采样四次的算法,只能给物体边缘混合提供5个不同的级别:即没有样本覆盖,或者有1,2,3,4个样本覆盖。而边缘预测算法所估测出的边缘位置,可以具有更多的位置情况,因此可以提供更好的结果。

  在某几种情况下,基于图像的算法可能会导致错误的结果。第一,如果两个物体之间的颜色差异低于该算法的阈值,则可能无法有效检测到这个边缘。第二,如果某个像素被三个或者三个以上的表面所覆盖,那么就很难对这种情况进行边缘预测。第三,具有高对比度或者高频元素的表面,其颜色可能会在像素之间快速变化,从而导致算法遗漏一些边缘。第四,当使用了形态学抗锯齿算法时,画面上的文字质量通常会受到影响。第五,物体的拐角处对于这种算法来说是一个挑战,使用一些算法后,可能会导致这些拐角变成圆角。第六,因为一般假设边缘都是直线的,因此弯曲的线段可能会被错误处理。第七,单独一个像素上的变化,就可能会导致边缘重建的方式发生巨大变化,从而在帧与帧之间产生鬼影和瑕疵;可以通过使用MSAA覆盖蒙版来提高边缘检测的准确性,从而改善这一现象。

  形态学抗锯齿通常只会使用已经提供的信息,例如:某个物体的宽度可能不足一个像素(例如电线或者绳子),只要它没有覆盖到这个像素的中心,那么屏幕上就会出现一个像素的空白。在这种情况下,可以通过增加采样数来提高渲染质量,但是基于图像的抗锯齿算法并不能做到这一点。除此之外,这类算法的执行时间是不确定的,它取决于当前所观察到的内容,例如:一片草地视图,其所需的抗锯齿处理时间,可能是一片天空视野的三倍。

  综上所述,基于图像的方法能够以较小的内存使用和处理开销,来提高抗锯齿效果,因此这类算法被很多应用程序所使用。仅仅使用纯颜色缓冲的算法可以很好地与渲染管线相解耦,使得它们易于修改和禁用,甚至可以暴露为GPU驱动中的选项。其中两种最流行算法分别是快速近似抗锯齿(fast approximate antialiasing ,FXAA)和亚像素形态学抗锯齿(subpixel morphological antialiasing ,SMAA),其中的部分原因是因为,二者都为各种设备平台提供了可靠免费的源代码实现。两种算法都只使用了颜色缓冲,而SMAA的优势在于,它还可以访问MSAA的样本。二者都包含了一系列可选的设置,用于在渲染速度和渲染质量之间进行平衡,其每帧开销基本都在1到2毫秒内,这也是电子游戏愿意花费给抗锯齿处理的时间。最后,这两种算法也可以额外应用时域抗锯齿技术。Jimenez 提供了一种改进的SMAA实现,其速度比FXAA更快,并描述了一种时域抗锯齿方案。总而言之,推荐读者阅读Reshetov和Jimenez 所撰写的形态学技术综述,以及它们在电子游戏中的应用。

透明度,Alpha,合成

  光线通过半透明(semitransparent)物体的方式有很多种,对于半透明渲染算法而言,这些效果可以大致分为基于光线(light-based)的半透明效果与基于视图(view-based)的半透明效果。基于光线的半透明效果是指,半透明物体会导致光线发生衰减和偏移,从而使得场景中的其他物体会被照亮,或者呈现出不同的渲染效果。基于视图的半透明效果是指透明物体自身的渲染效果。

  本小节会讨论最简单形式的基于视图的半透明效果,即半透明物体本身会作为背后物体颜色的衰减器(attenuator)。在后面的章节中会更加详细的讨论基于视图和基于光线的半透明效果,例如毛玻璃(frosted glass)、光线折射、由于透明物体的厚度而导致的光线衰减、以及因视角变化而带来的反射率(reflectivity)变化和透射率(transmission)的变化。

  一种产生透明错觉的方法被称作点阵剔除半透明(screen-door transparency),该方法的思路是使用一个像素对齐的棋盘格来对图案进行填充,从而渲染透明效果的三角形。也就是说,每个三角形像素都会被选择性地渲染,从而使得后面得物体变得部分可见。通常来说,如果屏幕上被渲染出来的像素比较紧凑密集,那么棋盘格本身的图案是不可见的。这种方法的主要缺点是,在屏幕上的一个区域内,通常只有一个物体的半透明效果比较令人信服;如果有多个半透明物体重叠在一起的话,这种方法就非常失真了。例如:如果一个红色的透明物体与一个绿色的透明物体,会在一个蓝色物体前面发生重叠的话,那么红绿蓝中的三种颜色中,只有两种可以出现在棋盘格图案上。除此之外,棋盘格的比例一般只能设置到50%,虽然也可以通过使用其他更大的像素蒙版,来给出其他的百分比混合效果,但是这样会导致半透明效果失真。

  这种点阵剔除的方法,其优点就是十分简单,透明物体可以在任意时间,以任意的顺序出现,并且不需要特殊的硬件支持。通过使得所有物体在棋盘格所覆盖的像素位置上变得不透明,而在其他地方变得透明,从而实现了半透明的效果。同样的思路也可以用于对镂空纹理(cutout texture)的边缘进行抗锯齿处理,不同的是这是在亚像素级别上实现的,使用了一个叫做alpha to coverage(简称A2C)的功能。

  Enderton等人提出了随机透明度(stochastic transparency)的方法,它将亚像素级别的遮罩和随机采样的方法相结合,通过使用随机点状图案来表示一个片元的alpha覆盖率,可以生成一个合理,但是有噪声的图像,如图5.31所示。为了使得透明效果看起来较为合理,每个像素都需要进行大量的采样,并且每个亚像素样本都需要相当大的内存空间。它吸引人的地方在于,它并不需要进行混合操作,而且抗锯齿,透明度以及其他会创建部分覆盖像素的现象,都可以使用这个算法来实现。

在这里插入图片描述

图5.31 随机透明度。图中放大区域内展示了该算法产生的噪声。

  大部分透明度算法都需要将透明物体的颜色,与其背后物体的颜色混合在一起,为此,需要使用alpha混合(alpha blending)的概念。当一个物体渲染到屏幕上时,每个像素都关联了一个RGB颜色和一个z-buffer深度值,我们还会为每个被物体所覆盖的像素,定义一个额外的分量,这个分量被叫做alpha ( α ) (\alpha) (α)。alpha描述了一个给定像素上,片元的透明度和像素覆盖率:当alpha为1.0的时候,意味着这个物体是不透明的,并且完全占据了这个像素内的所有区域;当alpha为0.0的时候,意味着这个像素完全没有被掩盖(obscured),即这个片元是完全透明的。

  一个像素的alpha值可以用于表示不透明度,覆盖率或者同时表示这两者,具体需要依情况而定。例如:一个肥皂泡的边缘可能占据了四分之三个像素,即0.75;并且它可能是几乎透明的,例如可以让十分之九的光穿过这个肥皂泡,即透明度为0.1,那么其alpha值为 0.75 × 0.1 = 0.075 0.75 \times 0.1 = 0.075 0.75×0.1=0.075。但是,如果我们使用了MSAA或者类似的抗锯齿方案,那么样本本身就考虑到了对像素的覆盖率,即像素内四分之三的样本都会受到肥皂泡的影响。那么此时对于每个样本,我们将会使用0.1的不透明度来作为其alpha值。

混合顺序

  为了使得一个物体看起来更加透明,需要将alpha小于1的物体渲染在场景的最前面。被这个物体所覆盖的每个像素,都会从像素着色器中接收到一个 R G B α \rm RGB\alpha RGBα值(也叫做RGBA)。通过使用 o v e r \mathbf{over} over运算符,来将这个片元的值和像素的原始颜色进行混合,其数学形式如下所示:

c o = α s c s + ( 1 − α s ) c d [ o v e r o p e r a t o r ] (5.24) \mathbf{c}_{o}=\alpha_{s} \mathbf{c}_{s}+\left(1-\alpha_{s}\right) \mathbf{c}_{d} \quad [\mathbf{over} \quad \rm{operator}] \tag{5.24} co=αscs+(1αs)cd[overoperator](5.24)

  其中的 c s \mathbf{c}_{s} cs代表透明物体的颜色(被称作源颜色source), α s \mathbf{\alpha}_{s} αs代表了这个物体的alpha值, c d \mathbf{c}_{d} cd代表该像素在混合之前的颜色(被称作目标颜色destination), c o \mathbf{c}_{o} co代表了将这个透明物体放置在现有场景前( o v e r \mathbf{over} over操作)而生成的结果颜色。当渲染管线此时输入了物体的颜色 c s \mathbf{c}_{s} cs和alpha值 α s \mathbf{\alpha}_{s} αs时,像素的原始颜色 c d \mathbf{c}_{d} cd会被替换为生成的结果颜色 c o \mathbf{c}_{o} co;如果这个输入的 R G B α \rm RGB\alpha RGBα值实际上是一个不透明的颜色(即 α s = 1.0 \mathbf{\alpha}_{s} = 1.0 αs=1.0)的话,那么方程5.24会简化为直接使用这个输入物体的颜色来替换像素的原始颜色。

  举例:混合:现在有一个红色的半透明物体被渲染到一个蓝色的背景上,假设物体上某个像素的 R G B \rm RGB RGB值为 ( 0.9 , 0.2 , 0.1 ) (0.9,0.2,0.1) (0.9,0.2,0.1),背景的 R G B \rm RGB RGB值为 ( 0.1 , 0.1 , 0.9 ) (0.1,0.1,0.9) (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.6(0.9,0.2,0.1)+(1-0.6)(0.1,0.1,0.9) 0.6(0.9,0.2,0.1)+(10.6)(0.1,0.1,0.9)

  最后生成的结果颜色值为 ( 0.58 , 0.16 , 0.42 ) (0.58,0.16,0.42) (0.58,0.16,0.42)

  这个 o v e r \mathbf{over} over运算符可以让一个被渲染的物体具有半透明的外观,这种方式实现的透明效果是可行的,因为从某种意义上来说,只要能透过这个物体看到其背后物体的样子,就会认为这个东西是透明的。例如可以使用 o v e r \mathbf{over} over运算符来模拟现实世界中轻薄布料(gauzy fabric)的效果,由于布料上的丝线(thread)是不透明的,因此布料背后的物体会被部分模糊。在实际实现中,松散布料的alpha覆盖率会随着视角的变化而变化。也就是说,这里我们认为alpha值模拟了材质对像素的覆盖程度。

在这里插入图片描述

图5.32 左侧是一个红色的方形布料,右侧是一个红色的塑料滤镜,二者的透明效果是完全不同的。请注意观察二者阴影效果的不同。

  在模拟其他透明效果的时候,尤其是通过彩色玻璃或者彩色塑料来进行观察的情况下, o v e r \mathbf{over} over运算符模拟的效果就不那么令人信服了。在现实世界中,在一个蓝色物体前面放置一个红色滤镜,通常会使得这个蓝色物体变得更暗,因为这个物体所反射出来的光线(蓝色光,波长较短),大部分都无法通过这个红色滤镜(只允许通过红色光,波长较长),由于通过的光线变少了,因此会显得更暗,如图5.32所示。而使用 o v e r \mathbf{over} over运算符来进行模拟的时候,其结果是红色的一部分和蓝色的一部分被相加在一起。最好是能够将这两种颜色相乘,再加上透明物体自身的反射颜色。

  在基本的混合阶段运算符中, o v e r \mathbf{over} over是一个常用于透明效果的运算符。另一个有一定用途的操作符是叠加混合(additive blending),在这个运算符中,参与混合的两个像素值只是简单的相加,其数学表达式为:

c o = α s c s + c d (5.25) \mathbf{c}_{o}=\alpha_{s} \mathbf{c}_{s}+\mathbf{c}_{d} \tag{5.25} co=αscs+cd(5.25)

  这种混合模式适用于模拟各种发光效果,例如闪电或者火花,这些效果并不会对背后的像素产生影响,而只是使像素本身变得更亮。但是这种模式的生产的透明度效果看起来并不正确,因为不透明表面看起来并没有被过滤。对于一些层状的半透明表面,例如烟雾或者火焰,这种叠加混合的模式具有饱和现象颜色的效果。

  为了能够正确的渲染透明物体,需要在不透明物体绘制完成之后,再去绘制透明物体。具体的实现方式是先关闭混合操作,渲染所有的不透明物体;然后再启用 o v e r \mathbf{over} over运算符,渲染所有的透明物体。理论上来说可以一直启用 o v e r \mathbf{over} over,因为不透明物体的alpha值为1.0,它并不会导致destination颜色被替换成source颜色;但是这样做增加额外的开销,并且没有实际的收益。

  z-buffer存在的一个限制是,每个像素位置上仅会存储距离相机最近的那个深度值,如果有好几个透明物体都重叠在同一个像素上的话,那么就无法通过维护z-buffer来解决所有可见物体的效果了。因此在任意给定像素上叠加透明表面时,通常需要严格按照从后往前的顺序进行渲染,如果不这样进行处理的话,会导致错误的感知暗示。一种实现这种排序的方法是,将透明物体沿着观察方向,按照各自质心到相机的距离大小进行排序。这种粗略的排序可以很好地运行,但是在各种情况下都存在一些问题。首先,这样的排序仅仅是一个近似,位于远处的物体也可能会错误地出现在较近物体的前面;并且相互贯通重叠的物体,无法针对所有视角都进行正确显示,除非将每个网格都划分成单独的块,图5.33左侧图片展示了一个这样的情况。即便是一个凹形的单个网格,也可能会出现沿着观察方向的排序问题,即在屏幕上显示为与自身发生重叠。

在这里插入图片描述

图5.33 左图中的模型使用了z-buffer来进行透明渲染,以任何一个顺序来渲染网格,都会出现严重的错误。右图中,使用深度剥离可以提供一个正确的渲染结果,但是代价是需要使用一个额外的pass。

  尽管如此,由于这种方法十分简单,速度很快,并且不需要使用额外的内存空间或者特殊的GPU特性,因此这种对透明度物体进行粗略排序的方法仍然被广泛使用。如果使用了这种方法,那么最好是在渲染透明物体的时候,关闭深度缓冲的写入替换功能。也就是说,z-buffer仍然可以用于正常的深度测试,但是通过测试的表面并不会改变已有的z-buffer内容,即最近的不透明表面深度仍然会保持不变。通过使用这种方法,所有后来出现的半透明物体,至少都位于不透明表面前面(深度测试),这样就不会因为旋转相机而导致物体排序出现变化时,造成透明物体突然出现或者突然消失。也有一些其他的技术可以用于改善透明物体的外观,例如将一个透明物体连续渲染两次,先渲染一次背面,然后再渲染一次正面。

  可以对 o v e r \mathbf{over} over运算符的方程进行一些修改,使得从前向后的混合也可以得到相同的结果,这种混合模式被称作为 u n d e r \mathbf{under} under运算符。

c o = α d c d + ( 1 − α d ) α s c s a o [ u n d e r o p e r a t o r ] a o = α s ( 1 − α d ) + α d = α s − α s α d + α d (5.26) \mathbf{c}_{o}=\dfrac{\alpha_{d} \mathbf{c}_{d}+\left(1-\alpha_{d}\right) \alpha_{s} \mathbf{c}_{s}}{\mathbf{a}_{o}} \quad [\mathbf{under} \quad {\rm operator}]\\\mathbf{a}_{o}=\alpha_{s}\left(1-\alpha_{d}\right)+\alpha_{d}=\alpha_{s}-\alpha_{s} \alpha_{d}+\alpha_{d} \tag{5.26} co=aoαdcd+(1αd)αscs[underoperator]ao=αs(1αd)+αd=αsαsαd+αd(5.26)

  请注意, u n d e r \mathbf{under} under运算符要求目标颜色维护一个alpha值,而 o v e r \mathbf{over} over运算符则不需要。换而言之,目标物体(更靠近相机的透明表面)并不是一个不透明物体,因此需要有一个alpha值。 u n d e r \mathbf{under} under的数学公式与 o v e r \mathbf{over} over很像,只是将源( s s s)和目标( d d d)进行了交换。另外需要注意的是,alpha的计算公式是与顺序无关的,因为在交换了方程中的源alpha和目标alpha之后,仍然可以得到相同的alpha结果。

  计算alpha的方程来自于将片元的alpha值,看作为该片元对像素的覆盖率。Porter和Duff指出,由于并不知道每个片元覆盖像素区域的形状,因此假设每个片元都会按照其alpha的比例,来覆盖另一个片元。例如:如果 α s = 0.7 \alpha_{s} =0.7 αs=0.7,即意味着这个像素会以某种方式被划分成两个区域,其中有70%的部分被源片元所覆盖,另外30%则没有被覆盖;这里假设目标片元的覆盖率为 α d = 0.6 \alpha_{d} =0.6 αd=0.6,在没有其他条件的情况下,目标片元将会按照覆盖率与源片元进行重叠。图5.34展示了该公式的几何解释。

在这里插入图片描述

图5.34 图中展示了一个像素和两个片元 s , d s,d s,d。将这两个片元沿着不同的轴进行对齐,每个片元都会按照一定的比例覆盖另一个片元,也就是说,这两个片元是不相关的。两个片元所覆盖的区域大小与 u n d e r \mathbf{under} under运算符输出的alpha值相等 ( α s − α s α d + α d ) (\alpha_{s}-\alpha_{s} \alpha_{d}+\alpha_{d}) αsαsαd+αd。从公式中可以看出来,这意味着将这两个区域相加,然后再减去二者重叠的部分。

顺序无关的透明度算法

   u n d e r \mathbf{under} under运算符用于将所有透明物体都绘制到一个单独的颜色缓冲中,然后再使用 o v e r \mathbf{over} over运算符将这个颜色缓冲合并到场景的不透明视图上。 u n d e r \mathbf{under} under运算符的另一个用途是执行被称为深度剥离(depth peeling)的算法,该算法是一种顺序无关的透明度算法(order-independent transparency,OIT),这里的顺序无关意味着:应用程序不需要对透明物体进行排序。深度剥离的核心思路是使用两个z-buffer和多个pass。首先,第一个pass会将所有表面的z-depth信息记录在第一个z-buffer中,包括所有的透明表面;在第二个pass中,只会渲染所有的透明物体,如果一个透明物体的z-depth与第一个z-buffer中的深度值相匹配,那么可以知道,这个透明物体是距离相机最近的,并将其 R G B α \rm RGB\alpha RGBα值保存在一个单独的颜色缓冲区中;并且此时会进行“剥离”操作,具体方式是使用距离相机第二近的透明物体的z-depth(如果存在的话)来更新第一个z-buffer中的对应位置,此时最靠近相机的那个透明物体便被剥离了出去,代替它的是第二近的透明物体。接下来的一系列pass会继续按照这种方式来剥离透明物体,并在透明物体的颜色缓冲区上,使用 u n d e r \mathbf{under} under运算符来进行混合。会在一定数量的pass操作之后终止算法,然后将透明图像(透明物体的颜色缓冲区)混合到不透明图像上,如图5.35所示。

在这里插入图片描述

图5.35 每个深度剥离的pass都会绘制其中一层的透明物体。左图是第一个pass的渲染结果,它代表的是相机直接可见的透明图层(位于最前面)。中间展示了第二个pass的渲染结果,每个像素上都是距离相机第二近的透明表面,在这个例子中代表了透明物体的背面。右图展示了第三个pass的渲染结果,每个像素上都是距离相机第三近的透明表面。

  深度剥离算法有好几种变体,例如:Thibieroz 给出了一种可以从后向前进行渲染的算法,其优势在于可以将透明值进行立即混合,这意味着不需使用额外的alpha通道。深度剥离算法的一个问题在于并不知道到底要使用多少个pass才能捕获所有的透明层,一种基于硬件的解决方案是,通过使用一个像素绘制计数器来记录当前pass中写入了多少像素;如果在一个pass中没有任何的像素被写入,那么就说明完成了深度剥离。使用 u n d e r \mathbf{under} under运算符的优点在于,最重要的那一个透明层(眼睛最先看到的)是最先被渲染的。每个透明表面总是会增加所覆盖像素的alpha值,如果一个像素的alpha值接近1.0的话,意味着这个像素已经接近不透明了,而对于后续需要进行混合的颜色(位于更远处的透明表面的颜色),其影响可以忽略不计。可以通过设置一个固定的pass数量,或者当一个pass所渲染的像素数量低于某个设定的最小值时,将算法终止,从而减少从前到后的剥离次数。但是这种方式不利于从后向前的剥离过程,因为最重要的那一层位于最前面,它通常是最后被绘制的,如果过早终止算法的话,可能会丢失这些最重要的透明信息。

  尽管深度剥离算法可以有效渲染透明物体,但是这个算法的效率并不高,因为每次剥离的过程,都是对所有透明物体的一次独立渲染pass。 Bavoil和Myers 提出了一种双重深度剥离算法(dual depth peeling),在每个pass中,会剥离当前最近和最远的两层透明表面,从而使得pass数量减半。Liu 等人探索了一种桶排序方法(bucket sort method),可以在一个pass中最多捕获32层透明表面;该方法的一个缺陷在于,它需要一个相当大的内存空间,来维护所有透明层的排序顺序。如果再使用MSAA或者类似的抗锯齿技术的话,会极大增加内存开销。

  如何以一个可交互的速率来正确混合透明物体,这个问题的关键不在于缺少这样的有效算法,而是在于如何将这些算法在GPU上进行高效实现。Carpenter于1984年提出了A-buffer ,这是多重采样的另一种方式。在A-buffer中,每个被渲染的三角形(片元),都会为其所覆盖地每个屏幕单元格创建一个覆盖掩码(coverage mask),每个像素上都会存储与其相关的所有片元。不透明片元会剔除位于它们后面的片元,就像z-buffer一样;而所有透明表面的片元都会被存储下来。当所有的信息都被构建完成之后,会通过遍历片元和解析样本的方式,来生成最终的结果。

  在GPU上创建片元链表(linked list)的想法,是通过DirectX 11中暴露的新特性实现的,这些新特性包括章节3中提到的无序访问视图(UAV)和原子操作(atomic operation)。基于MSAA的抗锯齿可以通过访问覆盖掩码和计算每个样本的像素着色器结果来实现。该算法的原理是对每个透明表面进行光栅化,并将生成的片元插入到一个长数组中。然后连同颜色信息和深度信息一起,会生成一个单独的指针结构,该结构会将每个片元与该像素的前一个片元相链接。然后会执行一个单独的pass,它会渲染一个填充屏幕的四边形,以便在每个屏幕像素位置上调用像素着色器,这个着色器会根据已有的指针链接,找到每个像素上所有的透明片元;检索到的每个透明片元,都会与前面的片元按照深度进行排序,然后将排序后的链表,按照从后往前的顺序进行混合,从而生成最终的颜色。由于这里的混合操作是通过像素着色器执行的,因此如果需要的话,可以为每个像素都指定不同的混合模式。GPU和API的持续发展降低了使用原子操作符的开销,从而提升了性能表现。

  A-buffer的优点在于,只需要为每个像素分配所需的片元即可,不会浪费额外的存储空间,GPU上的链表实现也是如此。但是从某种意义上来说,这也可能是一个缺点,因为在渲染之前并不知道到底需要多少的存储空间(链表可能会很长);而且对于一个带有头发、烟雾或者其他具有大量重叠透明物体的场景而言,它所能产生的片元数量将会是十分巨大的。Andersson指出,对于一些复杂的游戏场景,可能会有多达50个透明网格(如树叶)和200个半透明粒子会发生重叠。

在这里插入图片描述

图5.36 左上角执行的是传统的、从后向前的alpha混合,由于错误的排序顺序,会导致渲染出错。右上角使用了A-buffer,给出了一个完美的,非交互式的结果。左下角使用了多层alpha混合的渲染结果。右下角展示了A-buffer和多层alpha混合之间的差异,为了便于观察,将结果颜色放大了四倍

  GPU通常会有预先分配好的存储资源,例如缓冲区和数组,链表方法也不例外。可以由用户来决定到底使用多少内存,如果内存耗尽的话,则会造成一些很明显的瑕疵。Salvi和Vaidyanathan 提出了一种解决这个问题的方法,即多层alpha混合(multi-layer alpha blending),它使用了一个由Intel引入的GPU特性,该特性叫做像素同步(pixel synchronization),如图5.36所示。这一特性可以用于实现可编程混合,并且开销要比原子操作小。他们的方法重新定义了存储和混合,以便能够在内存耗尽的时候,适当降低质量。此外,粗略的排序对这种方法也有一定的帮助。DirectX 11.3引入了光栅器有序视图,这是缓冲区的一种类型,它允许在支持该功能的任何 GPU 上实现这种透明方法。移动设备也有着被称为tile本地存储(tile local storage)的类似技术,允许在这些设备上使用多层alpha混合。然而这种机制也有一定的性能成本,因此这类算法的开销可能会很大。

  这种方法建立在Bavoil等人提出的k-buffer的思想上,其中前几个可见层会被尽可能地保存和排序,而更深的层则会被尽可能地丢弃和合并。Maule等人使用了一个k-buffer,并使用加权平均(weighted average)来描述这些较远的透明层。即该算法会保留前面若干个透明层,并对它们进行排序;而对于后续的透明层,会将多个层划分为一组,并使用加权平均的方式进行混合。基于加权求和和加权平均的透明技术,都是顺序无关的,并且都是单pass的,因此可以运行在几乎所有的GPU上。但是它们的问题都在于,没有考虑到物体的前后顺序,例如:使用alpha来表示覆盖率,一条淡红色围巾叠加在一条淡蓝色围巾的上面,会给人一种紫罗兰色的感觉;而正确地结果是一条带有一些蓝色的红色围巾。虽然对于几乎不透明的物体而言,这种方法产生的效果会很差,但是这类算法对于可视化很有用,而且对于高度透明的表面和粒子也很有效,如图5.37所示。

在这里插入图片描述

图5.37 随着不透明度的增加,物体之间的前后顺序会变得越来越重要。

  加权和(weighted sum,WS)透明度的方程为:

c o = ∑ i = 1 n ( α i c i ) + c d ( 1 − ∑ i = 1 n α i ) (5.27) \mathbf{c}_{o}=\sum_{i=1}^{n}\left(\alpha_{i} \mathbf{c}_{i}\right)+\mathbf{c}_{d}\left(1-\sum_{i=1}^{n} \alpha_{i}\right) \tag{5.27} co=i=1n(αici)+cd(1i=1nαi)(5.27)

  其中方程中的 n n n代表透明表面的数量, c i \mathbf{c}_i ci α i \alpha_i αi代表了第 i i i个透明表面的颜色值和alpha值, c d \mathbf{c}_d cd是场景中不透明部分的颜色。方程中包含两个求和的部分,当透明表面被渲染的时候,这两部分会被分别累加并存储;并且在透明pass的最后,每个像素都会对方程5.27进行计算。这种方法的问题有两个:1,第一个求和式的结果会饱和,即产生超过 ( 1.0 , 1.0 , 1.0 ) (1.0,1.0,1.0) (1.0,1.0,1.0)的颜色;2,由于第二个求和式的结果也可能会大于1,因此背景颜色可能会产生反作用。

  由于加权和方程的上述问题,因此通常都会使用加权平均方程:

c s u m = ∑ i = 1 n ( α i c i ) , α s u m = ∑ i = 1 n α i , c w a v g = c s u m α s u m , α a v g = α s u m n , u = ( 1 − α a v g ) n , c o = ( 1 − u ) c w a v g + u c d . (5.28) \begin{aligned} \mathbf{c}_{\mathrm{sum}} & =\sum_{i=1}^{n}\left(\alpha_{i} \mathbf{c}_{i}\right), \quad \alpha_{\mathrm{sum}}=\sum_{i=1}^{n} \alpha_{i}, \\ \mathbf{c}_{\mathrm{wavg}} & =\frac{\mathbf{c}_{\mathrm{sum}}}{\alpha_{\mathrm{sum}}}, \quad \alpha_{\mathrm{avg}}=\frac{\alpha_{\mathrm{sum}}}{n}, \\ u & =\left(1-\alpha_{\mathrm{avg}}\right)^{n}, \\ \mathbf{c}_{o} & =(1-u) \mathbf{c}_{\mathrm{wavg}}+u \mathbf{c}_{d} .\end{aligned} \tag{5.28} csumcwavguco=i=1n(αici),αsum=i=1nαi,=αsumcsum,αavg=nαsum,=(1αavg)n,=(1u)cwavg+ucd.(5.28)

  第一行代表了在透明渲染的过程中,生成的两个独立缓冲区的结果,每个透明表面对 c s u m \mathbf{c}_\mathrm{sum} csum的贡献,都会受到其alpha值的影响:越是不透明的表面(alpha越大)所贡献的颜色就越多,越是透明的表面(alpha越小)所贡献的颜色就越少。令 c s u m \mathbf{c}_\mathrm{sum} csum除以 α s u m \alpha_\mathrm{sum} αsum,便可以得到一个加权平均的透明颜色;而 α a v g \alpha_\mathrm{avg} αavg则是所有alpha值的平均值。 u u u代表了对于 n n n个透明表面而言,将平均alpha应用 n n n次之后,对目标(不透明场景)可见性的估计值(即大约有 u % u\% u%的部分不可见,即有 ( 1 − u ) % (1-u)\% (1u)%的部分可见,即alpha值)。最后一行实际上就是 o v e r \mathbf{over} over操作符,其中 ( 1 − u ) (1-u) (1u)代表了源的alpha值。

  加权平均有一个限制,即对于具有相同alpha值的透明表面而言,无论它们的顺序如何,加权平均都会均匀混合它们的颜色。McGuire和Bavoil 引入了加权混合的顺序无关透明度渲染算法,得到了更加可信的结果。在他们提出方程中,表面到相机的距离也会对权重产生影响,即越靠前的表面会产生越大的影响。此外,方程中的 u u u不再使用平均alpha值来进行估计,而是用1减去 ( 1 − α i ) (1-\alpha_i) (1αi)各项相乘的结果,从而给出了这一组透明表面真实的alpha覆盖率。这个方法可以产生在视觉上更加可信的结果,如图5.38所示。

在这里插入图片描述

图5.38 图中展示了在两个不同的相机位置上,观察同一个引擎模型的结果,它们都使用了加权混合的顺序无关透明度算法。按照距离进行加权,有助于弄清哪些表面更加靠近观察者

  这种按照距离进行加权的方法存在一个缺点,即在一个很大的场景中,彼此靠近的两个物体会拥有几乎相同的权重,这使得最终计算的结果与使用加权平均方法的结果相差不大。除此之外,随着相机到透明物体的距离发生变化,深度权重也可能会发生变化,但是这种变化是平缓的,并不会造成突变的效果。

  McGuire和Mara对这种方法进行了扩展,使其包含了合理的颜色透射效果。上文中曾经提到,本小节中所讨论的透明度算法,都只是对像素覆盖率进行模拟,从而实现对颜色的混合,而不是对颜色进行过滤。为了得到颜色过滤的效果,需要在像素着色器中获取不透明场景的颜色,将每个透明表面的颜色与它所覆盖的像素颜色相乘,然后将结果存储到第三个缓冲区中。在这个缓冲区中,不透明物体现在会被透明物体染色,并在接下来解析半透明缓冲区的时候,使用第三个缓冲区来代替不透明场景。与使用覆盖率来模拟透明度的方法不同,颜色透射是顺序无关的,因此这个方法也是有效的。

  还有一些其他的算法,它们用到了这里所介绍技术的部分元素和思路。例如:Wyman 按照内存需求、插入和合并的方法、是否使用了alpha覆盖率或者几何覆盖率,以及如何处理被丢失的片元,对之前的透明度算法进行了分类。通过寻找先前研究中的空白与不足,他展示了两种被发现的新方法。其中一种方法叫做随机分层alpha混合(stochastic layered alpha blending),该方法使用了k-buffer,加权平均以及随机透明度;另一种方法是Salvi和Vaidyanathan方法的变体,使用了覆盖掩码来代替了alpha。

Alpha 预乘与合成

   o v e r \mathbf{over} over操作符也可以用于将照片或者物体的渲染图混合在一起,这个过程被称为合成(compositing)。在这种情况下,每个像素的alpha值会与物体的 R G B \rm RGB RGB值存储在一起,alpha通道所构成的图像有时候也被称为无光粗糙层(matte,也叫做哑光),它展示了物体的轮廓形状。这个 R G B α \rm RGB \alpha RGBα图像可以用于与其他元素和背景进行混合。

  使用合成 R G B α \rm RGB \alpha RGBα数据的一种方式是alpha预乘(premultiplied alpha,也被称为关联的alpha),它的意思是在使用这些 R G B \rm RGB RGB值之前,要先将它们与对应的alpha值相乘。在alpha预乘之后,会使得over方程变得更加高效:

c o = c s ′ + ( 1 − α s ) c d (5.29) \mathbf{c}_{o}=\mathbf{c}_{s}^{\prime}+\left(1-\alpha_{s}\right) \mathbf{c}_{d} \tag{5.29} co=cs+(1αs)cd(5.29)

  其中 c s ′ \mathbf{c}_{s}^{\prime} cs代表了已经alpha预乘过的源通道,它代替了方程5.25中的 α s c s \alpha_{s} \mathbf{c}_{s} αscs项。由于在混合期间已经叠加过了源颜色,因此alpha预乘还可以在不改变混合状态的情况下,直接使用 o v e r \mathbf{over} over操作符和叠加混合。请注意,对于已经alpha预乘过的 R G B α \rm RGB \alpha RGBα值而言,尽管也可以使用它们来创建一个特别明亮的半透明值,但是其中的 R G B \rm RGB RGB分量通常并不会大于其alpha值。

  渲染合成图像很适合使用预乘alpha的方法,在黑色背景上渲染抗锯齿的不透明物体默认会提供预乘值。假设一个白色 ( 1 , 1 , 1 ) (1,1,1) (1,1,1)的三角形,在其边缘上覆盖了某个像素的 40 % 40\% 40%,这时通过一些抗锯齿方法(非常精确),该像素值会被设置成为0.4的灰度值,即会将这个像素值保存为 ( 0.4 , 0.4 , 0.4 ) (0.4,0.4,0.4) (0.4,0.4,0.4)。如果还要存储alpha值的话,那么其alpha值也为0.4,因为这是三角形所覆盖的区域面积。该像素最终的 R G B α \rm RGB \alpha RGBα值为 ( 0.4 , 0.4 , 0.4 , 0.4 ) (0.4,0.4,0.4,0.4) (0.4,0.4,0.4,0.4),这是一个预乘过的像素值。

  另一种存储图像的方法是使用未相乘的alpha(unmultiplied alpha),也被称为不关联的alpha,甚至是令人费解的术语——未预乘的alpha(nonpremultiplied alpha)。未相乘的alpha就是其字面意思:存储的 R G B \rm RGB RGB并不乘以其alpha值。在刚才的那个白色三角形的例子中,不相乘的颜色值为 ( 1 , 1 , 1 , 0.4 ) (1,1,1,0.4) (1,1,1,0.4)。这种表示的方式的好处在于,它存储了三角形的原始颜色,但是这种颜色在被显示之前,还需要再乘以它的alpha值。在执行过滤和混合操作的时候,最好是使用alpha预乘的颜色数据;因为如果使用未相乘的alpha的话,诸如线性插值的一些操作无法正确执行。物体边缘也会产生黑色条纹状的瑕疵。另外,alpha预乘也可以使得理论表达更加整洁。

  对于图像处理的应用程序而言,使用不关联的alpha可以在不影响图像原始数据的情况下,对照片进行遮罩处理。此外,不关联的alpha意味着可以使用颜色通道的全精度范围;也就是说,在将未相乘的 R G B α \rm RGB \alpha RGBα值转换到用于计算机图形计算的线性空间时,需要格外注意转换的正确性。例如:现在没有浏览器可以对其正确转换,也不可能做这样的转换,因为现在的预期结果就是不正确的。支持alpha通道的文件格式包括PNG(仅支持不关联的alpha),OpenEXR(仅支持关联的alpha)和TIFF(同时支持两种关联类型的alpha)。

  一个与alpha通道相关的概念是色键抠像(chroma key),这是视频制作中的一个术语,即演员在绿色背景或者蓝色背景前进行拍摄,然后再与其他背景进行混合。在电影工业中这个过程被叫做绿幕(green-screening )或者蓝幕(blue-screening)。这个技术的核心思路是将某个特定的色调(用于电影工业),或者某个精确的值(用于计算机图形学)看作是透明的,当检测到这个颜色的时候,就会显示背景图像。这项技术允许仅使用 R G B \rm RGB RGB颜色来标注物体轮廓,并不需要使用额外的alpha通道。这个方案有一个缺点,即图像中的物体要么是完全不透明的,要么就是完全透明的,即alpha只有1.0和0.0两个取值。例如:GIF格式允许将一种颜色指定为透明颜色。

显示编码

  在计算光照,纹理效果或者是其他操作的时候,我们会假设所使用的值是线性的(linear)。通俗来讲,线性意味着加法和乘法可以生成预期的效果。但是为了避免各种视觉瑕疵,显示缓冲区和纹理中使用了非线性的编码方式,这是必须要考虑到的。一个简单粗略的答案是:将着色器输出的颜色范围设置为 [ 0 , 1 ] [0,1] [0,1],并将输出的结果缩放为原来的 1 / 2.2 1/2.2 1/2.2次方倍,这个过程被称作为伽马矫正(gamma correction);而对于输入的颜色和纹理则要进行相反的处理,即要乘以2.2次方。在大多数情况下,可以让GPU来执行这些转换操作,本小节的目的是简要总结如何进行伽马矫正以及为什么要进行伽马矫正。

  从阴极射线管(cathode-ray tube,CRT)开始,在数字成像的早期阶段,通常会使用CRT显示器来进行成像。这些设备的显示radiance和输入电压之间具有指数关系。当应用在一个像素上的能量增加时(电压增加),该像素的radiance并不会线性增长,例如:假设电压与发光强度之间的指数比为2,此时将应用于该像素上的电压设置为原来的50%,那么实际的发光强度为 0. 5 2 = 0.25 0.5^2=0.25 0.52=0.25,即原来的四分之一。虽然液晶和其他显示技术有着不同于CRT显示器的亮度响应曲线,但是由于它们都是通过转换电路制造的,因此可以模拟CRT显示器的响应方式。

  这个指数函数几乎与人类眼睛对亮度的敏感程度相反,这个幸运的巧合也使得这种编码方式符合人类的视觉感知。也就是说,在可显示范围内,一对相邻编码值 N N N N + 1 N+1 N+1之间的感知差异大致是恒定的。使用和阈值对比度(threshold contrast)类似的方法进行测量,可以在很大范围的条件下,检测到大约1%的亮度差异(即将人眼对亮度的感知曲线以及CRT显示器的响应曲线进行对比)。当颜色存储在有限精度的显示缓冲区中时,这个近似最优的分布能够最大程度地减少条带瑕疵。对使用相同编码方式的纹理,也可以起到同样的优化效果。

  显示转换函数(display transfer function)描述了显示缓冲区中的值与实际显示器发光强度之间的关系,因此它也被称为电光转换函数(electrical optical transfer function ,EOTF)。显示转换函数是硬件的一部分,对于不同类型的显示器而言(例如计算机显示器,电视以及电影放映机),具有不同的标准。对于这一过程的另一端(图像和视频捕获设备)也有一个相应的标准转换函数,它被称为光电转换函数(optical electric transfer function ,OETF)。

  当对用于显示的线性颜色值进行编码时,目标是抵消显示转换函数的影响,这样计算出的任何颜色值才能发出相应水平的亮度,例如:如果计算出的结果翻倍了,那么希望显示器输出的亮度也能相应的翻倍。为了保证这种关系,需要应用显示转换函数的逆,来抵消其非线性效应,这个消除显示器响应曲线的过程也被称作为伽马矫正(gamma correction),关于进行伽马矫正的原因会在接下来进行说明。当对纹理进行编码的时候,需要应用显示转换函数来生成用于着色的线性颜色值。图5.39展示了在显示过程中编码和解码的使用。

在这里插入图片描述

图5.39 最左边,GPU着色器访问了一个PNG格式的彩色纹理,该纹理的非线性编码值(蓝色过程)被转换为了线性值。在进行着色和色调映射之后,最终计算出的颜色值进行了编码(绿色过程),并存储在帧缓冲中。帧缓冲中的颜色值与显示转换函数(红色过程),最终决定了显示器上的发光强度。绿色的编码过程与红色的显示转换函数相抵消,因此显示器实际的发光强度与线性的计算值成正比。

  PC显示器的标准转换函数由一个被称为 s R G B sRGB sRGB的颜色空间所定义。大部分控制GPU的API可以被设置为:当读取纹理和写入颜色缓冲时,自动完成适当的 s R G B \rm sRGB sRGB转换。例如,mipmap在生成的时候也会采用 s R G B \rm sRGB sRGB编码。在纹理上进行双线性插值的时候,为了保证插值结果的正确性,首先会将纹理中的颜色值转换为线性值,然后再进行插值计算。alpha混合的时候也是类似的,先将存储在缓存中的值解码为线性值,然后执行混合操作,再对混合结果进行编码和写入。

  在渲染的最后阶段,当颜色值被写入帧缓冲中并等待显示的时候,应用这个转换是十分重要的。如果在编码之后再进行一些其他的后处理操作,那么此时是在对非线性的颜色值进行处理,这通常是不正确的,会导致明显的瑕疵。显示编码可以被认为是一个压缩过程,它最大程度上保留了颜色值的感知效果。有一个很好的方法可以用于理解这个转换过程:使用线性值来执行实际的计算,每当要显示结果或者访问可显示的图像(例如彩色纹理)时,就需要使用适当的编解码转换,来将数据转换到合适的编码格式中。

  如果需要手动进行 s R G B \rm sRGB sRGB转换的话,可以使用标准的转换方程,或者一些简化过的版本。在实际操作中,每个颜色通道的比特数会对显示结果进行控制,例如:对于消费级的显示器而言,一般都是8bit的,它可以提供 [ 0 , 255 ] [0,255] [0,255]共计256个不同的颜色等级。这里我们为了方便讲解,将显示编码的级别表示为 [ 0.0 , 1.0 ] [0.0,1.0] [0.0,1.0]之间的一个浮点数,线性值也位于这个范围中。我们常用 x x x来表示线性值,用 y y y来表示帧缓冲中的非线性值。为了将线性值 x x x转换为 s R G B \rm sRGB sRGB编码的非线性值 y y y,需要对 x x x应用 s R G B \rm sRGB sRGB显示转换函数的逆,即:

y = f sRGB  − 1 ( x ) = { 1.055 x 1 / 2.4 − 0.055 ,  where  x > 0.0031308 12.92 x ,  where  x ≤ 0.0031308 (5.30) y=f_{\text {sRGB }}^{-1}(x)=\left\{\begin{array}{ll}1.055 x^{1 / 2.4}-0.055, & \text { where } x>0.0031308 \\ 12.92 x, & \text { where } x \leq 0.0031308\end{array}\right. \tag{5.30} y=fsRGB 1(x)={1.055x1/2.40.055,12.92x, where x>0.0031308 where x0.0031308(5.30)

  其中 x x x代表了 R G B \rm RGB RGB值中的一个通道,会对每个通道的线性颜色值都应用这个方程,然后由生成的非线性值来驱动显示器。如果手动应用这个转换函数的话,那么需要格外的小心,一般会有两个常犯的错误:1,对已经编码过的颜色值(而不是线性值)进行编码处理;2,对一个颜色进行编码两次或者解码两次。

  这个变换过程是一个两段函数,两个表达式实际上都是简单的乘法,因为硬件设备需要这个变换是完全可逆的。其中第一行表达式包含一个指数运算,它几乎适用于 [ 0.0 , 1.0 ] [0.0,1.0] [0.0,1.0]的所有范围。考虑到偏移量和尺度,这个函数可以被近似为下面的简单形式:

y = f display  − 1 ( x ) = x 1 / γ (5.31) y=f_{\text {display }}^{-1}(x)=x^{1 / \gamma} \tag{5.31} y=fdisplay 1(x)=x1/γ(5.31)

  其中 γ = 2.2 \gamma = 2.2 γ=2.2,希腊字母 γ \gamma γ是“伽马矫正”的名称来源。

  计算出的数值结果需要进行编码才能正确显示;类似地,使用相机拍摄的图片,在用于计算之前也必须将其转换为线性值。在显示器或者电视机上看到的任何颜色,都具有被显示编码过的 R G B \rm RGB RGB三元组,通过屏幕截图或者颜色选择器来获得具体的数值。这些值会以例如PNG,JPEG,GIF等文件格式来进行存储,这些文件格式可以直接发送到帧缓冲中并显示在屏幕上,而并不需要进行编码转换。换而言之,在屏幕上看到的任何图像,都是已经经过显示编码的数据;在使用这些颜色进行着色计算之前,都必须将其转换为线性值。将 s R G B \rm sRGB sRGB格式解码为线性值的方程如下:

x = f s R G B ( y ) = { ( y + 0.055 1.055 ) 2.4 ,  where  y > 0.04045 , y 12.92 ,  where  y ≤ 0.04045 , (5.32) x=f_{\mathrm{sRGB}}(y)=\left\{\begin{array}{ll} \left(\dfrac{y+0.055}{1.055}\right)^{2.4}, & \text { where } y>0.04045, \\[3mm] \dfrac{y}{12.92}, & \text { where } y \leq 0.04045, \end{array}\right. \tag{5.32} x=fsRGB(y)= (1.055y+0.055)2.4,12.92y, where y>0.04045, where y0.04045,(5.32)

  其中 y y y代表了归一化的显示通道值,即存储在图像或者帧缓冲中的值,其范围是 [ 0.0 , 1.0 ] [0.0,1.0] [0.0,1.0]。这个解码函数刚好与之前的 s R G B \rm sRGB sRGB编码方程相反,这意味着如果在一个着色器中读取了一张图片(解码过程),并且不对它进行任何处理直接输出(编码过程)的话,它将和处理之前的结果完全相同。解码函数其实和显示转换函数是完全一样的,因为存储在纹理中的值已经被编码过了,而且它能够正确地显示在显示器上;二者之间不同的是,解码函数将输入转换为一个线性值,而显示转换函数则是将其转换为一个线性响应显示。

  更加简单的伽马显示转换函数,是直接将方程5.31取逆:

x = f display  ( y ) = y γ . (5.33) x=f_{\text {display }}(y)=y^{\gamma}. \tag{5.33} x=fdisplay (y)=yγ.(5.33)

  有时会看到一组更加简洁的转换函数,特别是在移动和浏览器应用程序中:

y = f simpl  − 1 ( x ) = x x = f simpl ⁡ ( y ) = y 2 (5.34) y=f_{\text {simpl }}^{-1}(x)=\sqrt{x} \\[2mm] x=f_{\operatorname{simpl}}(y)=y^{2} \tag{5.34} y=fsimpl 1(x)=x x=fsimpl(y)=y2(5.34)

  也就是说,直接对线性值开平方就完成了编码过程,并直接用于显示;反过来,直接取编码值的平方根就完成了解码过程,二者互为反函数。这种转换仅仅是一个很粗略的近似,但是要比完全忽略伽马矫正的过程好上不少。

  如果不进行伽马矫正的话,那么数值较小的线性值在屏幕上会显得太暗,同时某些颜色的色调可能会发生改变。假设设置 γ = 2.2 \gamma = 2.2 γ=2.2,如果希望屏幕上像素的发光强度与计算出来的线性值成正比的话,这意味着要必须要将这个线性值提升到原来的 1 / 2.2 1/2.2 1/2.2次方倍。线性值0.1对应的发光强度为0.351,0.2对应0.481,0.5对应0.730。如果不进行编码的话,直接将这些线性值输入到显示器中,会导致显示器的发光强度低于所需要的值。需要注意的是,线性值为0.0和1.0在经过编码之后不会有任何改变。在引入伽马矫正之前,场景建模人员通常会手动拉高暗表面的颜色值,从而使得它们在显示变换之后,不至于显得太暗。

在这里插入图片描述

图5.40 上图展示了两盏聚光灯照亮同一个平面的情况。左图中的两盏灯的亮度分别为0.6和0.4,中间重叠的部分直接将两盏灯的亮度相加,即0.6+0.4=1.0,并没有使用伽马矫正;对非线性值进行加法运算,会导致结果出错。可以观察到,左边的灯光要比右边的更亮,但是重叠的部分却出奇的亮。右图中的亮度值在相加之前进行了伽马矫正,两盏灯相较于左图都要更亮,但是中间重叠的部分会显得比较自然。

  忽略伽马矫正的另一个问题是,对基于物理的线性radiance的正确着色计算,是在非线性值上进行的,图5.40展示了这种情况的一个例子。

在这里插入图片描述

图5.41 左图中,一个黑色背景(为了方便展示,图中显示的是灰色)上有一个白色三角形,其边缘覆盖了四个像素,像素内标注了真实的覆盖率。如果不进行伽马矫正的话,像素整体会偏暗,从而扭曲人眼对于边缘的感知,如右图所示。

  忽略伽马矫正也会影响抗锯齿边缘的质量,例如:假设一个三角形的边界覆盖了四个平面单元格(图5.41),其中三角形的归一化radiance为1(白色),背景的radiance为0(黑色)。从左到右,白色三角形分别占据了单元格的 1 8 , 3 8 , 5 8 , 7 8 \frac{1}{8}, \frac{3}{8}, \frac{5}{8},\frac{7}{8} 81,83,85,87,假设使用box滤波器来进行抗锯齿处理,我们希望处理后像素的线性radiance分别为0.125,0.375,0.625和0.875。正确的方法是对线性值进行抗锯齿处理,然后再对结果的像素值进行编码处理。如果不这么做的话,边缘像素的radiance会变得很暗,这将导致人眼对于边缘的感知发生变形,如图5.41右侧所示。这种瑕疵被称为扭绳(roping),因为边缘看起来有点像一个扭曲的绳子,图5.42展示了这种瑕疵的效果。

在这里插入图片描述

图5.42 左边,在抗锯齿之后进行了伽马矫正。中间,在抗锯齿之后进行了部分伽马矫正。右边,没有进行伽马矫正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaScript】算法之分治、动态规划

一个大问题分成多个小问题&#xff0c;递归解决小问题&#xff0c;将结果合并从而来解决原来的问题 分治 子问题都是独立的 动态规划 把分治优化了【重复的问题&#xff0c;单独保存起来】斐波那契数列 leetcode 习题 分治、动态规划习题

Varjo在芬兰开设新工厂,以满足国防部门在XR模拟训练中的需求

在军事国防领域&#xff0c;全新技术的投入使用最看重的就是保密与安全。作为全球领先的XR头戴式显示器提供商Varjo&#xff0c;近日正式宣布将在位于芬兰的赫尔辛基开设一家新的安全制造工厂。 此次工厂扩建将使Varjo能够满足国防训练和模拟领域对其高分辨率XR解决方案日益增…

Python if 语句优化技巧

大家好&#xff01;今天我们来聊聊Python中的if语句优化技巧。if语句是Python中最基本的控制结构之一&#xff0c;它用于根据条件执行不同的代码块。虽然if语句本身非常简单&#xff0c;但通过一些小技巧&#xff0c;可以让我们的代码更加高效、简洁。接下来&#xff0c;我们将…

怎么选择合适的员工电脑监控软件,优质的电脑屏幕监控软件

在当今信息化管理的时代&#xff0c;员工电脑监控软件已成为中小公司管理的重要工具之一。无论是提升工作效率、保障公司数据安全&#xff0c;还是确保员工在工作时间专注于任务&#xff0c;选择一款合适的监控软件都至关重要。今天&#xff0c;我们将重点介绍一款国内优秀的监…

Android TV RecyclerView列表获得焦点左右换行

在TV上&#xff0c;用RecyclerView显示一个列表&#xff0c;飞鼠遥控左右遥控获得Item焦点&#xff0c;到最后一个进行右键换行&#xff0c;是不能做到的&#xff0c;因此需要监听key事件处理换行。 效果图如下 代码实现 Item.xml布局 <?xml version"1.0" enc…

24最全网最全面的Comfyui工作流原理拆解分析教程!

前言 前言 前面几篇有讲到Comfyui的安装和入门基础的文生图&#xff0c;图生图加上CN和局部重绘的工作流教程&#xff0c;这工作流是基于sd webui的工作流原理跟大家简单讲了Comfyui工作流的基本原理。 今天我们通过拆解组合的方式再稍微深入拓展给大家讲一下Comfyui的工作流…

基于springboot+vue图书管理系统的设计与实现

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;图书信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

昂科烧录器支持Senasic琻捷电子的蓝牙低功耗芯片SNP746

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Senasic琻捷电子的蓝牙低功耗芯片SNP746已经被昂科的通用烧录平台AP8000所支持。 SNP746是一款蓝牙低功耗芯片&#xff0c;集成了压力传感器和加速度传感器的测量电路。它是为…

以电子书号出版的论著有哪些优缺点?

以电子书号出版的论著具有以下优点和缺点&#xff1a; 优点&#xff1a; 1. 费用较低&#xff1a; - 电子书号的管理费用相对较低&#xff0c;因为其不需要分配 CIP 数据&#xff08;图书在版编目数据&#xff09;&#xff0c;这一项就节省了不少成本。对于一些预算有限的作者…

不小心把U盘格式化了怎么恢复?教你轻松找回数据

U盘作为我们日常工作和生活中的重要数据存储工具&#xff0c;其便携性和大容量深受用户喜爱。然而&#xff0c;不小心将U盘格式化&#xff0c;导致重要数据丢失&#xff0c;是许多人都可能遇到的问题。 当这种突发情况发生时&#xff0c;我们应该如何迅速有效地恢复被格式化的…

最佳实践 · 如何高效索引MySQL JSON字段

概述 从MySQL 5.7.8版本开始&#xff0c;MySQL引入了对JSON字段的支持&#xff0c;这为处理半结构化数据提供了极大的灵活性。然而&#xff0c;MySQL原生并不支持直接对JSON对象中的字段进行索引。本文将介绍如何利用MySQL 5.7中的虚拟字段功能&#xff0c;对JSON字段中的数据…

数据结构-排序(冒泡,选择,插入,希尔,快排,归并,堆排)

文章目录 排序冒泡排序代码实现 选择排序动图演示代码实现 插入排序动图演示代码实现 希尔排序动图演示代码实现 快速排序动图演示代码实现(递归) 归并排序动图演示代码实现 堆排序动图演示代码实现 排序 概念&#xff1a;排序就是将一组杂乱无章的数据按照一定的规律&#xff…

web基础—dvwa靶场(九)Weak Session IDs

Weak Session IDs&#xff08;弱会话&#xff09; Weak Session IDs&#xff08;弱会话&#xff09;&#xff0c;用户访问服务器的时候&#xff0c;一般服务器都会分配一个身份证 session id 给用户&#xff0c;用于标识。用户拿到 session id 后就会保存到 cookies 上&#x…

数据可视化pyecharts——数据分析(柱状图、折线图、饼图)

安装 首先确保已经安装了pyecharts库&#xff0c;如果没有&#xff0c;可以通过pip install pyecharts进行安装。 柱状图 从pyecharts.charts导入Bar&#xff0c;从pyecharts导入options。准备数据&#xff08;如类别数据x_data和对应的数值数据y_data&#xff09;。创建Bar对…

C# 动态编译

一、简介 CSharpCodeProvider 是 .NET 提供的一个强大工具&#xff0c;它允许开发人员在应用程序运行时动态地生成和执行 C# 代码。这一特性为后端开发带来了前所未有的灵活性和动态性&#xff0c;特别是在处理那些需要高度定制化或难以在编译时确定逻辑的场景时&#xff0c;尤…

【项目实训1】手把手教你使用 Dehazeformer 模型去雾:服务器租用、环境配置、自定义数据集、模型的训练与测试(全网最全的操作指导)

前言 文章性质:实操笔记 📖 代码来源:GitHub - IDKiro/DehazeFormer: [IEEE TIP] Vision Transformers for Single Image Dehazing 主要内容:本文详细记录了如何借助 Tabby 图形界面工具在 AutoDL 远程服务器上配置 Dehazeformer 所需的项目环境,并且成功运行 Dehazeform…

HeterGCL-Graph Contrastive Learning Framework on Heterophilic Graph

推荐指数: #paper/⭐⭐ 发表于:IJCAI24 类型&#xff1a;个人觉得算是图结构学习&#xff0c;部分思想不错 问题背景&#xff1a; 传统的随机增强不适合异配图。随机增强主要保留的是同配信息。这就导致在异配图用随机增强会抑制高频信息&#xff0c;直接使用时不合理的(这个…

JDBC 编程

目录 JDBC 是什么 JDBC 的工作原理 JDBC 的使用 引入驱动 使用 常用接口和类 Connection Statement ResultSet 使用总结 JDBC 是什么 JDBC&#xff08;Java Database Connectivity&#xff09;&#xff1a;Java数据库连接&#xff0c;是一种用于执行 SQL 语句的Java…

【附激活码】2024最新PyCharm下载安装激活汉化教程!

一、PyCharm激活 激活码&#xff1a; KQ8KMJ77TY-eyJsaWNlbnNlSWQiOiJLUThLTUo3N1RZIiwibGljZW5zZWVOYW1lIjoiVW5pdmVyc2l0YXMgTmVnZXJpIE1hbGFuZyIsImxpY2Vuc2VlVHlwZSI6IkNMQVNTUk9PTSIsImFzc2lnbmVlTmFtZSI6IkpldOWFqOWutuahtiDorqTlh4blupflkI0iLCJhc3NpZ25lZUVtYWlsIjoi…

如何使用 Python Matplotlib 绘制 3D 曲面图

在数据可视化中&#xff0c;3D 图表是一个非常有用的工具&#xff0c;特别是当想要展示复杂的三维数据时&#xff0c;如期权的波动率曲面。Python 的 matplotlib 库提供了生成各种类型图表&#xff0c;包括 3D 图表。 本文将介绍如何使用 Python 中的 matplotlib 绘制 3D 曲面…