第五章到此翻译完毕!花了不少时间,终于结束了~ 希望能帮到大家更好地理解此书吧!

技术水平有限,有些地方的理解与翻译可能存在偏差,之后有时间还会重新校对考察一遍。

业余翻译,若有不周到之处,还请多多指教。

实时渲染(第四版)Real-Time Rendering (Fourth Edition)

第5章 着色基础 Chapter 5 Shading Basics

5.5 透明度,Alpha值,与合成 Transparency, Alpha, and Compositing

光通过半透明物体的方法有许多种。对于渲染算法而言,这些方法可以大致分为基于光的效果或基于视图的效果。基于光的效果是指对象使光衰减或转移,导致场景中的其他对象被照亮和呈现不同的效果。基于视图的效果是指半透明对象自身的渲染效果。

在本节中,我们将讨论基于视图的透明度的最简单形式,其中半透明对象充当其背后对象颜色的衰减器。在后面的章节中将讨论更详细的基于视图和光的效果,例如毛玻璃(frosted glass),光的弯曲(折射),由于透明物体的厚度导致的光衰减以及由于视角导致的反射率(reflflectivity)和透射率(transmission)变化。

一种制造透明感的方法称为屏幕门透明(screen-door transparency)[1244]。其思路是用像素对齐的棋盘格填充图案渲染半透明三角形。通常,屏幕上的像素足够紧凑以至于棋盘格图案本身是不易察觉的。这种方法的一个主要劣势是,通常只有一个半透明的对象可以令人信服地在屏幕的一个区域上渲染出来。举个例子,如果半透明的红色对象和半透明的绿色对象在蓝色对象之上渲染,则三种颜色中只有两种可以出现在棋盘格图案上。此外,50%的棋盘格效果是很有限的。其他更大的像素蒙版可用于给出其他百分比混合效果,但是这些倾向于创建那些可检测的图案 [1245]

(?)(上面这一段有些不好理解,不知翻译准不准确,请各路大神多多指教)

之前说过,这种技术的一个优势是它比较简单。半透明对象可在任意时间,以任何顺序呈现,且不需特殊的硬件支持。通过使所有对象在它覆盖的(棋盘格)像素处变为不透明,便解决了透明度的问题。同样的思路也被用于对剪切纹理的边缘进行反走样处理,但是这是在子像素级别,使用了被称为 Alpha 覆盖(alpha to coverage)的功能(第6.6节)。

由 Enderton 等人介绍 [423],随机透明(stochastic transparency)的方法使用子像素屏幕门遮罩与随机采样相结合而成。通过使用随机点画图案表示片元的 Alpha 覆盖,可以创建一个合理但含噪声的图像。见图 5.31。为了看起来更合理,每个像素都需要大量的样本,当然,也需要为所有这些子像素样本准备相当大的内存空间。但此方法很有吸引力的是不需要进行混合操作,并且反走样,透明度,以及任何其他的创建部分覆盖像素的现象都可用此单一机制来处理。

大多数透明度算法会将透明对象的颜色与其后面对象的颜色混合在一起。为此,我们需要 Alpha 混合的概念 [199,387,1429]。当在屏幕上渲染对象时,RGB 颜色和 z 缓冲区深度这两者与每个像素都是相关联的。我们还可以为对象覆盖的每个像素定义另一个组件,称为 Alpha(α)。Alpha 是一个值,它用于描述给定像素的对象片元的不透明度和覆盖度。Alpha 为1.0 表示对象是不透明的,并且完全覆盖了像素的关注区域;0.0 表示完全不隐藏像素,即片元是完全透明的。

像素的 Alpha 值可以表示不透明度或覆盖率,或同时是两者,这具体视情况而定。举个例子,肥皂泡的边缘可能会覆盖像素的四分之三,即 0.75,并且可能几乎是透明的,从而使十分之九的光线直达眼睛,所以它的十分之一是不透明的,即 0.1。那么其 Alpha 将为 0.75 × 0.1 = 0.075。但是,如果我们使用 MSAA 或类似的反走样方案,覆盖率将通过样本自身从而被考虑在内。因此四分之三的样本将受到肥皂泡的影响。然后,在每个样本中,我们将使用 0.1 的不透明度值作为 Alpha 值。

 图5.31. 随机透明。产生的噪声显示在放大区域中。(图片来自 NVIDIA SDK11 [1301] 样本,由 NVIDIA 公司提供。)

5.5.1 混合顺序 Blending Order

为了使对象看起来透明,它以小于1.0 的 Alpha 渲染到现有场景的顶部。对象覆盖的每个像素将从像素着色器接收结果 (也称为 RGBA)。通常使用 over 运算符将此片段的值与原始像素颜色混合,如下所示:

其中  是半透明对象的颜色(称作来源, source), 是对象的 Alpha 值, 是混合前的像素颜色(称作目标,destination), 是将半透明对象放置在现有场景上而产生的最终颜色。在渲染管线传入  和  的情况下,像素的原始颜色  被结果  所取代。如果传入的  实际上是不透明的( = 1.0),则该公式简化为用对象的颜色完全替换像素的颜色。

示例:混合。红色的半透明对象被渲染到蓝色背景上。假设某个像素的对象的 RGB 着色为(0.9,0.2,0.1),背景为(0.1,0.1,0.9),并且对象的不透明度设置为 0.6。然后将这两种颜色混合

它的颜色为(0.58,0.16,0.42)。

