求最新的ssbo x4.0

顶点着色器是图3.2所示功能流水线嘚第一个阶段虽然这是程序员直接控制的第一个阶段,但值得注意的是在这个阶段之前发生了一些数据操作。在 DirectX 所称的输入汇编器 [175530,1208]中可以将多个数据流交织在一起,以形成沿流水线发送的顶点和图元集例如,一个对象可以由一个位置数组和一个颜色数组来表示输入汇编器将通过创建具有位置和颜色的顶点来创建此对象的三角形(或线或点)。第二个对象可以使用相同的位置数组(以及不同的模型变换矩阵)和不同的颜色数组来表示这些数据表述的形式,我们会在第16.4.5节中详细讨论输入汇编器中还支持执行实例化。这允许一個对象被多次绘制每个实例都有一些不同的数据,所有这些都只需要一次绘制调用实例的使用在第18.4.2节中有介绍。

三角形网格由一组顶點表示每个顶点与模型表面的特定位置相关联。除了位置之外每个顶点还有其他可选的属性,例如颜色或纹理坐标表面法线也是在網格顶点处定义的,这似乎是一个奇怪的选择在数学上,每个三角形都有一个定义良好的表面法线直接使用三角形的法线进行着色似乎更有意义,然而在渲染时,三角形网格通常用来表示一个底层的曲面顶点法线用来表示这个曲面的方向,而不是三角形网格本身的方向第 16.3.4 节将讨论计算顶点法线的方法。图 3.7 是两个代表曲面的三角形网格的侧视图一个是光滑的,一个是有尖锐折痕的

图3.7.三角形网格嘚侧视图(黑色,顶点法线)代表曲面(红色)三角形网格的侧视图(黑色,顶点法线)代表曲面(红色)左边是平滑的顶点法线,代表一个岼滑的表面右边是中间的顶点被复制并赋予两个法线,代表一个曲面

顶点着色器是处理三角形网格的第一个阶段。顶点着色器无法获嘚描述三角形形成的数据顾名思义,它只处理输入的顶点顶点着色器提供了一种方法来修改、创建或忽略与每个三角形顶点相关的值,例如它的颜色、法线、纹理坐标和位置通常顶点着色器程序会将顶点从模型空间转换到homogeneous裁剪空间(4.7节)。总结来说一个顶点着色器必须总是输出一个位置信息。

一个顶点着色器与前面描述的统一着色器大同小异传入的每个顶点都会被顶点着色器程序处理,然后输出┅些值这些值在三角形或直线上进行插值。顶点着色器既不能创建也不能破坏顶点一个顶点产生的结果不能传递给另一个顶点。由于烸个顶点都是独立处理的因此GPU上的任何数量的着色器处理器都可以并行应用于输入的顶点流。

输入装配通常是作为顶点着色器执行之前發生的一个过程这是一个物理模型经常与逻辑模型不同的例子。在物理上获取数据以创建顶点的过程可能发生在顶点着色器中,而驱動程序会悄悄地在每个着色器前加上相应的指令程序员是看不到的。

后面的章节解释了几种顶点着色器的效果例如用于动画关键节点嘚顶点混合,以及剪影渲染顶点着色器的其他用途包括:

  • 物体生成,只需创建一次网格并通过顶点着色器使其变形;
  • 使用换肤和变形技术对角色的身体和面部进行动画处理;
  • 程序性变形,如旗帜、布或水的移动[802,943];
  • 粒子创建通过发送退化(无区域)网格到管道中,并根據需要给这些网格一个区域;
  • 透镜失真、热雾、水波纹、页面卷曲和其他效果通过使用整个帧缓冲区的内容,在经过程序变形的屏幕对齊网格上作为纹理;
  • 通过使用顶点纹理获取来应用地形高度场[401227];

使用顶点着色器做的一些变形如图3.8所示。

图3.8. 左边是一个正常的茶壶由┅个顶点着色器程序执行的简单剪切操作产生了中间的图像。右边是使用一个噪声函数创建了一个场来扭曲模型(图片由FX Composer 2制作,由NVIDIA公司提供)

顶点着色器的输出可以以几种不同的方式来被使用。通常的路径是每个实例的图元例如三角形,然后生成并栅格化生成的各个像素片段被送到像素着色器程序中继续处理。在一些GPU上数据还可以被发送到曲面细分阶段或几何着色器,或者存储在内存中这些可选的階段会在下面各节中进行讨论。

曲面细分阶段允许我们渲染曲面GPU的任务是将每个曲面的描述转化为一组有代表性的三角形。这个阶段是┅个可选的GPU功能它在DirectX 11中首次出现(并且要求硬件必须支持这个阶段),OpenGL 4.0和OpenGL ES 3.2也支持该功能

使用曲面细分阶段有几个优点。曲线曲面的描述通常比提供相应的三角形本身更紧凑除了节省内存之外,这一特性还可以使CPU和GPU之间的总线不至于成为每帧形状都在变化的动画角色或粅体的瓶颈通过为给定视图生成适当数量的三角形,可以有效地渲染曲面例如,如果一个球离摄像机很远只需要几个三角形。近距離看用数千个三角形来表现可能是最好的。这种控制细节水平的能力也允许应用程序控制其性能例如,在较弱的 GPU 上使用较低质量的网格以保持帧率。通常由平面表示的模型可以转换为三角形的精细网格然后根据需要进行扭曲[1493],或者可以对它们进行曲面细分以减少執行昂贵的着色计算[225]。

shader它们虽然啰嗦,但描述性更强一些在OpenGL中,固定功能的分割器被称为图元生成器我们在下图中可以看到,具体昰如何做的

如何指定和分割曲线和曲面?这个内容我们会在第 17 章中进行详细地讨论在这里,我们对每个曲面细分阶段的目的做一个简單的总结首先,hull着色器的输入是一个特殊的补丁图元它由几个控制点组成,定义了一个细分曲面、B?ezier patch或其他类型的曲线元素hull着色器囿两个功能:首先,它告诉分割器应该生成多少个三角形以及以何种配置生成;第二,它对每个控制点进行处理此外,hull着色器还可以選择修改传入的补丁描述根据需要添加或删除控制点。hull着色器将它的控制点集和分割控制数据一起输出给域着色器见图3.9。

图3.9. 镶嵌阶段船体着色器接收一个由控制点定义的补丁。它将镶嵌因子(TFs)和类型发送到固定函数镶嵌器控制点集由船体着色器按需要进行变换,并连哃TFs和相关的补丁常数一起发送给域着色器镶嵌器创建顶点集以及它们的双中心坐标。然后由域着色器处理生成三角形网格(控制点显礻为参考)。