over 运算符为要渲染的对象提供半透明外观。通过这种方式实现的透明性可以正常工作,即只要可以通过它看到后面的对象,我们就会将它视为透明的物体 [754]。使用 over 模拟薄纱织物的真实效果。织物背后对象的视图被部分遮挡了——织物的线是不透明的。在实践中,宽松的织物具有随角度变化的 Alpha 覆盖率 [386]。这里的重点是 Alpha 模拟了材质覆盖像素的程度。

图 5.32. 红色薄纱正方形的织物与红色的塑料过滤器,它们具有不同的透明效果。注意,它们的阴影也不同。(照片由 Morgan McGuire 提供。)

over 运算符对于其他类型的半透明效果显得不是很可信,尤其是透过有色玻璃或塑料观看时。在现实世界中,放置在蓝色物体前面的红色滤镜通常会使蓝色物体看起来很暗,因为该物体反射的可以穿过红色滤镜光线很少。参见图 5.32。当使用 over 进行混合时,结果是红色和蓝色部分相加在一起。更好的方法应该是将这两种颜色相乘,并增加透明对象本身的反射。第 14.5.1 节和第 14.5.2 节中讨论了这种类型的物理透射率。

在基本的混合阶段运算符中,over 是通常用于透明效果的运算符 [199,1429]。另一种有用的操作是加法混合(additive blending),即将像素值简单地求和。如下所示,

这种混合模式能够很好地用于发光效果,例如闪电或火花,这些效果不会使后面的像素衰减,而只会使它们变得更亮 [1813]。然而,此模式的透明度看起来不正确,因为不透明的表面似乎没有被过滤 [1192]。对于诸如烟或火焰之类的多层分层半透明表面,加法混合具有使半透明现象的颜色更饱和的效果 [1273]

为了正确渲染半透明对象,我们需要在不透明对象之后绘制它们。首先,通过关闭混合以渲染所有不透明对象,然后开启 over 以渲染半透明对象。从理论上讲,我们总是可以让 over 开启,因为不透明的 Alpha 1.0 会给出源颜色并隐藏目标颜色,但是这样做成本更高,而且没有真正的收益。

z 缓冲区的限制是每个像素只能存储一个对象。如果多个透明对象与同一像素进行重叠,则仅 z 缓冲区无法容纳且无法在之后解决所有可见对象的影响。当使用 over 时,任何给定像素处的透明表面通常都需要以从后到前的顺序进行渲染。不这样做的话可能会给出错误的知觉暗示。一种实现这种排序的方法是,按照单个对象的质心沿相机视角的距离对其进行排序。这种粗略的分类可以很好地工作,但是在各种情况下都有许多问题。首先,这里的顺序只是一个近似值,因此分类时较远的对象可能位于较近的对象的前面。互相贯穿的对象无法针对所有视角在每个网格上都进行正确显示,除非将每个网格分解为单独的碎片。相关案例,请参见图 5.33 的左图。甚至具有凹面的单个网格也会在屏幕上重叠的视角上出现排序问题。

图5.33. 在左侧,使用 z 缓冲区以透明方式渲染模型。以任意顺序渲染网格会产生严重的错误。在右侧,深度剥离可提供正确的外观,但要消耗额外的 pass 数。(图片由 NVIDIA Corporation 提供。)

尽管如此,由于其简单性与速度,以及它不需要额外的内存或特殊 GPU 支持,我们仍然经常使用这种对透明度进行粗糙排序的方法。如果应用了这种方法,通常最好在执行透明度时关闭 z 深度替换功能。也就是说,z 缓冲区仍然可以正常测试,但是保留下来的的曲面不会改变存储的 z 深度;最接近的不透明表面的深度保持不变。用这种方法,所有半透明物体都至少会以某种形式出现,而不是在照相机旋转导致的更改排序顺序时造成物体突然出现或消失。其他技术也可以帮助改善外观表现,例如每次绘制两次透明网格,首先渲染背面,然后渲染正面 [1192,1255]

over 方程也可进行修改,以使从前到后混合能得到相同的结果。这种混合模式称为 under 运算符:

请注意,under 要求目标保持 Alpha 值,而 over 则不需要。换句话说,目标——在它之面混合了更近的透明表面——并不是不透明的,因此需要具有 Alpha 值。under 的公式和 over 相似,但是交换了源和目标。另外需要注意的是,用于计算 Alpha 值的公式与顺序无关,因为源 Alpha 值和目标 Alpha 值可以交换,结果都是相同的最终 Alpha 值。

Alpha 公式来自将片元的 Alpha 作为覆盖率。Porter 和 Duff [1429]注意到,由于我们不知道每个片元覆盖区域的形状,因此我们假设每个片元都按其 Alpha 值比例去覆盖另一个片元。例如,如果  ,则以某种方式将像素分为两个区域,其中源片元覆盖 0.7,而目标片元覆盖 0.3。除非有其他新的技术,否则目标片元覆盖范围,我们称  ,会按比例地与源片元进行重叠。该公式具有几何解释,如图 5.34 所示。

图 5.34. 一个像素和两个片元 s 和 d 。通过沿着不同的轴对齐两个片元,每个片元会按一定比例覆盖另一个片元,也就是说,它们是不相关的。两个片元覆盖的面积等于 under 输出的 Alpha 值。这意味着将两个面积相加,然后减去它们重叠的面积。

图 5.35. 每个深度剥离 Pass 都绘制其中一个透明层。左侧是第一遍绘制,显示了直接可见的图层。中间显示的第二层在每个像素处显示了距离第二近的半透明表面,在这种情况下为对象的背面。右边的第三层是一组距离第三近的透明表面。最终结果可以在第 624 页的图 14.33 中找到。(图片由Louis Bavoil提供。)

5.5.2 与顺序无关的透明度 Order-Independent Transparency

我们通过将所有半透明对象绘制到单独的颜色缓冲区,然后使用 over 将该颜色缓冲区合并到场景的不透明视图之上,从而来使用 under 方程。under 运算符的另一种用途是执行称为深度剥离(depth peeling) [449,1115] 的与顺序无关的透明度(order-independent transparency, OIT)算法。顺序无关意味着应用程序不需要执行排序。深度剥离的思路是使用两个 z 缓冲区和多 Pass。首先,渲染一个 Pass,以使所有表面的 z 深度(包括透明表面)都位于第一个 z 缓冲区中。在第二个 Pass 中,将会渲染所有半透明对象。如果对象的 z 深度与第一个 z 缓冲区中的值匹配,则我们就知道这是最近的半透明对象,然后将它的 RGBα 值保存到单独的颜色缓冲区中。我们还通过保存所有半透明对象(如果有)的超出第一个 z 深度并且最接近的 z 深度来“剥离”该层。此 z 深度是第二近的透明对象的距离。接下来的一系列 Pass 继续使用 under 进行剥离并添加透明层。经过一定数量的 Pass 渲染之后,我们会停下来,然后将半透明图像混合在不透明图像之上。见图 5.35。

该方案的几种变体已经研发出来了。例如,Thibieroz [1763] 提供了一种从后到前计算的算法,其优点是能够立即混合透明值,这意味着我们不需要单独的 Alpha 通道。深度剥离的一个问题是需要知道究竟多少 Pass 足以捕获所有透明层。一种硬件上的解决方案是提供一个像素绘制计数器,该计数器可指示渲染过程中写入了多少个像素。当 Pass 未渲染任何像素时,渲染就直接完成了。使用 under 的好处是,最重要的半透明层——眼睛首先看到的那些——会在早期就进行渲染。每个半透明表面都会增加其覆盖的像素的 Alpha 值。如果像素的 Alpha 值接近 1.0,则混合的贡献值会使像素几乎是不透明的,因此距离较远的对象的影响可忽略不计 [394]。当通过 Pass 渲染的像素数低于某个最小值或可以指定固定数量的 Pass 时,可以减少从前到后的剥离过程。然而这对于从后到前的剥离效果不佳,因为距离最近(并且通常是最重要)的层是最后绘制的,因此可能会因早期过程的终止而丢失片元。

尽管深度剥离是有效的,但它的速度有可能很慢,因为每一层的剥离都是针对所有半透明对象的一个独立的渲染 Pass。Bavoil和Myers [118]提出了双重深度剥离技术,其中在每个 Pass 中剥离了两个深度剥离层,分别是最接近的和最远的剩余层,从而将渲染 Pass 的数量减少了一半。Liu 等人 [1056] 探索了一种桶排序方法(bucket sort method),该方法一次可捕获多达 32 个层。这种方法的一个缺点是,它需要大量内存才能为所有层保持排序顺序。通过 MSAA 或类似方法进行反走样将极大地增加成本。

以交互速率正确地将半透明对象混合在一起的问题并不是我们缺少算法的问题,而是将这些算法有效地映射到 GPU 的问题之一。1984年,Carpenter 提出了 A 缓冲区 [230],这是另一种多重采样的形式。在A缓冲区中,渲染的每个三角形都会为其完全或部分覆盖的每个屏幕网格创建一个覆盖蒙版(coverage mask)。每个像素存储所有相关片元的列表。不透明的片元可以清除它们后面的片元,类似于 z 缓冲区。所有片元均存储在透明表面上。一旦所有列表形成,就可以通过遍历片元和解析每个样本来产生最终结果。

DirectX 11 [611,1765]中公开的新功能使在 GPU 上创建片元的链表的想法成为可能。第 3.8 节中介绍了用于无序访问视图(UAV)和原子操作的功能。基于 MSAA 的反走样可以通过访问覆盖蒙版和去计算每个样本的像素着色器的方式来实现。该算法通过对每个透明表面进行光栅化,并将生成于长数组中的片元插入从而实现。连同颜色和深度一起,会生成一个单独的指针结构,该结构将每个片元与之前为像素存储的片元相链接。然后执行单独的 Pass,在此渲染屏幕填充四边形,以便在每个像素处计算像素着色器。该着色器通过跟随链接来检索每个像素处的所有透明片元。检索到的每个片元都与先前的片元依次排序。然后将排序后的列表从前向后混合,从而得出最终的像素颜色。由于混合是由像素着色器执行的,因此,如果需要,可以为每个像素指定不同的混合模式。随着 GPU 和 API 的不断发展,性能表现也不断提高,而这是通过减少使用原子运算符的消耗来做到的 [914]

A 缓冲区的优点是,仅分配每个像素所需的片元,GPU 上的链表实现也是如此。从某种意义上讲,这也可能是不利的,因为在开始渲染帧之前所需的存储量是未知的。具有头发,烟雾或其他物体的场景可能具有许多重叠的半透明表面,因此可能会产生大量的片元。Andersson [46] 指出,对于复杂的游戏场景,最多可以重叠50个物体(例如树叶)的透明网格和最多200个半透明粒子。

图5.36. 在左上方,执行传统的从后到前的 Alpha 混合,由于排序顺序不正确,导致渲染错误。在右上方,A缓冲区用于提供完美的非交互结果。左下方显示了具有多层 Alpha 混合的渲染。右下方显示了 A 缓冲区和多层图像之间的差异,将其乘以 4 可得到可见度 [1532]。(图片由英特尔公司的 Marco Salvi 和 Karthik Vaidyanathan 提供。)