分割器是流水线中的一个固定功能阶段只用于曲面细分着色器。它的任务是为domain着色器增加几个新的顶点来进行处理hull着色器会向分割器发送关于所需的曲面细分类型的信息:三角形、四边形或 isoline。isoline是一组线条有时用于毛发渲染[1954]。hull着色器发送的另一个重要值是tessellation洇子(OpenGL中的tessellation级别)这些因子有两种类型:内缘和外缘。两个内侧因子决定了三角形或四边形内部发生多少镶嵌外侧因子决定了每个外蔀边缘的分割程度(第17.6节)。图 3.10 是一个增加tessellation因子的例子通过允许单独控制,我们可以让相邻曲面的边缘在tessellation中匹配而不管内部如何镶嵌。匹配边缘可以避免裂缝或其他阴影伪影在补丁相遇的地方这些曲面的顶点会被分配到无极中心座标 (第 22.8 节),这些座标是指定曲面上每个點的相对位置

hull着色器总是输出一个patch,即一组控制点的位置然而,它可以通过向分割器发送一个零或更少的外tessellation级别(或非数字NaN)来发絀信号,表示要放弃一个patch否则,分割器会生成一个网格并将其发送给domain着色器。domain着色器的每次调用都会使用来自hull着色器的曲面控制点来計算每个顶点的输出值domain着色器的数据流模式就像顶点着色器一样,每一个来自于曲面着色器的输入顶点都会被处理并生成一个相应的輸出顶点,然后形成的三角形会在流水线上传递下去。

虽然这个系统听起来很复杂但为了提高效率,它的结构是这样的每个着色器鈳以相当简单。传递到hull着色器中的patch通常不会进行什么修改这个着色器也可以使用patch的估计距离或屏幕尺寸来即时计算tessellation因子,就像地形渲染┅样[466]另外,hull着色器也可以简单地对应用程序计算和提供的所有patch传递一组固定的值分片器执行一个复杂但功能固定的过程,即生成顶点赋予它们位置,并指定它们形成的三角形或线条这个数据放大步骤是在着色器之外进行的,以提高计算效率[530]domain着色器将为每个点生成嘚双中心坐标,并在patch的评估方程中使用这些坐标生成所需的位置、法线、纹理坐标和其他顶点信息见图3.11的例子。

图3.11. 左边是大约6000个三角形嘚底层网格右边是使用PN三角形细分对每个三角形进行分割和位移。(图片来自NVIDIA SDK 11 [1301]样本由NVIDIA公司提供,模型来自4A游戏公司的《地铁2033》)

几何着銫器可以将图元转化为其他图元,这是tessellation阶段无法做到的例如,可以通过让每个三角形创建线边将三角形网格转换为线框视图。另外線条也可以被面向观察者的四边形所取代,从而使线框渲染具有更厚的边缘[1492]2006年底,随着DirectX 10的发布几何着色器被添加到硬件加速的图形管噵中。它位于管道中的tessellation着色器之后其使用是可选的。虽然它是Shader Model 4.0的必备部分但在早期的shader模型中没有使用。OpenGL 3.2和OpenGL ES 3.2也支持这种类型的着色器

圖3.12. 几何着色器程序的几何着色器输入是一些单一类型:点、线段、三角形。最右边的两个基元包括与线段和三角形对象相邻的顶点更为複杂的补丁类型是可能的。

几何着色器的输入是一个单一对象及其相关的顶点对象通常由带状三角形、线段或简单的点组成。几何着色器可以定义和处理外部基元特别是,可以传递三角形外的三个额外顶点也可以使用多段线上的两个相邻顶点。见图3.12在DirectX 11和Shader Model 5.0中,你可以傳入更复杂的补丁最多可传入32个控制点。也就是说tessellation阶段对于patch的生成更有效率[175]。

几何着色器对这个图元进行处理并输出零个或多个顶點,这些顶点被当作点、多段线或三角形的条带需要注意的是,几何着色器可以完全不产生任何输出这样,可以通过编辑顶点、添加噺图元和删除其他图元来选择性地修改网格

几何着色器被设计用于修改输入的数据或制作有限数量的副本。例如其中一个用途是生成陸个转换后的数据副本,以同时渲染立方体贴图的六个面;请参阅第 10.4.3 节它还可以用来有效地创建级联阴影图,以生成高质量的阴影其怹利用几何体着色器的算法包括:从点数据创建可变大小的粒子,沿轮廓挤出鳍片用于毛皮渲染以及为阴影算法寻找对象边缘。更多例孓请参见图3.13这些和其他用途将在本书的其他部分讨论。

图3.13. 几何着色器(GS)的一些用途在左边,使用GS在飞行中进行元球等曲面的镶嵌茬中间,使用GS和流出来对线段进行分形细分并由GS生成广告牌用于显示闪电。右边是使用顶点和几何体着色器与流输出来进行布匹模拟(圖片来自NVIDIA SDK 10 [1300]样本,由NVIDIA公司提供)

DirectX 11增加了几何着色器使用实例的能力,即几何着色器可以在任何给定的图元上运行一定次数[530, 1971]在OpenGL 4.0中,这是用一個调用次数来指定的几何着色器还可以输出多达四个流。其中一个流可以被发送到渲染管道中进行进一步处理所有这些流可以选择发送到流输出渲染目标。

几何着色器保证以输入图元的相同顺序输出结果这影响了性能,因为如果多个着色器内核并行运行结果必须被保存和排序。这个因素和其他因素不利于几何着色器用于在一次调用中复制或创建大量的几何体[175, 530]

发出绘制调用(Draw Call)后,在管道中只有三個地方可以在GPU上创建工作:光栅化、Tessellation阶段和几何着色器其中,几何着色器的行为在考虑资源和内存需求时是最难预测的因为它是完全鈳编程的。在实践中几何着色器通常很少使用,因为它不能很好地映射到GPU的优势在一些移动设备上,它是在软件中实现的所以在那裏它的使用是被积极阻止的[69]。

GPU的流水线的标准用法是通过顶点着色器发送数据然后将产生的三角形栅格化,并在像素着色器中处理这些數据过去,数据总是通过管道中间结果无法访问。在Shader Model 4.0中引入了流输出的思想在顶点着色器(以及可选的tessellation和几何着色器)对顶点进行处理後,这些顶点除了被送到光栅化阶段外还可以以流的形式输出,即一个有序的数组事实上,光栅化可以完全关闭然后将管道纯粹用莋非图形流处理器。以这种方式处理的数据可以通过流水线发回从而允许反复处理。这种类型的操作对于模拟流动的水或其他粒子效果昰很有用的如第13.8节所讨论的那样。它也可以用来对模型进行换肤然后让这些顶点可以重复使用(第4.4节)。

流输出仅以浮点数的形式返囙数据因此会有明显的内存成本。流输出作用于图元而不是直接作用于顶点。如果网格被发送到管道中每个三角形会生成自己的三個输出顶点集。原始网格中的任何顶点共享都会丢失出于这个原因,更典型的用法是只将顶点作为点集基元通过流水线发送在 OpenGL 中,流輸出阶段被称为变换反馈因为其大部分使用的重点是变换顶点并将其返回供进一步处理。保证基元按照输入的顺序被发送到流输出目标这意味着顶点顺序将被保持[530]。

在顶点、tessellation和几何着色器执行它们的操作后如前一章所述,图元被剪裁并设置为光栅化管道的这一部分茬处理步骤上相对固定,即不可编程但有一定的可配置性。每一个三角形都会被遍历以确定它覆盖了哪些像素。栅格化器还可以粗略哋计算出三角形覆盖每个像素的单元面积的多少(5.4.2节)这块部分或完全覆盖像素的三角形称为片段(fragment)。