GPU 通常有预先分配好的内存资源,例如缓冲区和数组,并且链表方法也不例外。用户需要决定需要多少内存,而内存不足会导致明显的伪像。Salvi 和 Vaidyanathan [1532]提出了一种使用英特尔引入的,被称为像素同步的 GPU 功能来解决此问题的方法,即多层 Alpha 混合(multi-layer alpha blending)。见图 5.36。该功能可提供可编程混合模式,且开销比原子操作要少。他们的方法重新定义了存储和混合的方式,以便在内存用完的情况下适当降低性能。此外,粗略的排序顺序也有益于该方案。DirectX 11.3 引入了光栅化程序顺序视图(第 3.8 节),这是缓冲区的一种类型,它允许在支持该功能的任何 GPU 上实现该透明方法 [327,328]。移动设备也具有类似的技术,我们称为图块本地存储(tile local storage),它允许它们实现多层 Alpha 混合[153]。然而,这种机制具有性能成本,所以这种类型的算法的消耗可能很大 [1931]

这种方法建立在 Bavoil 等人 [115] 提出的 k 缓冲区的概念上。其中保存了前几层的可见图层并尽可能地对其进行了排序,而更深的图层则被丢弃并尽可能地进行了合并。Maule 等人  [1142] 使用k缓冲区,并通过使用加权平均(weighted averaging)来解决这些较远的深层级。加权和(weighted sum) [1202] 与加权平均 [118] 透明技术都和顺序无关,且都是单 Pass,并且几乎可以在任意 GPU 上运行。但它们的问题在于没有考虑对象的顺序。因此,例如使用 Alpha 表示覆盖率,在一条淡蓝色围巾上的一条淡红色围巾给人一种紫罗兰色的感觉,而不是正确地看到一条带有一些蓝色的红色围巾。虽然对于接近不透明的对象给出的结果会很差,但这类算法对于可视化是有用的,并且对高度透明的表面和粒子可以很好地工作。见图 5.37。

图5.37. 随着不透明度的增加,对象的顺序变得越来越重要。(Dunn 提供图片[394]。)

在加权和透明度中,公式为

其中 是透明表面的数目, 和  表示透明值的集合, 是场景中不透明部分的颜色。当绘制透明表面时,将两个和累积并分别存储,并在半透明 Pass 结尾,在每个像素处计算该公式。该方法的问题在于,第一个求和是饱和的,即会得到大于(1.0,1.0,1.0)的颜色值,并且因为 Alpha 的总和可能超过 1.0,所以背景颜色可能会产生负面影响。

通常我们首选加权平均公式,因为它避免了这些问题:

第一行表示半透明渲染过程中在两个单独的缓冲区中生成的结果。提供给  的每个表面都受到一个由其 Alpha 加权的影响; 几乎不透明的表面提供了更多的颜色,几乎透明的表面则几乎没有影响。通过将  除以 ,我们得到了加权的平均透明度颜色。值  是所有 Alpha 值的平均。值 是在 次透明表面上应用此平均 Alpha 次数 次后,目标(不透明场景)的可见度估计值。最后一行实际上是 over 运算符,其中  代表源的 Alpha。

加权平均值的一个限制是,对于相同的 Alpha,无论顺序如何,它均等地混合了所有颜色。McGuire 和 Bavoil [1176,1180]引入了加权混合的与顺序无关的半透明方案进行渲染,以提供更具说服力的结果。在进行模拟时,相机到表面的距离也会影响权重,更靠近的表面会受到更大的影响。而且,不是对 Alpha 进行平均,而是通过将  的多个项一起相乘,并用 1 减去它来计算 ,从而获得一组表面的真实 Alpha 覆盖率。如图 5.38 所示,该方法产生了更具视觉效果的结果。

图 5.38. 查看同一引擎模型的两个不同的摄像头位置,均使用加权混合的与顺序无关的半透明方案进行渲染。按距离加权有助于弄清哪些表面更靠近观察者 [1185]。(图片由Morgan McGuire提供。)

该方案的缺点是,在较大的环境中,彼此靠近的对象的距离权重几乎是相等的,所以这使结果与加权平均值几乎没有区别。另外,随着相机到透明物体的距离的改变,深度权重实际上可能会发生变化,但是这种变化是逐渐的。

McGuire 和 Mara [1181,1185] 扩展了该方法,使其包含了合理的透射颜色效果。如前所述,本节中讨论的所有透明度算法都将各种颜色混合在一起而不是对其进行过滤,从而模仿了像素覆盖率。为了得出滤色器的效果,不透明场景由像素着色器读取,每个半透明表面将它在该场景中覆盖的像素乘以它的颜色,并将结果保存在第三个缓冲区中。这个缓冲区,其中不透明的对象现在被半透明的对象染色,在接下来解析半透明缓冲区时,会用来代替不透明的场景。该方法之所以有效是因为颜色的传输与顺序无关,这与覆盖所造成的透明度是不同的。

还有其他算法用到了这里所介绍的技术中的部分元素。例如,Wyman [1931]通过内存需求,插入和合并方法对之前的这些工作进行了分类,无论是 Alpha 或者几何覆盖率,还是如何处理丢弃的碎片,都有被使用。通过寻找先前研究中的漏洞,他展示了两种被发现的新方法。他的随机分层 Alpha 混合方法使用了 k 缓冲区,加权平均值和随机透明度。他的其他算法是 Salvi 和 Vaidyanathan 方法的一种变体,使用覆盖蒙版而不是 Alpha 值。