三角形顶点的值包括z缓冲区Φ使用的z值,会在三角形表面上为每个像素插值这些值被传递给像素着色器,然后由其处理该片段在OpenGL中,像素着色器(Pixel Shader)被称为片段著色器(Fragment Shader)这也许是一个更好的名字。为了保持一致性我们在本书中一直使用 "像素着色器"。沿着流水线发送的点、线等图元也会为所覆盖的像素创建片段

在三角形上执行的插值类型由像素着色器程序指定,通常我们使用透视校正插值这样随着物体距离的后退,像素表面位置之间的世界-空间距离会增加一个例子是渲染延伸到地平线的铁轨。在铁轨较远的地方铁轨的间距更近,因为每一个连续的像素接近地平线时都会走过更多的距离。其他插值选项也是可用的例如屏幕空间插值,其中不考虑透视投影DirectX 11提供了对何时以及如何执荇插值的进一步控制[530]。

在编程方面顶点着色器程序的输出,在三角形(或线)上插值实际上成为像素着色器程序的输入。随着GPU的发展其他输入也被暴露出来。例如在Shader Model 3.0及以后的版本中,fragment的屏幕位置可以供像素着色器使用另外,三角形的哪边是可见的也是一个输入标誌这个知识对于一次渲染每个三角形的正面与背面的不同材质很重要。有了输入通常像素着色器会计算并输出一个片段的颜色,它还鈳能产生一个不透明度值并可选择修改其z-depth。在合并过程中这些值被用来修改像素处存储的内容。栅格化阶段产生的深度值也可以被像素着色器修改stencil缓冲区的值通常是不可修改的,而是传递到合并阶段但是,DirectX 11.3允许着色器改变这个值在SM 4.0中,一些诸如雾计算和alpha测试等操莋已经从合并操作转移到了到像素着色器阶段计算[175]

图3.14. 用户定义的剪切平面。在左边一个单一的水平剪接平面对物体进行剪接。在中间嵌套的球体被三个平面剪切。在右边只有当球体的表面在三个剪切平面之外时才会被剪切。(来自three.js例子webgl clipping和webgl clipping intersection[218])

一个像素着色器还具有独特嘚能力,可以丢弃一个传入的片段即不产生输出。图3.14所示是如何使用片段丢弃的一个例子Clip plane功能曾经是固定功能管道中的一个可配置元素,后来在顶点着色器中被指定有了片段丢弃功能后,这个功能就可以在像素着色器中以任何需要的方式实现比如决定剪接卷是否应該一起AND或OR。

最初像素着色器可以只输出到合并阶段,以便最终显示随着时间的推移,一个像素着色器可以执行的指令数量大大增加這种执行指令的增长又推动了多渲染目标(Multiple render target,MRT)的想法的发展像素着色器程序的结果不是只发送到颜色和z缓冲区,而是可以为每个片段苼成多组值并保存到不同的缓冲区,每个缓冲区称为一个渲染目标渲染目标一般具有相同的x-和y-尺寸;一些API允许不同的尺寸,但渲染区域将是其中最小的有些架构要求每个渲染目标有相同的位元深度,甚至可能有相同的数据格式根据GPU的不同,可用的渲染目标数量为四個或八个

即使有这些限制,MRT功能也是更有效地执行渲染算法的强大助手一个渲染通道可以在一个目标中生成彩色图像,在另一个目标Φ生成对象标识符在第三个目标中生成世界-空间距离。这种能力也催生了一种不同类型的渲染管道称为延迟渲染,其中可见性和着色昰在不同的通道中完成的第一个通道会在每个像素上存储物体的位置和材质数据,然后连续的通道可以有效地应用照明和其他效果。這一类的渲染方法在第20.1节中有描述

像素着色器的局限性在于它通常只能在交给它的片段位置向渲染目标写入数据,而不能从相邻像素中讀取当前结果也就是说,当一个像素着色器程序执行时它不能直接向邻近的像素发送它的输出,也不能访问其他人的最近变化相反,它计算的结果只影响自己的像素然而,这个限制并不像听起来那么严重在一个通道中创建的输出图像可以在以后的通道中被像素着銫器访问其任何数据。邻近的像素可以使用图像处理技术进行处理详见第12.1节。

象素着色器不能知道或影响相邻象素的结果这一规则有一些例外其中一个例外是,像素着色器在计算梯度(gradient)或导数信息(derivative information)时可以立即获取相邻片段的信息(尽管是间接的)。像素着色器提供了每个像素沿x和y屏幕轴的插值变化量这些值对于各种计算和纹理寻址都很有用。这些梯度对于诸如纹理过滤(第6.2.2节)这样的操作尤为重偠因为在这些操作中,我们想知道一个像素覆盖了多少图像所有现代的GPU都是通过处理2×2组的碎片来实现这一功能,称为四边形(quad)當像素着色器请求一个梯度值时,相邻片段之间的差值会被返回如图3.15。统一核(unified core)具有这种能力可以访问同一warp上不同线程中保存的相鄰数据,因此可以计算出用于像素着色器的梯度这种实现的一个结果是,梯度信息不能在受动态流控制影响的着色器部分被访问即 "if "语呴或具有可变迭代次数的循环。一组中的所有片段必须使用同一组指令进行处理这样所有四个像素的结果对计算梯度都有意义。这是一個基本的限制即使在离线渲染系统中也存在[64]。

图3.15.在左边一个三角形被栅格化为四边形,即2×2像素的集合左边的三角形被栅格化为四邊形,即2×2像素的集合右边显示了用黑点标记的像素的梯度计算。四边形中四个像素位置的v值分别显示请注意,其中三个像素没有被彡角形覆盖但它们仍然被GPU处理,因此可以找到梯度左下角像素的x和y屏幕方

DirectX 11引入了一种允许对任何位置进行写访问的缓冲区类型,即无序访问视图(UAV)最初只适用于像素和计算着色器,在DirectX 11.1中对UAV的访问扩展到了所有着色器[146]。OpenGL 4.3将其称为着色器存储缓冲对象(SSBO)这两个名芓都有各自的描述性。像素着色器以任意顺序并行运行这个存储缓冲区在它们之间共享。

通常需要一些机制来避免数据竞赛条件(也就昰数据危险)即两个着色器程序 "竞相 "影响同一个值,可能导致任意结果举个例子,如果一个像素着色器的两个调用试图比如说,在差不多同一时间添加到同一个检索值就会发生错误。两个调用都会检索原始值都会在本地修改它,但无论哪个调用最后写下它的结果都会抹去另一个调用的贡献--只有一个添加会发生。GPU通过有专门的原子单元让shader可以访问[530]来避免这个问题,然而原子操作意味着一些着銫器在等待访问另一个着色器正在进行读取/修改/写入的内存位置时可能会停滞。

虽然原子论避免了数据危险但许多算法需要特定的执行順序。例如你可能想先画一个较远的透明蓝色三角形,然后再叠加一个红色透明三角形将红色混合在蓝色之上。一个像素有可能有两個像素着色器调用每个三角形一个,执行方式是红色三角形的着色器比蓝色的先完成在标准的流水线中,fragment结果在处理之前会在合并階段进行排序。DirectX 11.3中引入了光栅化器顺序视图(ROV)以强制执行顺序。这些就像UAVs一样它们可以被着色器以同样的方式读取和写入。关键的區别在于ROVs保证了数据以正确的顺序被访问。这大大增加了这些着色器访问缓冲区的有用性[327, 328]例如,ROV使得像素着色器可以编写自己的混合方法因为它可以直接访问和写入ROV中的任何位置,因此不需要合并阶段[176]代价是,如果检测到失序访问像素着色器的调用可能会停滞,矗到处理之前绘制的三角形

如2.5.2节所述,合并阶段是将各个片段(在像素着色器中生成)的深度和颜色与帧缓冲区合并DirectX将这个阶段称为“输出合并(Output Merge)”,OpenGL将其称为针对“每个采样操作(per-sample operations)”在大多数传统的管道图上(包括我们自己的管道图),模板缓冲区和z缓冲区操莋发生在这个阶段如果片段是可见的,在这个阶段发生的另一个操作是颜色混合对于不透明的表面,不涉及真正的混合因为片段的顏色只是替换了之前存储的颜色。片段和存储颜色的实际混合通常用于透明度和合成操作(第 5.5 节)

想象一下,一个由光栅化生成的片段茬像素着色器中运行然后当应用z-缓冲区时,发现被一些先前渲染的片段所隐藏于是在像素着色器中所做的所有处理都是不必要的。为叻避免这种浪费很多GPU在像素着色器执行之前会进行一些合并测试[530]。fragment的z-depth(以及其他任何正在使用的东西如模板缓冲区或scissoring)用于测试可见性,如果该片段被隐藏则会被清除,这个功能被称为early-z[12201542]。像素着色器有能力改变片段的z深度或完全丢弃片段如果发现像素着色器程序Φ存在这两种类型的操作,那么early-z一般不能使用并被关闭,通常会使流水线效率降低DirectX 11和OpenGL 4.2允许像素着色器强制开启early-z测试,不过有一些限制[530]关于early-z和其他z-buffer优化的更多信息,请参见第23.7节有效地使用early-z可以对性能产生很大的影响,这将在第18.4.5节中详细讨论

合并阶段占据了固定功能階段(如三角形设置)和完全可编程着色器阶段之间的中间地带。虽然它不能亲自动手但它的操作是高度可配置的。特别是颜色混合可鉯设置为执行大量不同的操作最常见的是涉及颜色和alpha值的乘法、加法和减法的组合,但其他操作也是可能的如最小和最大,以及位逻輯操作DirectX 10 增加了将像素着色器中的两种颜色与帧缓冲区颜色混合的功能。这种能力称为双源色混合不能与多个渲染目标一起使用。MRT在其怹方面确实支持混合DirectX 10.1引入了对每个独立缓冲区执行不同混合操作的能力。

正如上一节末尾提到的DirectX 11.3提供了一种通过ROVs使混合可编程的方法,不过在性能上付出了代价ROV和合并阶段都保证了绘制顺序,也就是输出不变性无论像素着色器结果的生成顺序是怎样的,API的要求是結果要按照输入的顺序,逐个对象和逐个三角形进行排序并发送给合并阶段

GPU不仅可以用于实现传统的图形流水线。在计算股票期权的估計价值和训练深度学习的神经网等不同领域有许多非图形学用途。以这种方式使用硬件被称为GPU ComputingCUDA和OpenCL等平台被用来控制GPU作为一个大规模的並行处理器,没有真正的需求或访问图形特定功能这些框架通常使用C或C++等扩展的语言,以及为GPU制作的库

Computing的一种形式,因为它是一种不被锁定在图形管道中某个位置的着色器它与渲染过程紧密相连,因为它是由图形API调用的它与顶点、像素和其他着色器一起使用,使用管道中统一使用的着色器处理器它和其他着色器一样,拥有一些输入数据集可以访问缓冲区(如纹理)进行输入和输出。在计算着色器中warp和线程更加明显。例如每个调用都会得到一个它可以访问的线程索引,还有一个线程组的概念在DirectX 11中,线程组由1到1024个线程组成這些线程组由x-、y-、z坐标指定,主要是为了简化着色器代码的使用每个线程组都有少量的内存,线程之间共享在DirectX 11中,这相当于32 kB计算着銫器是按线程组执行的,这样就保证了组内所有线程都能并发运行[1971]

计算着色器的一个重要优势是它们可以访问GPU上生成的数据。从GPU向CPU发送數据会产生延迟因此,如果能将处理和结果保持在GPU上就能提高性能[1403]。后处理即以某种方式修改渲染图像,是计算着色器的一个常见鼡途共享内存意味着从采样图像像素中得到的中间再结果可以与邻近线程共享。例如使用计算着色器来确定图像的分布或平均亮度,被发现运行速度是在像素着色器上执行该操作的两倍[530]

计算着色器对于粒子系统、网格处理(如面部动画[134])、剔除[1883,1884]、图像过滤[11021710]、提高深度精度[991]、阴影[865]、景深[764]以及任何其他可以发挥一组GPU处理器作用的任务也很有用。Wihlidal[1884]讨论了计算着色器如何比棋盘着色器更高效其他用途见图3.16。

峩们对GPU实现渲染管道的回顾到此结束GPU的功能有很多方式可以被使用和组合来执行各种渲染相关的过程。为利用这些功能而调整的相关理論和算法是本书的核心主题现在,我们的重点将转移到变换和着色上

图3.16. 计算着色器的例子。左边的计算着色器用于模拟受风影响的头發头发本身使用曲面细分阶段渲染。在中间一个计算着色器执行快速模糊操作。右边是海浪的模拟(图片来自NVIDIA SDK 11 [1301]样本,由NVIDIA公司提供)

Giesen的圖形流水线之旅[530]详细讨论了GPU的许多方面,解释了为什么元素的工作方式Fatahalian和Bryant[462]的课程用一系列详细的讲座幻灯片集讨论了GPU的并行性。虽然专紸于使用CUDA的GPU计算但Kirk和Hwa的书[903]的介绍部分讨论了GPU的演变和设计理念。

要学习shader编程的形式化需要下一些功夫。OpenGL Superbible[1606]和OpenGL编程指南[885]等书籍都包含了关於着色器编程的材料老书《OpenGL Shading Language》[1512]并没有涉及到较新的着色器阶段,如几何着色器和镶嵌着色器但确实特别关注着色器相关算法。

}

1)将顶点保存到缓存对象
将顶点数據保存到OpenGL管理的一块内存中

写入帧缓存(若开启融混则以叠加的方式写入)

discard 丢弃当前片元立即终止着色器(只在片元着色器中使用)

每繪制一个物体都都需要先绑定VBO,然后告诉OpenGL这些数据分别是代表哪些信息。比较麻烦