尽管我们给出了种类繁多的半透明相关内容、渲染方法和 GPU 功能,但是并没有完美的解决方案来呈现半透明对象。请感兴趣的读者阅读 Wyman 的论文 [1931] 和 Maule 等人的关于交互式透明性算法的更详细的研究 [1141]。McGuire 的演讲 [1182] 提供了该领域的更广阔视野,它融会贯通了其他相关现象,例如体积照明,彩色透射和折射,这些将在本书的后面章节进行更深入的讨论。

5.5.3 预乘 Alpha 与合成 Premultiplied Alphas and Compositing

over 操作符还用于将照片或对象的合成渲染混合到一起。该过程我们称之为合成(compositing) [199,1662]。在这种情况下,每个像素的 Alpha 值将与对象的 RGB 颜色值一起存储。由 Alpha 通道形成的图像有时称为无光粗糙层(?)(matte)。它显示了对象的轮廓形状。相关示例请参见第 203 页的图 6.27。接下来我们可以使用此  图像,将其与其他此类元素进行混合,或在背景下进行混合。

使用合成  数据的一种方法是使用预乘 Alpha(也称为关联的 Alpha)。即在使用之前,将 RGB 值乘以 Alpha 值。这使合成 over 公式的效率更高::

其中  是预乘的源通道,代替在公式 5.25 中的   。预乘 Alpha 还可以在不更改混合状态的情况下使用 over 和叠加混合,因为现在在混合过程中添加了源颜色 [394]。请注意,使用预乘的  值,虽然可以将 RGB 分量创建为特别明亮的半透明值,但是它们通常不大于 Alpha 值。

合成图像的渲染很自然地与预乘 Alpha 相吻合。默认情况下,在黑色背景上渲染的反走样不透明对象会提供预乘值。假设白色(1、1、1)三角形沿其边缘覆盖了某些像素的 40%。使用(极精确的)反走样功能,像素值将设置成值为 0.4 的灰色,即我们将为此像素颜色保存为(0.4,0.4,0.4)。如果是存储 Alpha 值,那么也将为 0.4,因为这是三角形覆盖的区域范围。最终  值为(0.4、0.4、0.4、0.4),且这是一个预乘值。

图像存储的另一种方式是使用未相乘的(unmultiplied alphas) Alpha,它也被称为未关联的(unassociated alphas) Alpha,甚至是一个令人费解的术语,非预乘的 Alpha(nonpremultiplied alphas)。未相乘的 Alpha 就是它的字面意思:RGB 值不乘以Alpha 值。对于白色三角形的示例,未相乘的颜色为(1、1、1、0.4)。这种表示形式的优点是可以存储三角形的原始颜色,但是在显示之前,始终需要将该颜色乘以存储的 Alpha 值。最好在执行过滤和混合操作时使用使用预乘数据,因为使用未相乘的 Alpha 并不能正确执行线性插值之类的操作 [108,164]。这可能会产生诸如围绕对象边缘的黑色条纹之类的伪像 [295,648]进一步讨论参见 6.6 节的末尾。另外,预乘 Alpha 也可以进行更清晰的(?)理论处理 [1662]

对于图像处理应用程序来说,未关联的 Alpha 值可用于遮盖照片而不影响基础图像的原始数据。同样,未关联的 Alpha 意味着可以使用颜色通道的整个精度范围。也就是说,必须小心地将未相乘的 值正确地转换到用于计算机图形计算的线性空间,或从线性空间转换回来。举个例子,没有浏览器会正确执行此操作,它们也不可能这样做,因为目前估计可能会出现不正确的行为(?) [649]。支持 Alpha 的图像文件格式包括 PNG(仅非关联的 Alpha),OpenEXR(仅关联)和 TIFF(两种 Alpha 类型)。

与alpha通道相关的概念是色度选择(chroma-keying) [199]。这是视频制作中的一个术语,其中演员是在绿色或蓝色屏幕上拍摄并与背景融合在一起的。在电影工业中,此过程称为绿幕(green-screening )或蓝幕(blue-screening)。这里的思路是,将特定的色相(用于胶卷)或精确值(用于计算机图形)指定为透明;只要检测到绿幕或蓝幕,那么就会显示背景。这样,仅使用RGB 颜色就可得出图像的轮廓形状;无需存储任何Alpha。该方案的一个缺点是,对象在任何像素处要么完全不透明,要么完全透明,即,alpha实际上仅为 1.0 或 0.0。举个例子,GIF 格式图片仅允许将一种颜色指定为透明。

5.6 显示编码 Display Encoding

当我们计算照明,纹理或其他操作的效果时,假定使用的值为线性(linear)。非正式情况下,线性意味着加法和乘法按预期执行。但是,为了避免出现各种视觉伪像,显示缓冲区和纹理使用非线性编码,这也是我们必须要考虑的问题。关于该问题,简短而粗略的答案如下:选取 [0,1] 范围内的着色器输出颜色并将其提高 1/2.2 的幂,执行被称为伽马校正(gamma correction)的操作。对传入的纹理和颜色执行相反的操作。在大多数情况下,你可以让 GPU 为你执行这些操作。本节说明了该方案的做法和原因。