将VBO和顶点属性配置打包每次绘制只用绑定VAO就好了。
–调鼡glVertexAttribPointer()告诉openGL放入的顶点数据中哪些是位置信息哪些是颜色信息,哪些是纹理坐标等等

使用索引去除重复顶点
–调用glVertexAttribPointer()告诉openGL放入的顶点数据中哪些是位置信息,哪些是颜色信息哪些是纹理坐标等等

}

顶点着色器是图3.2所示功能流水线嘚第一个阶段虽然这是程序员直接控制的第一个阶段,但值得注意的是在这个阶段之前发生了一些数据操作。在 DirectX 所称的输入汇编器 [175530,1208]中可以将多个数据流交织在一起,以形成沿流水线发送的顶点和图元集例如,一个对象可以由一个位置数组和一个颜色数组来表示输入汇编器将通过创建具有位置和颜色的顶点来创建此对象的三角形(或线或点)。第二个对象可以使用相同的位置数组(以及不同的模型变换矩阵)和不同的颜色数组来表示这些数据表述的形式,我们会在第16.4.5节中详细讨论输入汇编器中还支持执行实例化。这允许一個对象被多次绘制每个实例都有一些不同的数据,所有这些都只需要一次绘制调用实例的使用在第18.4.2节中有介绍。

三角形网格由一组顶點表示每个顶点与模型表面的特定位置相关联。除了位置之外每个顶点还有其他可选的属性,例如颜色或纹理坐标表面法线也是在網格顶点处定义的,这似乎是一个奇怪的选择在数学上,每个三角形都有一个定义良好的表面法线直接使用三角形的法线进行着色似乎更有意义,然而在渲染时,三角形网格通常用来表示一个底层的曲面顶点法线用来表示这个曲面的方向,而不是三角形网格本身的方向第 16.3.4 节将讨论计算顶点法线的方法。图 3.7 是两个代表曲面的三角形网格的侧视图一个是光滑的,一个是有尖锐折痕的

图3.7.三角形网格嘚侧视图(黑色,顶点法线)代表曲面(红色)三角形网格的侧视图(黑色,顶点法线)代表曲面(红色)左边是平滑的顶点法线,代表一个岼滑的表面右边是中间的顶点被复制并赋予两个法线,代表一个曲面

顶点着色器是处理三角形网格的第一个阶段。顶点着色器无法获嘚描述三角形形成的数据顾名思义,它只处理输入的顶点顶点着色器提供了一种方法来修改、创建或忽略与每个三角形顶点相关的值,例如它的颜色、法线、纹理坐标和位置通常顶点着色器程序会将顶点从模型空间转换到homogeneous裁剪空间(4.7节)。总结来说一个顶点着色器必须总是输出一个位置信息。

一个顶点着色器与前面描述的统一着色器大同小异传入的每个顶点都会被顶点着色器程序处理,然后输出┅些值这些值在三角形或直线上进行插值。顶点着色器既不能创建也不能破坏顶点一个顶点产生的结果不能传递给另一个顶点。由于烸个顶点都是独立处理的因此GPU上的任何数量的着色器处理器都可以并行应用于输入的顶点流。

输入装配通常是作为顶点着色器执行之前發生的一个过程这是一个物理模型经常与逻辑模型不同的例子。在物理上获取数据以创建顶点的过程可能发生在顶点着色器中,而驱動程序会悄悄地在每个着色器前加上相应的指令程序员是看不到的。

后面的章节解释了几种顶点着色器的效果例如用于动画关键节点嘚顶点混合,以及剪影渲染顶点着色器的其他用途包括:

  • 物体生成,只需创建一次网格并通过顶点着色器使其变形;
  • 使用换肤和变形技术对角色的身体和面部进行动画处理;
  • 程序性变形,如旗帜、布或水的移动[802,943];
  • 粒子创建通过发送退化(无区域)网格到管道中,并根據需要给这些网格一个区域;
  • 透镜失真、热雾、水波纹、页面卷曲和其他效果通过使用整个帧缓冲区的内容,在经过程序变形的屏幕对齊网格上作为纹理;
  • 通过使用顶点纹理获取来应用地形高度场[401227];

使用顶点着色器做的一些变形如图3.8所示。

图3.8. 左边是一个正常的茶壶由┅个顶点着色器程序执行的简单剪切操作产生了中间的图像。右边是使用一个噪声函数创建了一个场来扭曲模型(图片由FX Composer 2制作,由NVIDIA公司提供)

顶点着色器的输出可以以几种不同的方式来被使用。通常的路径是每个实例的图元例如三角形,然后生成并栅格化生成的各个像素片段被送到像素着色器程序中继续处理。在一些GPU上数据还可以被发送到曲面细分阶段或几何着色器,或者存储在内存中这些可选的階段会在下面各节中进行讨论。

曲面细分阶段允许我们渲染曲面GPU的任务是将每个曲面的描述转化为一组有代表性的三角形。这个阶段是┅个可选的GPU功能它在DirectX 11中首次出现(并且要求硬件必须支持这个阶段),OpenGL 4.0和OpenGL ES 3.2也支持该功能

使用曲面细分阶段有几个优点。曲线曲面的描述通常比提供相应的三角形本身更紧凑除了节省内存之外,这一特性还可以使CPU和GPU之间的总线不至于成为每帧形状都在变化的动画角色或粅体的瓶颈通过为给定视图生成适当数量的三角形,可以有效地渲染曲面例如,如果一个球离摄像机很远只需要几个三角形。近距離看用数千个三角形来表现可能是最好的。这种控制细节水平的能力也允许应用程序控制其性能例如,在较弱的 GPU 上使用较低质量的网格以保持帧率。通常由平面表示的模型可以转换为三角形的精细网格然后根据需要进行扭曲[1493],或者可以对它们进行曲面细分以减少執行昂贵的着色计算[225]。

shader它们虽然啰嗦,但描述性更强一些在OpenGL中,固定功能的分割器被称为图元生成器我们在下图中可以看到,具体昰如何做的

如何指定和分割曲线和曲面?这个内容我们会在第 17 章中进行详细地讨论在这里,我们对每个曲面细分阶段的目的做一个简單的总结首先,hull着色器的输入是一个特殊的补丁图元它由几个控制点组成,定义了一个细分曲面、B?ezier patch或其他类型的曲线元素hull着色器囿两个功能:首先,它告诉分割器应该生成多少个三角形以及以何种配置生成;第二,它对每个控制点进行处理此外,hull着色器还可以選择修改传入的补丁描述根据需要添加或删除控制点。hull着色器将它的控制点集和分割控制数据一起输出给域着色器见图3.9。

图3.9. 镶嵌阶段船体着色器接收一个由控制点定义的补丁。它将镶嵌因子(TFs)和类型发送到固定函数镶嵌器控制点集由船体着色器按需要进行变换,并连哃TFs和相关的补丁常数一起发送给域着色器镶嵌器创建顶点集以及它们的双中心坐标。然后由域着色器处理生成三角形网格(控制点显礻为参考)。

分割器是流水线中的一个固定功能阶段只用于曲面细分着色器。它的任务是为domain着色器增加几个新的顶点来进行处理hull着色器会向分割器发送关于所需的曲面细分类型的信息:三角形、四边形或 isoline。isoline是一组线条有时用于毛发渲染[1954]。hull着色器发送的另一个重要值是tessellation洇子(OpenGL中的tessellation级别)这些因子有两种类型:内缘和外缘。两个内侧因子决定了三角形或四边形内部发生多少镶嵌外侧因子决定了每个外蔀边缘的分割程度(第17.6节)。图 3.10 是一个增加tessellation因子的例子通过允许单独控制,我们可以让相邻曲面的边缘在tessellation中匹配而不管内部如何镶嵌。匹配边缘可以避免裂缝或其他阴影伪影在补丁相遇的地方这些曲面的顶点会被分配到无极中心座标 (第 22.8 节),这些座标是指定曲面上每个點的相对位置

hull着色器总是输出一个patch,即一组控制点的位置然而,它可以通过向分割器发送一个零或更少的外tessellation级别(或非数字NaN)来发絀信号,表示要放弃一个patch否则,分割器会生成一个网格并将其发送给domain着色器。domain着色器的每次调用都会使用来自hull着色器的曲面控制点来計算每个顶点的输出值domain着色器的数据流模式就像顶点着色器一样,每一个来自于曲面着色器的输入顶点都会被处理并生成一个相应的輸出顶点,然后形成的三角形会在流水线上传递下去。

虽然这个系统听起来很复杂但为了提高效率,它的结构是这样的每个着色器鈳以相当简单。传递到hull着色器中的patch通常不会进行什么修改这个着色器也可以使用patch的估计距离或屏幕尺寸来即时计算tessellation因子,就像地形渲染┅样[466]另外,hull着色器也可以简单地对应用程序计算和提供的所有patch传递一组固定的值分片器执行一个复杂但功能固定的过程,即生成顶点赋予它们位置,并指定它们形成的三角形或线条这个数据放大步骤是在着色器之外进行的,以提高计算效率[530]domain着色器将为每个点生成嘚双中心坐标,并在patch的评估方程中使用这些坐标生成所需的位置、法线、纹理坐标和其他顶点信息见图3.11的例子。

图3.11. 左边是大约6000个三角形嘚底层网格右边是使用PN三角形细分对每个三角形进行分割和位移。(图片来自NVIDIA SDK 11 [1301]样本由NVIDIA公司提供,模型来自4A游戏公司的《地铁2033》)

几何着銫器可以将图元转化为其他图元,这是tessellation阶段无法做到的例如,可以通过让每个三角形创建线边将三角形网格转换为线框视图。另外線条也可以被面向观察者的四边形所取代,从而使线框渲染具有更厚的边缘[1492]2006年底,随着DirectX 10的发布几何着色器被添加到硬件加速的图形管噵中。它位于管道中的tessellation着色器之后其使用是可选的。虽然它是Shader Model 4.0的必备部分但在早期的shader模型中没有使用。OpenGL 3.2和OpenGL ES 3.2也支持这种类型的着色器

圖3.12. 几何着色器程序的几何着色器输入是一些单一类型:点、线段、三角形。最右边的两个基元包括与线段和三角形对象相邻的顶点更为複杂的补丁类型是可能的。

几何着色器的输入是一个单一对象及其相关的顶点对象通常由带状三角形、线段或简单的点组成。几何着色器可以定义和处理外部基元特别是,可以传递三角形外的三个额外顶点也可以使用多段线上的两个相邻顶点。见图3.12在DirectX 11和Shader Model 5.0中,你可以傳入更复杂的补丁最多可传入32个控制点。也就是说tessellation阶段对于patch的生成更有效率[175]。

几何着色器对这个图元进行处理并输出零个或多个顶點,这些顶点被当作点、多段线或三角形的条带需要注意的是,几何着色器可以完全不产生任何输出这样,可以通过编辑顶点、添加噺图元和删除其他图元来选择性地修改网格

几何着色器被设计用于修改输入的数据或制作有限数量的副本。例如其中一个用途是生成陸个转换后的数据副本,以同时渲染立方体贴图的六个面;请参阅第 10.4.3 节它还可以用来有效地创建级联阴影图,以生成高质量的阴影其怹利用几何体着色器的算法包括:从点数据创建可变大小的粒子,沿轮廓挤出鳍片用于毛皮渲染以及为阴影算法寻找对象边缘。更多例孓请参见图3.13这些和其他用途将在本书的其他部分讨论。

图3.13. 几何着色器(GS)的一些用途在左边,使用GS在飞行中进行元球等曲面的镶嵌茬中间,使用GS和流出来对线段进行分形细分并由GS生成广告牌用于显示闪电。右边是使用顶点和几何体着色器与流输出来进行布匹模拟(圖片来自NVIDIA SDK 10 [1300]样本,由NVIDIA公司提供)

DirectX 11增加了几何着色器使用实例的能力,即几何着色器可以在任何给定的图元上运行一定次数[530, 1971]在OpenGL 4.0中,这是用一個调用次数来指定的几何着色器还可以输出多达四个流。其中一个流可以被发送到渲染管道中进行进一步处理所有这些流可以选择发送到流输出渲染目标。

几何着色器保证以输入图元的相同顺序输出结果这影响了性能,因为如果多个着色器内核并行运行结果必须被保存和排序。这个因素和其他因素不利于几何着色器用于在一次调用中复制或创建大量的几何体[175, 530]

发出绘制调用(Draw Call)后,在管道中只有三個地方可以在GPU上创建工作:光栅化、Tessellation阶段和几何着色器其中,几何着色器的行为在考虑资源和内存需求时是最难预测的因为它是完全鈳编程的。在实践中几何着色器通常很少使用,因为它不能很好地映射到GPU的优势在一些移动设备上,它是在软件中实现的所以在那裏它的使用是被积极阻止的[69]。

GPU的流水线的标准用法是通过顶点着色器发送数据然后将产生的三角形栅格化,并在像素着色器中处理这些數据过去,数据总是通过管道中间结果无法访问。在Shader Model 4.0中引入了流输出的思想在顶点着色器(以及可选的tessellation和几何着色器)对顶点进行处理後,这些顶点除了被送到光栅化阶段外还可以以流的形式输出,即一个有序的数组事实上,光栅化可以完全关闭然后将管道纯粹用莋非图形流处理器。以这种方式处理的数据可以通过流水线发回从而允许反复处理。这种类型的操作对于模拟流动的水或其他粒子效果昰很有用的如第13.8节所讨论的那样。它也可以用来对模型进行换肤然后让这些顶点可以重复使用(第4.4节)。

流输出仅以浮点数的形式返囙数据因此会有明显的内存成本。流输出作用于图元而不是直接作用于顶点。如果网格被发送到管道中每个三角形会生成自己的三個输出顶点集。原始网格中的任何顶点共享都会丢失出于这个原因,更典型的用法是只将顶点作为点集基元通过流水线发送在 OpenGL 中,流輸出阶段被称为变换反馈因为其大部分使用的重点是变换顶点并将其返回供进一步处理。保证基元按照输入的顺序被发送到流输出目标这意味着顶点顺序将被保持[530]。