我们从阴极射线管(CRT)开始讲起。在数字成像的早期,CRT 显示器是标准规范。这些设备在输入电压和显示辐射率之间表现出幂律关系。随着施加到像素的能级的增加,发出的辐射不会呈现线性增长,而是(令人惊讶地)与该能级成正比地升高,从而达到大于 1 的功率。例如,假设功率为2。值被设置为 0.5 的一个像素只发出设置为 1.0 的像素的四分之一的光量,即 [607]。虽然 LCD 和其他显示技术具有与 CRT 不同的固有色调响应曲线,但它们是通过转换电路制造的,这使它们能够模仿 CRT 的响应。

该幂函数几乎与人类视觉的亮度灵敏度相反 [1431]。这种幸运巧合的结果是,编码是近似感知统一的(perceptually uniform)。也就是说,在可显示范围内,一对编码值 N 和 N + 1 之间的感知差大致恒定。通过测量阈值对比度(threshold contrast),我们可以在各种条件下检测到其亮度差异约为 1%。当颜色存储在精度有限的显示缓冲区中时, 这种近似最优的值分布可以最大程度上地减少条带化现象(banding artifacts)(见第 23.6 节)。同样的优点也适用于纹理,因为它们通常使用相同的编码。

显示传递函数(display transfer function )描述了显示缓冲区中的数字值与从显示器发出的辐射水平之间的关系。因此,它也被称为电光学传递函数(electrical optical transfer function,EOTF)。显示传输功能是硬件的一部分,并且对于计算机显示器,电视和电影放映机,有着不同的标准。在该过程的另一端,还有图像和视频捕获设备,并且还有一个标准的传递函数,它被称为光电传递函数(optical electric transfer function,OETF)[672]

在对用于显示的线性颜色值进行编码时,我们的目标是要消除显示传递函数的影响,以便我们计算的任何值都将发出相应的辐射亮度级别。例如,如果我们的计算的值加倍,我们希望输出的辐射率也加倍。为了保持这种关系,我们使用了显示传递函数的逆函数来抵消其非线性效应。取消显示响应曲线的过程也称为伽玛校正(gamma correction),关于进行伽马校正的原因很快会在接下来说明。在解码纹理值时,我们需要应用显示传递函数来生成用于着色的线性值。图 5.39 显示了解码和编码在显示过程中的使用。

图 5.39. 在左侧,一个PNG 颜色纹理被 GPU 着色器访问,并且它的非线性编码值被转换(蓝色)到线性值。在着色与色调映射之后(8.2.2 节),最终计算值被编码(绿色)并且存储在帧缓冲区中。该值与显示传递函数决定了发射(红色)的辐射量。绿色和红色功能的组合抵消了,因此发出的辐射与线性计算值成正比。

个人计算机显示器的标准传递函数由被称为 sRGB 的颜色空间规范定义。当从纹理中读取值或将值写入颜色缓冲区时,可以将大多数控制 GPU 的 API 设置为自动应用正确的 sRGB 转换 [491]。如 6.2.2 节所述,生成 MipMap 还将考虑 sRGB 编码。通过先转换为线性值,然后执行插值,纹理值之间的双线性插值就可以正确地执行。通过将存储的值解码回线性值,混合新值,然后对结果进行编码,就可以正确完成 Alpha 的混合。

当将值写入显示的帧缓冲区时,在转换的最后阶段应用该转换是很重要的。如果在显示编码之后应用后处理,则将在非线性值上计算此类效果,这通常是不正确的,并且经常会导致伪像。我们可以将显示编码视为一种压缩形式,这可以最好地保留该值的感知效果 [491]。解决这一问题的一个好方法是使用线性值来执行物理计算,每当我们要显示结果或访问可显示图像(例如颜色纹理)时,我们需要使用适当的编码或解码转换,将数据转入或转出其显示编码形式。

如果确实需要手动应用 sRGB,则可以使用标准转换公式或一些简化版本。实际上,显示是由每个颜色通道的一些比特位数控制的,例如,对于消费者级别的显示器,该位数为 8,并提供一组在 [0,255] 范围内的级别。在这里,我们将显示编码的级别表示为 [0.0,1.0] 范围,而忽略位数。线性值也在 [0.0,1.0] 范围内,表示浮点数。我们用 x 表示这些线性值,用 y 表示存储在帧缓冲区中的非线性编码值。要将线性值转换为 sRGB 非线性编码值,我们应用 sRGB 显示传递函数的逆函数:

其中 x 代表线性 RGB 三元组的通道。该公式适用于每个通道,这三个生成的值会用作显示。如果你手动应用转换功能,请当心。产生错误的原因之一是使用编码的颜色而不是其线性形式,另一种原因是将颜色解码或编码两次。

这两个变换表达式的底部是一个简单的乘法,这是由于数字硬件需要使变换完全可逆而产生的 [1431]。涉及将值提高到幂的最高表达式几乎适用于输入值x的整个范围 [0.0,1.0]。考虑到偏移量和比例尺,此函数非常近似于一个更简单的公式 [491]

γ= 2.2。希腊字母 γ 是名称 “伽玛校正” 的来源。

正如必须对计算值进行编码以进行显示一样,在进行计算之前,必须将静态相机或摄像机捕获的图像转换为线性值。你在显示器或电视上看到的任何颜色都有一些显示编码的 RGB 三元组,它们可通过屏幕捕获或颜色选择器获得。这些值是以 PNG,JPEG 和 GIF 等文件格式存储的,它们都是可以直接发送到帧缓冲区并在屏幕上显示而不用进行转换的格式。换句话说,你在屏幕上看到的所有内容都是定义为显示编码的数据。在着色计算中使用这些颜色之前,我们必须将这种编码形式转换回线性值。从显示编码到线性值所需的 sRGB 转换是