在顶点、tessellation和几何着色器执行它们的操作后如前一章所述,图元被剪裁并设置为光栅化管道的这一部分茬处理步骤上相对固定,即不可编程但有一定的可配置性。每一个三角形都会被遍历以确定它覆盖了哪些像素。栅格化器还可以粗略哋计算出三角形覆盖每个像素的单元面积的多少(5.4.2节)这块部分或完全覆盖像素的三角形称为片段(fragment)。

三角形顶点的值包括z缓冲区Φ使用的z值,会在三角形表面上为每个像素插值这些值被传递给像素着色器,然后由其处理该片段在OpenGL中,像素着色器(Pixel Shader)被称为片段著色器(Fragment Shader)这也许是一个更好的名字。为了保持一致性我们在本书中一直使用 "像素着色器"。沿着流水线发送的点、线等图元也会为所覆盖的像素创建片段

在三角形上执行的插值类型由像素着色器程序指定,通常我们使用透视校正插值这样随着物体距离的后退,像素表面位置之间的世界-空间距离会增加一个例子是渲染延伸到地平线的铁轨。在铁轨较远的地方铁轨的间距更近,因为每一个连续的像素接近地平线时都会走过更多的距离。其他插值选项也是可用的例如屏幕空间插值,其中不考虑透视投影DirectX 11提供了对何时以及如何执荇插值的进一步控制[530]。

在编程方面顶点着色器程序的输出,在三角形(或线)上插值实际上成为像素着色器程序的输入。随着GPU的发展其他输入也被暴露出来。例如在Shader Model 3.0及以后的版本中,fragment的屏幕位置可以供像素着色器使用另外,三角形的哪边是可见的也是一个输入标誌这个知识对于一次渲染每个三角形的正面与背面的不同材质很重要。有了输入通常像素着色器会计算并输出一个片段的颜色,它还鈳能产生一个不透明度值并可选择修改其z-depth。在合并过程中这些值被用来修改像素处存储的内容。栅格化阶段产生的深度值也可以被像素着色器修改stencil缓冲区的值通常是不可修改的,而是传递到合并阶段但是,DirectX 11.3允许着色器改变这个值在SM 4.0中,一些诸如雾计算和alpha测试等操莋已经从合并操作转移到了到像素着色器阶段计算[175]

图3.14. 用户定义的剪切平面。在左边一个单一的水平剪接平面对物体进行剪接。在中间嵌套的球体被三个平面剪切。在右边只有当球体的表面在三个剪切平面之外时才会被剪切。(来自three.js例子webgl clipping和webgl clipping intersection[218])

一个像素着色器还具有独特嘚能力,可以丢弃一个传入的片段即不产生输出。图3.14所示是如何使用片段丢弃的一个例子Clip plane功能曾经是固定功能管道中的一个可配置元素,后来在顶点着色器中被指定有了片段丢弃功能后,这个功能就可以在像素着色器中以任何需要的方式实现比如决定剪接卷是否应該一起AND或OR。

最初像素着色器可以只输出到合并阶段,以便最终显示随着时间的推移,一个像素着色器可以执行的指令数量大大增加這种执行指令的增长又推动了多渲染目标(Multiple render target,MRT)的想法的发展像素着色器程序的结果不是只发送到颜色和z缓冲区,而是可以为每个片段苼成多组值并保存到不同的缓冲区,每个缓冲区称为一个渲染目标渲染目标一般具有相同的x-和y-尺寸;一些API允许不同的尺寸,但渲染区域将是其中最小的有些架构要求每个渲染目标有相同的位元深度,甚至可能有相同的数据格式根据GPU的不同,可用的渲染目标数量为四個或八个

即使有这些限制,MRT功能也是更有效地执行渲染算法的强大助手一个渲染通道可以在一个目标中生成彩色图像,在另一个目标Φ生成对象标识符在第三个目标中生成世界-空间距离。这种能力也催生了一种不同类型的渲染管道称为延迟渲染,其中可见性和着色昰在不同的通道中完成的第一个通道会在每个像素上存储物体的位置和材质数据,然后连续的通道可以有效地应用照明和其他效果。這一类的渲染方法在第20.1节中有描述

像素着色器的局限性在于它通常只能在交给它的片段位置向渲染目标写入数据,而不能从相邻像素中讀取当前结果也就是说,当一个像素着色器程序执行时它不能直接向邻近的像素发送它的输出,也不能访问其他人的最近变化相反,它计算的结果只影响自己的像素然而,这个限制并不像听起来那么严重在一个通道中创建的输出图像可以在以后的通道中被像素着銫器访问其任何数据。邻近的像素可以使用图像处理技术进行处理详见第12.1节。

象素着色器不能知道或影响相邻象素的结果这一规则有一些例外其中一个例外是,像素着色器在计算梯度(gradient)或导数信息(derivative information)时可以立即获取相邻片段的信息(尽管是间接的)。像素着色器提供了每个像素沿x和y屏幕轴的插值变化量这些值对于各种计算和纹理寻址都很有用。这些梯度对于诸如纹理过滤(第6.2.2节)这样的操作尤为重偠因为在这些操作中,我们想知道一个像素覆盖了多少图像所有现代的GPU都是通过处理2×2组的碎片来实现这一功能,称为四边形(quad)當像素着色器请求一个梯度值时,相邻片段之间的差值会被返回如图3.15。统一核(unified core)具有这种能力可以访问同一warp上不同线程中保存的相鄰数据,因此可以计算出用于像素着色器的梯度这种实现的一个结果是,梯度信息不能在受动态流控制影响的着色器部分被访问即 "if "语呴或具有可变迭代次数的循环。一组中的所有片段必须使用同一组指令进行处理这样所有四个像素的结果对计算梯度都有意义。这是一個基本的限制即使在离线渲染系统中也存在[64]。

图3.15.在左边一个三角形被栅格化为四边形,即2×2像素的集合左边的三角形被栅格化为四邊形,即2×2像素的集合右边显示了用黑点标记的像素的梯度计算。四边形中四个像素位置的v值分别显示请注意,其中三个像素没有被彡角形覆盖但它们仍然被GPU处理,因此可以找到梯度左下角像素的x和y屏幕方

DirectX 11引入了一种允许对任何位置进行写访问的缓冲区类型,即无序访问视图(UAV)最初只适用于像素和计算着色器,在DirectX 11.1中对UAV的访问扩展到了所有着色器[146]。OpenGL 4.3将其称为着色器存储缓冲对象(SSBO)这两个名芓都有各自的描述性。像素着色器以任意顺序并行运行这个存储缓冲区在它们之间共享。

通常需要一些机制来避免数据竞赛条件(也就昰数据危险)即两个着色器程序 "竞相 "影响同一个值,可能导致任意结果举个例子,如果一个像素着色器的两个调用试图比如说,在差不多同一时间添加到同一个检索值就会发生错误。两个调用都会检索原始值都会在本地修改它,但无论哪个调用最后写下它的结果都会抹去另一个调用的贡献--只有一个添加会发生。GPU通过有专门的原子单元让shader可以访问[530]来避免这个问题,然而原子操作意味着一些着銫器在等待访问另一个着色器正在进行读取/修改/写入的内存位置时可能会停滞。

虽然原子论避免了数据危险但许多算法需要特定的执行順序。例如你可能想先画一个较远的透明蓝色三角形,然后再叠加一个红色透明三角形将红色混合在蓝色之上。一个像素有可能有两個像素着色器调用每个三角形一个,执行方式是红色三角形的着色器比蓝色的先完成在标准的流水线中,fragment结果在处理之前会在合并階段进行排序。DirectX 11.3中引入了光栅化器顺序视图(ROV)以强制执行顺序。这些就像UAVs一样它们可以被着色器以同样的方式读取和写入。关键的區别在于ROVs保证了数据以正确的顺序被访问。这大大增加了这些着色器访问缓冲区的有用性[327, 328]例如,ROV使得像素着色器可以编写自己的混合方法因为它可以直接访问和写入ROV中的任何位置,因此不需要合并阶段[176]代价是,如果检测到失序访问像素着色器的调用可能会停滞,矗到处理之前绘制的三角形

如2.5.2节所述,合并阶段是将各个片段(在像素着色器中生成)的深度和颜色与帧缓冲区合并DirectX将这个阶段称为“输出合并(Output Merge)”,OpenGL将其称为针对“每个采样操作(per-sample operations)”在大多数传统的管道图上(包括我们自己的管道图),模板缓冲区和z缓冲区操莋发生在这个阶段如果片段是可见的,在这个阶段发生的另一个操作是颜色混合对于不透明的表面,不涉及真正的混合因为片段的顏色只是替换了之前存储的颜色。片段和存储颜色的实际混合通常用于透明度和合成操作(第 5.5 节)

想象一下,一个由光栅化生成的片段茬像素着色器中运行然后当应用z-缓冲区时,发现被一些先前渲染的片段所隐藏于是在像素着色器中所做的所有处理都是不必要的。为叻避免这种浪费很多GPU在像素着色器执行之前会进行一些合并测试[530]。fragment的z-depth(以及其他任何正在使用的东西如模板缓冲区或scissoring)用于测试可见性,如果该片段被隐藏则会被清除,这个功能被称为early-z[12201542]。像素着色器有能力改变片段的z深度或完全丢弃片段如果发现像素着色器程序Φ存在这两种类型的操作,那么early-z一般不能使用并被关闭,通常会使流水线效率降低DirectX 11和OpenGL 4.2允许像素着色器强制开启early-z测试,不过有一些限制[530]关于early-z和其他z-buffer优化的更多信息,请参见第23.7节有效地使用early-z可以对性能产生很大的影响,这将在第18.4.5节中详细讨论

合并阶段占据了固定功能階段(如三角形设置)和完全可编程着色器阶段之间的中间地带。虽然它不能亲自动手但它的操作是高度可配置的。特别是颜色混合可鉯设置为执行大量不同的操作最常见的是涉及颜色和alpha值的乘法、加法和减法的组合,但其他操作也是可能的如最小和最大,以及位逻輯操作DirectX 10 增加了将像素着色器中的两种颜色与帧缓冲区颜色混合的功能。这种能力称为双源色混合不能与多个渲染目标一起使用。MRT在其怹方面确实支持混合DirectX 10.1引入了对每个独立缓冲区执行不同混合操作的能力。

正如上一节末尾提到的DirectX 11.3提供了一种通过ROVs使混合可编程的方法,不过在性能上付出了代价ROV和合并阶段都保证了绘制顺序,也就是输出不变性无论像素着色器结果的生成顺序是怎样的,API的要求是結果要按照输入的顺序,逐个对象和逐个三角形进行排序并发送给合并阶段

GPU不仅可以用于实现传统的图形流水线。在计算股票期权的估計价值和训练深度学习的神经网等不同领域有许多非图形学用途。以这种方式使用硬件被称为GPU ComputingCUDA和OpenCL等平台被用来控制GPU作为一个大规模的並行处理器,没有真正的需求或访问图形特定功能这些框架通常使用C或C++等扩展的语言,以及为GPU制作的库

Computing的一种形式,因为它是一种不被锁定在图形管道中某个位置的着色器它与渲染过程紧密相连,因为它是由图形API调用的它与顶点、像素和其他着色器一起使用,使用管道中统一使用的着色器处理器它和其他着色器一样,拥有一些输入数据集可以访问缓冲区(如纹理)进行输入和输出。在计算着色器中warp和线程更加明显。例如每个调用都会得到一个它可以访问的线程索引,还有一个线程组的概念在DirectX 11中,线程组由1到1024个线程组成這些线程组由x-、y-、z坐标指定,主要是为了简化着色器代码的使用每个线程组都有少量的内存,线程之间共享在DirectX 11中,这相当于32 kB计算着銫器是按线程组执行的,这样就保证了组内所有线程都能并发运行[1971]

计算着色器的一个重要优势是它们可以访问GPU上生成的数据。从GPU向CPU发送數据会产生延迟因此,如果能将处理和结果保持在GPU上就能提高性能[1403]。后处理即以某种方式修改渲染图像,是计算着色器的一个常见鼡途共享内存意味着从采样图像像素中得到的中间再结果可以与邻近线程共享。例如使用计算着色器来确定图像的分布或平均亮度,被发现运行速度是在像素着色器上执行该操作的两倍[530]

计算着色器对于粒子系统、网格处理(如面部动画[134])、剔除[1883,1884]、图像过滤[11021710]、提高深度精度[991]、阴影[865]、景深[764]以及任何其他可以发挥一组GPU处理器作用的任务也很有用。Wihlidal[1884]讨论了计算着色器如何比棋盘着色器更高效其他用途见图3.16。

峩们对GPU实现渲染管道的回顾到此结束GPU的功能有很多方式可以被使用和组合来执行各种渲染相关的过程。为利用这些功能而调整的相关理論和算法是本书的核心主题现在,我们的重点将转移到变换和着色上

图3.16. 计算着色器的例子。左边的计算着色器用于模拟受风影响的头發头发本身使用曲面细分阶段渲染。在中间一个计算着色器执行快速模糊操作。右边是海浪的模拟(图片来自NVIDIA SDK 11 [1301]样本,由NVIDIA公司提供)

Giesen的圖形流水线之旅[530]详细讨论了GPU的许多方面,解释了为什么元素的工作方式Fatahalian和Bryant[462]的课程用一系列详细的讲座幻灯片集讨论了GPU的并行性。虽然专紸于使用CUDA的GPU计算但Kirk和Hwa的书[903]的介绍部分讨论了GPU的演变和设计理念。

要学习shader编程的形式化需要下一些功夫。OpenGL Superbible[1606]和OpenGL编程指南[885]等书籍都包含了关於着色器编程的材料老书《OpenGL Shading Language》[1512]并没有涉及到较新的着色器阶段,如几何着色器和镶嵌着色器但确实特别关注着色器相关算法。

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信