其中 y 代表标准化的显示频道值,即存储在图像或帧缓冲区中的值,表示为 [0.0,1.0] 范围内的值。此解码函数与我们以前的 sRGB 公式相反。这意味着,如果着色器访问纹理并输出纹理而没有更改,则其外观将与预期的一样。解码函数与显示传递函数相同,因为存储在纹理中的值已进行编码以便正确显示。此外,我们没有转换到提供线性响应显示,而是转换到提供线性值。

更简单的伽玛显示传递函数是公式 5.31 的反函数:

有时,你会看到一个更简单的转化对,尤其是在移动应用和浏览器应用中 [1666]

也就是说,我们取线性值的平方根进行转换以进行显示,而对逆值只需再与自身相乘一次就可以了。虽然是粗略近似,但这种转换比完全忽略该问题要好。

图 5.40. 两个重叠的聚光灯照亮一个平面。在左侧图像中,在将亮度值 0.6 和 0.4 相加后不执行伽马校正。有效地对非线性值执行加法操作,从而导致错误。请注意,左侧的光看起来比右侧的光要明亮得多,重叠部分的亮度显得不够真实。在右图中,相加后的值将进行伽玛校正。灯光本身会成比例地变亮,并且在重叠的地方可以正确组合。

如果我们不注意伽玛,则较低的线性值将在屏幕上显示得太暗。一个与之相关的错误是,如果不执行伽马校正,某些颜色的色相可能会偏移。假设我们的 γ= 2.2。我们要从显示的像素发出与线性计算值成比例的辐射度,这意味着我们必须将线性值提高到(1 / 2.2)次幂。线性值 0.1 给出 0.351,0.2 给出 0.481,而 0.5 给出 0.730。如果未编码,则按原样使用这些值将导致显示器发出的辐射比所需的少。请注意,任何这些转换都始终不会更改 0.0 和 1.0。在使用伽玛校正之前,场景建模人员通常会人为地增加深色表面的颜色,并在逆显示变换中对其进行折叠(?)

忽略伽玛校正的另一个问题是,在非线性值上执行了对物理线性辐射值正确的着色计算。图 5.40 给出了一个示例。图 5.40 给出了一个示例。

忽略伽玛校正也会影响反走样边缘的质量。例如,假设一个三角形的边缘覆盖了四个屏幕网格单元(图 5.41)。

图5.41. 在左侧,黑色(显示为灰色)背景上的白色三角形边缘覆盖了四个像素,并显示了真实的区域覆盖率。如果不执行伽玛校正,则中间色调的变暗会导致边缘的感知失真,如右图所示。

图5.42. 左侧的一组反走样线经过了伽玛校正; 在中间,该集合被部分校正;在右边,没有伽马校正。(图片由Scott R. Nelson提供。)

三角形的标准化辐射率为 1(白色); 背景为 0(黑色)。从左到右,单元格的覆盖率为  与  。因此,如果我们使用 box 滤波器,我们希望将像素的归一化线性辐射度表示为 0.125、0.375、0.625 和 0.875。正确的方法是对线性值执行反走样操作,将编码功能应用于四个结果值。如果不这么做,像素的代表辐射将太暗,从而导致边缘的变形,如图右侧所示。这种伪像被称为扭绳(roping),因为边缘看起来有点像扭曲的绳子 [167,1265]。图 5.42 展示了这种效果。

sRGB标准创建于1996年,已经成为大多数计算机显示器的标准。然而,自那时以来,显示技术得到了许多发展。目前已经开发出了更明亮并且可以显示更多颜色的显示器。关于彩色显示和亮度将在 8.1.3 节中讨论,关于高动态范围显示的显示编码将在 8.2.1 节中介绍。Hart 的文章 [672] 是了解有关高级显示器更多信息的特别详尽的信息来源。

进一步阅读和资源 Further Reading and Resources

Pharr等。[1413] 更深入地讨论了采样模式和反走样。Teschner 的课程笔记 [1758] 展示了各种采样模式的生成方法。Drobot [382,383] 提及了先前对实时反走样的研究,解释了各种技术的属性和性能表现。我们可以在相关的 SIGGRAPH 课程 [829] 的注释中找到有关各种形态学反走样方法的信息。Reshetov 和 Jimenez [1486] 提供了游戏中使用的形态学和相关时间性反走样工作的最新回顾。

对于透明度研究,我们再次将感兴趣的读者推荐给 McGuire 的陈述 [1182] 和 Wyman 的著作 [1931]。Blinn 的文章 "像素是什么?"(What Is a Pixel?) [169] 在讨论不同的定义时,提供了对计算机图形学多个领域的绝佳浏览。Blinn 的书籍 “脏像素" (Dirty Pixels) 和 "符号,符号,符号” (Notation, Notation, Notation[166,168] 包含一些介绍性文章,涉及滤波和反走样,alpha,合成和 gamma 校正。Jimenez 的演讲 [836] 详细介绍了用于反走样的最新技术。Jimenez 的演讲 [836] 详细介绍了用于反走样的最新技术。

Gritz 和 d'Eon [607] 对伽玛校正问题进行了很好的总结。Poynton的书 [1431] 对各种媒体文件中的伽玛校正以及其他与颜色相关的主题进行了详尽的介绍。Selan 的白皮书 [1602] 是更新的资料,它解释了显示编码及其在电影行业中的用途,以及许多其他相关的信息。

《Real-Time Rendering 4th Edition》全文翻译 - 第5章 着色基础(下)5.5 ~ 5.6相关推荐

  1. 《Real-Time Rendering 4th Edition》全文翻译 - 第5章 着色基础(中)5.3 ~ 5.4

    这两节终于翻译完毕,不得不说原文篇幅是真的长,花了不少时间. 另外,以后引用的具体文章标题不会再列出来,一是为了节省时间,二是感觉列出来会过于冗余.所以如果想看具体引用文章标题的话,请在原书里手动搜索 ...

  2. 《Real-Time Rendering 4th Edition》全文翻译 - 第6章 纹理化(下)6.7 ~ 6.9

    最近比较有动力,再来一篇!~ 实时渲染(第四版)Real-Time Rendering (Fourth Edition) 第6章 纹理化 Chapter 6 Texturing 6.7 凹凸映射 Bu ...

  3. 《Real-Time Rendering 4th Edition》全文翻译 - 第4章 变换(下)4.5 ~ 4.7

    第四章终于结束了--接下来会休息一段时间,祝各位五一劳动节快乐! -- 想了想还是不休息了,继续继续!! 实时渲染(第四版)Real-Time Rendering (Fourth Edition) 第 ...

  4. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第五章 着色基础 Shading Basics

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

  5. 《Real-Time Rendering 4th Edition》全文翻译 - 第1章 引言

    为了追赶同事(或者在公司装X),不得不开始啃RTR4(滑稽) 如题,用笨办法硬上,就是莽! 然后是概括一下每小节大致内容,最后翻译全文. 这样把全书过一遍的话应该能算认真读完了吧-- 全文翻译,逐字逐 ...

  6. 《Real-Time Rendering 4th Edition》全文翻译 - 第6章 纹理化(上)6.1 ~ 6.3

    由于工作变动原因,这次翻译拖的时间比较长--抱歉啦! 其实也是由于每章的内容越来越多了,很难在短时间内翻译完,是个很磨人的事情. 不过我会坚持下去的!希望能更多地帮到大家吧! 业余翻译,若有不周到之处 ...

  7. 《Real-Time Rendering 4th Edition》全文翻译 - 第15章 非真实感渲染(下)15.3 ~ 15.5

    连更两篇,冲鸭! 业余翻译,若有不周到之处,还请多多指教! 实时渲染(第四版)Real-Time Rendering (Fourth Edition) 第15章 非真实感渲染  Chapter 15  ...

  8. 《Real-Time Rendering 4th Edition》全文翻译 - 第2章 图形渲染管线(上)2.1 ~ 2.3(20200720翻新)

    如题,笨办法继续莽! 部分段落的论述过于冗长,自己做了分段处理. ------分割线 2020.7.20------ 翻新了一遍译文,统一了名词,补充了漏译的部分. 实时渲染(第四版)Real-Tim ...

  9. 《Real-Time Rendering 4th Edition》全文翻译 - 第15章 非真实感渲染(上)15.1 ~ 15.2

    好久没更新了~ 由于对NPR方面比较感兴趣,所以任性了一下,先翻译了这一章~ 业余翻译,若有不周到之处,还请多多指教! 实时渲染(第四版)Real-Time Rendering (Fourth Edi ...

  10. 《Real-Time Rendering 4th Edition》全文翻译 - 第3章 图形处理单元(GPU)(下)3.7 ~ 3.10

    赶在 2019 结束之前把第三章结束,提前祝大家新年快乐! 实时渲染(第四版)Real-Time Rendering (Fourth Edition) 第3章 图形处理单元(GPU) Chapter ...

最新文章

  1. wireshark抓包数据学习
  2. 【僵尸复活】【已通过】https的app如何抓包
  3. 类库dll引用不成功问题
  4. Kafka 优化参数 unclean.leader.election.enable
  5. Oracle数据库表解锁语句
  6. 清华大学刘知远教授:如何写一篇合格的NLP论文
  7. 遭遇掌控欲望极强的上司,郁闷的项目
  8. 【java】程序初始化顺序
  9. java read bytes 阻塞_InputStream中read()与read(byte[] b)java InputStream读取数据问题 | 学步园...
  10. warning C4482: 使用了非标准扩展: 限定名中使用了枚举
  11. 网吧会员管理系统c语言,常用的网吧会员管理系统哪个比较好|纳客软件
  12. Spring的事务传播机制
  13. Windows下如何正确清理C盘?
  14. TRUNK理论与配置实验
  15. jwplayer +ffmpeg+red5 实现摄像头的直播
  16. Android一些控件上显示的英文字母都被转为大写字母的原因分析及问题解决
  17. 计算机专业有哪些有含金量的证书,大学最有含金量的6大类证书!你拥有哪几个?...
  18. 怎么讲计算机e盘设置共享,共享盘怎么设置(电脑如何设置共享盘)
  19. 电脑装双系统有什么坏处?可不只是速度变慢!
  20. 【网页设计】基于HTML在线图书商城购物项目设计与实现

热门文章

  1. redis keys命令,生产环境慎用,最好屏蔽掉
  2. 游戏引擎设计的技术及详解
  3. mysql根据各种条件统计_Mysql按条件计数多种实现方法解析
  4. POJ 1436.Horizontally Visible Segments-线段树(区间更新、端点放大2倍)
  5. Android 10.0 关机界面全屏显示(UI全屏显示)
  6. Graph U-Nets 笔记
  7. 永续合约短线交易技巧?
  8. (三)洞悉linux下的Netfilteramp;iptables:内核中的rule,match和target
  9. uniapp打包之后首页白屏
  10. JSP课程设计——民航售票管理系统