RTR4 第三章 图形处理单元 3.7-3.10
3.7 几何着色器几何着色器可以将图元转换为其他图元,而细分阶段无法做到。例如,可以通过让每个三角形创建线形边界,将三角形网格转换为线框视图。或者,可以将这些线替换为面向观察者的四边形,从而使线框渲染的边缘更厚。几何着色器是在2006年末发布的DirectX 10中添加到硬件加速图形管线的。它作为一个可选使用项位于管线中的细分着色器之后。与此同时它是Shader Model 4.0的一部分,在较早的着色器模型中未使用它。 OpenGL 3.2和OpenGLES 3.2也支持这种类型的着色器。
几何着色器的输入是单个对象及其关联的顶点。对象通常由三角形带(triangles in a strip),线段或简单点中组成。扩展的图元可以由几何着色器定义和处理。特别是,可以传入三角形以外的三个附加顶点,并且可以使用折线(polyline)上的两个相邻顶点。见图3.12。使用DirectX 11和ShaderModel 5.0,您可以传递多达32个控制点的更加精细的面片(patch)。那就是说,细分阶段对于面片生成的影响更大。
几何着色器处理此图元并输出零个或多个顶点,这些顶点被视为点,折线或三角形Strip。请注意,几何着色器根本不会生成任何输出。通过这种方式,可以通过编辑顶点,添加新图元并删除其他图元来选择性地修改网格。
几何着色器被设计用于修改传入的数据或制作有限数量的副本。例如,一种用途是生成六个转换后的数据副本,以同时渲染立方体贴图的六个面。参见第10.4.3节。它还可以用来有效地创建级联的阴影贴图,以生成高质量的阴影。其他利用几何着色器的算法包括从点数据创建可变大小的粒子,沿着轮廓拉伸鳍状物(fins)以进行毛发渲染以及为阴影算法寻找对象边缘。有关更多示例,请参见图3.13。在本书的其余部分中将讨论这些和其他用途。
图3.13 几何着色器(GS)的某些用途。左图使用GS快速执行元球(metaball)等值面细分。中间图使用GS进行线段的分形细分并将其涌出,并且GS生成广告牌以显示雷电。右图是通过使用顶点和几何着色器进行流输出来进行布料模拟的。(来自NVIDIA SDK 10 示例的图像,由NVIDIA Corporation提供)
DirectX 11为几何着色器增加了使用实例化的功能,其中几何着色器可以在任何给定的图元上运行设定的次数。这个设置在OpenGL 4.0中通过调用计数来指定。几何着色器也可以输出多达四个流(streams)。可以在渲染管线上发送一个流以进行进一步处理。所有这些流都可以选择被发送到流输出渲染目标。
几何着色器会以与输入相同的顺序输出来自图元结果。这会影响性能,因为如果几个着色器核心并行运行,则必须保存并排序结果。此因素和其他因素不利于在一次调用中被用于复制或创建大量几何图形的几何着色器
在一个绘制(draw call)被调用后,管线中只有三个位置可以在GPU上创建工作:栅格化,细分阶段和几何着色器,其中考虑资源和内存需求时,几何着色器的行为是最难以预测的,因为它是完全可编程的。实际上,几何着色器通常使用很少,因为它不能很好地反映出GPU的优势。在某些移动设备上,它是通过软件实现的,因此强烈建议不要使用它。
3.7.1 流输出
GPU管线的标准用法是通过顶点着色器发送数据,然后把生成的三角形光栅化并在像素着色器中对其进行处理。在过去总是通过管线传递数据,而中间结果却无法访问。在Shader Model 4.0中引入了流输出的概念。在顶点着色器(以及可选的细分和几何着色器)处理了顶点之后,除了可以发送到光栅化阶段外,还可以将它们输出到流(即有序数组)中。实际上,光栅化可以完全关闭,然后将管线纯粹用作非图形流处理器。这种数据处理可以通过管线发回,从而允许迭代处理。如第13.8节所述,这种类型的操作可用于模拟流水或其他粒子效应。它也可以用于为模型蒙皮,然后可以重新使用这些顶点(第4.4节)。
流输出仅以浮点数的形式返回数据,因此可能会有明显的内存开销。流输出适用于图元,而不是直接在顶点上。如果沿管线发送网格,则每个三角形将生成自己的三个输出顶点集。原始网格中共享的所有顶点都将丢失。为此,一个更典型的用法是仅通过管线将顶点作为点集基元发送。在OpenGL中,流输出阶段称为转换反馈(transform feedback),因为它的主要用途是转换顶点并将其返回以进行进一步处理。保证按输入顺序将基元发送到流输出目标,这意味着将保持顶点顺序。
3.8 像素着色器
如上一章所述,在顶点着色器,细分着色器和几何着色器执行完操作后,将裁剪并设置原始对象以进行栅格化。管线的这一部分在其处理步骤中相对固定,即不可编程的,但是有些部分是可配置的。每个三角形都将被遍历以确定其覆盖哪些像素。光栅化器还可以粗略地计算出三角形覆盖每个像素的像元(cell)区域的数量(第5.4.2节)。这部分三角形或完全重叠像素的这个三角形称为一个片元(fragment)。
三角形表面上的每个像素将对三角形顶点的值(包括z缓冲区中使用的z值)进行插值。这些值将传递到像素着色器(pixel shader),然后再处理片元。在OpenGL中,像素着色器称为片元着色器(fragment shader),这也许是一个更好的名称。为了保证一致性,我们在本书中始终使用“像素着色器”。沿管线发送的点和线图元也会为覆盖的像素创建片元。
跨三角形执行的插值类型由像素着色器程序指定。通常,我们使用透视校正内插法,以便世界空间下像素表面位置之间的距离随着对象距离的减少而增加。一个示例是渲染延伸到地平线的铁轨。铁路在更远的地方间隔更近,因为每两个连续的像素越接近地平线都需要越多的距离。也有其他可用插值选项,例如屏幕空间插值,其中不考虑透视投影。 DirectX 11对执行插补的时间和方式提供了进一步的控制。
用编程术语来说,顶点着色器程序的输出(在三角形(或线)上插值)实际上成为像素着色器程序的输入。随着GPU的发展,其他输入也已公开。例如,片元的屏幕位置可用于Shader Model 3.0及更高版本中的像素着色器。与此同时,三角形的哪一侧是可见的成为一个输入标志。这一认识对于在单Pass内执行在每个三角形的正面和背面渲染不同的材质非常重要。
有了输入,通常像素着色器会计算并输出片元的颜色。它还可能会产生不透明度值,并可以选择修改其z深度。在合并期间,这些值将用于修改已存储在像素处的值。栅格化阶段生成的深度值也可以通过像素着色器进行修改。模板缓冲区值通常是不可修改的,而是传递到合并阶段。 DirectX 11.3允许着色器更改此值。雾计算和Alpha测试等操作已从合并操作转变为ShaderModel 4.0中的像素着色器计算。
像素着色器还具有丢弃传入片元(即不产生任何输出)的独特功能。图3.14显示了如何使用片元丢弃(fragment discard)的一个示例。裁切平面功能以前是固定功能管线中的可配置元素,后来在顶点着色器中指定。有了片元丢弃功能之后,就可以在像素着色器以任何方式需要时来实现此功能,例如确定剪切量应“与”还是“或”在一起。
图3.14 用户定义的剪切平面。左图,单个水平裁剪平面将对象切片。中间图,嵌套球被三个平面修剪。右图,仅当球体的表面在所有三个剪切平面之外时才被剪切。(来自three.js示例webglclipping和webglclippingintersection 。)
最初,像素着色器只能输出到合并阶段,以进行最终显示。随着时间的流逝,像素着色器可以执行的指令数量大大增加。这种增加产生了多个渲染目标(multiple render targets, 简称MRT)的想法。除了将像素着色器程序的结果仅发送到颜色和z缓冲区外,还可以为每个片元生成多个值集并将其保存到不同的缓冲区中,每个缓冲区称为渲染目标(render target)。渲染目标通常具有相同的x和y维度;某些API允许使用不同的大小,但渲染区域将是这两者中最小的。某些体系结构要求渲染目标各自具有相同的位深,甚至可能具有相同的数据格式。取决于GPU,可用的渲染目标数是四个或八个。
即使有这些限制,MRT功能仍然是对于执行渲染算法的更有效的强大的辅助工具。一个单渲染通道可以在一个目标中生成彩色图像,在另一个目标中生成对象标识符,在第三个目标中生成世界空间距离,这种能力还产生了另一种类型的渲染管线,称为延迟着色(deferred shading),其中可见性和着色在各自的通道中完成。第一个Pass存储每个像素上对象的位置和材质。然后,连续的Pass可以有效地施加照明和其他效果。此类渲染方法在第20.1节中进行了描述。
像素着色器的局限性在于,它通常只能在传递给目标的片元位置上写入渲染目标,而无法读取相邻像素的当前结果。也就是说,执行像素着色器程序时,它无法将其输出直接发送到相邻像素,也无法访问其他人的最新更改。而是计算仅影响其自身像素的结果。但是,这个限制听起来并不严格。第一个Pass创建的输出图像可以在以后的Pass中由像素着色器访问其任何数据。可以使用第12.1节中所述的图像处理技术来处理相邻像素。
像素着色器无法知道或影响相邻像素的结果的规则是有例外的。一种是像素着色器可以在梯度或微分信息的计算过程中立即访问相邻片元的信息(尽管是间接的)。像素着色器提供了每个像素的沿屏幕x和y轴的任何插值的变化量。这样的值可用于各种计算和纹理寻址。这些渐变对于诸如纹理过滤(第6.2.2节)之类的操作尤其重要,在该操作中,我们想知道多少图像覆盖一个像素。所有现代GPU都通过以2×2为一组处理片元(称为quad)来实现此功能。当像素着色器请求渐变值时,将返回相邻片元之间的差异。见图3.15。统一内核具有访问相邻数据(在同一warp上具有的不同线程)的功能(这里应该指的是前文N卡的warps),因此可以计算用于像素着色器的渐变。此实现的一个结果是,无法在受动态流控制(dynamic flow control)影响的着色器的某些部分(即具有可变迭代次数的“ if”状态或循环)中访问渐变信息。一个组中的所有片元都必须使用相同的指令集进行处理,以便所有四个像素的结果对于计算渐变都有意义。这是一个基本限制,即使在脱机渲染系统中也存在。
图3.15 左图三角形被栅格化为四边形,每组2×2像素。然后,在右侧显示带有黑点的像素的梯度计算。v值代表的是一个四边形中四个像素各自的位置。请注意,三个像素是如何没有被三角形覆盖,却仍能被GPU对其进行处理,以便可以找到渐变。通过使用左下像素的两个邻域,可以计算出屏幕xy方向上的梯度
DirectX 11引入了一种缓冲区类型,该类型允许对任何位置的写入访问,即无序访问视图(unordered access view, UAV)。最初仅用于像素着色器和计算着色器(compute shader),在DirectX 11.1中对UAV的访问已扩展到的所有着色器。 OpenGL 4.3称之为一个着色存储缓冲区对象(shader storage buffer object, SSBO)。这两个名称都是用它们自己的方式描述的。像素着色器以任意顺序并行运行,并且此存储缓冲区在它们之间共享。
通常需要某种机制来避免数据竞争(data race condition)情况(又称为一种数据危害(data hazard)),因为两个着色器程序都在“竞相”以影响相同的值,可能导致随机(arbitrary)结果。例如,如果像素着色器的两次调用试图在大约同一时间添加到相同的检索值,则可能会发生错误。两者都将检索原始值,都将在本地对其进行修改,但是无论哪一个调用最后写入其结果都将抹去另一个调用的作用,最终只会发生一次添加。 GPU通过专用于着色器可以访问的原子单元(atomic units)来避免此问题。但是,原子意味着某些着色器可能在等待访问另一个着色器正在进行读取/修改/写入操作的存储位置时停滞。
尽管原子避免了数据危害,但是许多算法都需要特定的执行顺序。例如,您可能需要绘制更远的半透明蓝色三角形,然后再用红色半透明三角形覆盖它,将红色混合在蓝色上。一个像素可能对一个像素进行两次像素着色器调用,每个三角形执行一次,以使红色三角形的着色器在蓝色之前完成。在标准管线中,片元结果在处理之前在合并阶段进行排序。在DirectX11.3中引入了光栅化程序顺序视图(Rasterizer order views, ROVs)以强制执行顺序。它们就像UAV,着色器可以以相同的方式读取和写入它们。主要区别在于ROV保证以正确的顺序访问数据。这极大地增加了这些着色器可访问缓冲区的有用性。例如,ROV使得像素着色器可以编写自己的混合方法,因为它可以直接访问和写入ROV中的任何位置,因此不需要合并阶段。代价是,如果检测到乱序访问,像素着色器调用可能会拖延直到较早绘制的三角形处理完毕。
3.9 合并阶段
如第2.5.2节所述,合并阶段是将各个片元(在像素着色器中生成)的深度和颜色与帧缓冲区组合在一起的阶段。 DirectX将此阶段称为输出合并; OpenGL将其称为“逐采样操作”。在大多数传统管线图(包括我们自己的管线图)上,此阶段是进行模版缓冲区和z缓冲区操作的地方。如果片元可见,则此阶段中发生的另一种操作是颜色混合。对于不透明的表面,不涉及真正的混合,因为片元的颜色只是替换了先前存储的颜色。片元和所存储颜色的实际混合通常用于透明度和合成操作(第5.5节)。
想象一下,当应用z缓冲区时,光栅化生成的片元经过像素着色器,然后被某些先前渲染的片元片元隐藏。所有在像素着色器中完成的处理变得完全没有必要。为避免这种浪费,许多GPU在执行像素着色器之前先进行一些合并测试。片元的z深度(以及其他正在使用的东西,例如模板缓冲区或剪切(scissoring))用于测试可见性。如果片元被隐藏则会被清除。此功能称为early-z 。像素着色器具有更改片元的z深度或完全丢弃片元的能力。如果在像素着色器程序中发现任何一种类型的操作,则early-z无法被使用并将被关闭,通常会使管线效率降低。DirectX 11和OpenGL 4.2允许像素着色器强制进行early-z测试,尽管有很多限制。有关early-z和其他z缓冲区优化的更多信息,请参见第23.7节。有效地early-z使用会对性能产生很大影响,有关详细信息,请参见第18.4.5节。
合并阶段占据了固定功能阶段(例如三角形设置)和完全可编程的着色器阶段之间的中间地带。尽管它不是可编程的,但是它的操作是高度可配置的。可以特别设置颜色混合以执行大量不同的操作。最常见的是涉及颜色和Alpha值的乘法,加法和减法的组合,但是其他操作也是可能的,例如最小值和最大值以及按位逻辑运算。 DirectX 10添加了将像素着色器的两种颜色与帧缓冲区颜色混合的功能。此功能称为“双源混色”(Dual source-color blending),不能与MRT一起使用。 MRT确实支持混合,DirectX 10.1引入了在每个单独的缓冲区上执行不同混合操作的功能。
如上一节末尾所述,DirectX 11.3提供了一种通过ROV进行混合编程的方法,尽管这是以性能为代价的。ROV和合并阶段都保证了绘制顺序,也就是输出不变性。不管生成像素着色器结果的顺序如何,API的要求都是按输入顺序,逐对象和逐三角形地对结果进行排序并发送到合并阶段。
3.10 计算着色器(Compute shader)
GPU不仅可以用于实现传统的图形管线,还可以用于许多领域的非图形用途,例如计算股票期权的估计价值和训练用于深度学习的神经网络。以这种方式使用硬件称为GPU计算。诸如CUDA和OpenCL之类的平台被用于控制GPU作为大型并行处理器,无需实际需求或访问特定图形的功能。这些框架通常使用带有扩展功能的语言(例如C或C ++)以及为GPU制作的库。
在DirectX 11中引入了计算着色器,它是GPU计算的一种形式,因为它是未锁定在图形管线中某个位置的着色器。它由图形API调用,与渲染过程紧密相关。它与顶点,像素和其他着色器一起使用。它使用与管线中使用的相同的统一着色器处理器池。它与其他着色器一样,具有一组输入数据,并且可以访问缓冲区(例如纹理)以进行输入和输出。在计算着色器中,warps和线程(的作用)更明显(Warps and threads are more visible in a compute shader,这里应该指的是前文N卡的warps功能)。例如,每次调用都会获取一个可以访问的线程索引。还有一个线程组的概念,它由DirectX 11中的1到1024个线程组成。这些线程组由x,y和z坐标指定,主要是为了简化在着色器代码中的使用。每个线程组都有少量线程之间共享的内存。在DirectX 11中,共享内存为32 kB。计算着色器由线程组执行,因此保证该组中的所有线程可以同时运行。
计算着色器的一个重要优点是它们可以访问GPU上生成的数据。从GPU向CPU发送数据会产生延迟,因此如果可以将处理和结果保留在GPU上,则可以提高性能。在后期处理中,以某种方式修改了渲染图像,这是计算着色器的常见用法。共享内存意味着来自采样图像像素的中间结果可以与相邻线程共享。例如,使用计算着色器确定图像的分布或平均亮度,发现其运行速度是对像素着色器执行此操作的两倍
计算着色器还可用于粒子系统,网格处理(例如面部动画,剔除,图像过滤,提高深度精度,阴影,景深) ,以及可以使用一组GPU处理器的任何其他任务。 Wihlidal 讨论了计算着色器如何比曲面细分的着色器更高效。其他用途请参见图3.16。
图3.16 计算着色器示例。左图是计算着色器用于模拟受风影响的头发,并使用细分阶段渲染头发本身。中间图,计算着色器执行快速模糊操作。右图则模拟了海浪。(来自NVIDIA SDK 11 示例的图像,由NVIDIA Corporation提供。)
至此,我们对GPU的渲染管线实现的回顾结束了。有多种方法可以使用和组合GPU功能来执行各种与渲染相关的流程。调整为可利用这些功能的相关理论和算法是本书的重点。现在,我们将重点转移到变换和着色上。
进一步阅读和资源
吉森(Giesen)的图形管线之旅详尽地讨论了GPU的许多方面,并解释了元素为何以那样的方式工作。 Fatahalian和Bryant 的课程在一系列详细的PPT集中讨论了GPU并行性。在着眼于使用CUDA进行GPU计算时,Kirk 和Hwa的书的介绍部分讨论了GPU的演变和设计理念。要了解着色器编程的形式方面,需要一些努力。诸如《OpenGL Superbible 》和《 OpenGL编程指南》 之类的书都包含有关着色器编程的材料。较老的书籍《 OpenGL着色语言》 没有涵盖较新的着色器阶段,例如几何和细分着色器,但确实专注于与着色器相关的算法。有关最新和推荐的图书,请参见本书的网站
Rideout, Philip, “Silhouette Extraction,”The Little Grasshopperblog, Oct. 24, 2010.Citedon p. 47, 668
Blythe, David, “The Direct3D 10 System,”ACM Transactions on Graphics, vol. 25, no. 3,pp. 724–734, July 2006.Cited on p. 29, 39, 42, 47, 48, 50, 249
Giesen,Fabian,“ATripthroughtheGraphicsPipeline2011,”The ryg blog,July9,2011.Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684,701, 784, 1040
Zink, Jason, Matt Pettineo, and Jack Hoxley,Practical Rendering & Computation with Di-rect3D 11,CRCPress,2011.Cited on p. 47, 54, 90, 518, 519, 520, 568, 795, 813, 814,914
ARMLimited,“ARMRMaliTMApplicationDeveloperBestPractices,Version1.0,”ARMdocumentation, Feb. 27, 2017.Cited on p. 48, 798, 1029
Bilodeau, Bill, “Vertex Shader Tricks:New Ways to Use the Vertex Shader to Improve Per-formance,”Game Developers Conference,Mar.2014.Cited on p. 51, 87, 514, 568, 571,798
Davies,Leigh,“OITtoVolumetricShadowMapping,101UsesforRaster-OrderedViewsUsing DirectX 12,”Intel Developer Zoneblog, Mar. 5, 2015.Cited on p. 52, 139, 156
Davies, Leigh, “Rasterizer Order Views 101:A Primer,”Intel Developer Zoneblog, Aug. 5,2015.Cited on p. 52, 156
Bookout, David, “Programmable Blend with Pixel Shader Ordering,”Intel Developer Zoneblog, Oct. 13, 2015.Cited on p. 52
Mitchell,JasonL.,andPedroV.Sander,“ApplicationsofExplicitEarly-ZCulling,”SIG-GRAPH Real-Time Shading course, Aug. 2004.Cited on p. 53, 1016
Sander,Pedro V.,Natalya Tatarchuk,and Jason L. Mitchell,“Explicit Early-Z Culling forEfficientFluidFlowSimulation,”inWolfgangEngel,ed.,ShaderX5,CharlesRiverMedia,pp. 553–564, 2006.Cited on p. 53, 1016
Zink, Jason, Matt Pettineo, and Jack Hoxley,Practical Rendering & Computation with Di-rect3D 11,CRCPress,2011.Cited on p. 47, 54, 90, 518, 519, 520, 568, 795, 813, 814,914
Pettineo, Matt, “A Sampling of Shadow Techniques,”The Danger Zoneblog, Sept. 10, 2013.Cited on p. 54, 238, 245, 250, 265
Bentley, Adrian, “inFAMOUS Second SonEngine Postmortem,”Game Developers Confer-ence, Mar. 2014.Cited on p. 54, 490, 871, 884, 904
Wihlidal,Graham,“OptimizingtheGraphicsPipelinewithCompute,”Game DevelopersConference, Mar. 2016.Cited on p. 54, 798, 834, 837, 840, 848, 849, 851, 908, 986
Wihlidal,Graham,“Optimizig the Graphics Pipeline with Compute,”in Wolfgang Engel,ed.,GPU Zen, Black Cat Publishing, pp. 277–320, 2017.Cited on p. 54, 702, 784, 798, 812,834, 837, 840, 848, 850, 851, 908, 986
Mah, Layla, and Stephan Hodes, “DirectCompute for Gaming:Supercharge Your Engine withCompute Shaders,”Game Developers Conference, Mar. 2013.Cited on p. 54, 518, 535
Story, Jon, “DirectCompute Accelerated Separable Filtering,”Game Developers Conference,Mar. 2011.Cited on p. 54, 518
Lauritzen, Andrew, Marco Salvi, and Aaron Lefohn, “SampleDistribution Shadow Maps,” inSymposium on Interactive 3D Graphics and Games, ACM, pp. 97–102, Feb. 2011.Cited onp. 54, 101, 244, 245
Kasyan, Nikolas, “Playing with Real-Time Shadows,”SIGGRAPH Efficient Real-Time Shad-ows course, July 2013.Cited on p. 54, 234, 245, 251, 264, 585
Hoobler, Nathan, “High Performance Post-Processing,”Game Developers Conference, Mar.2011.Cited on p. 54, 536
Wihlidal,Graham,“Optimizing the Graphics Pipeline with Compute,”in Wolfgang Engel,ed.,GPU Zen, Black Cat Publishing, pp. 277–320, 2017.Cited on p. 54, 702, 784, 798, 812,834, 837, 840, 848, 850, 851, 908, 986
Fatahalian,Kayvon,and Randy Bryant,Parallel Computer Architecture and Programmingcourse, Carnegie Mellon University, Spring 2017.Cited on p. 30, 55
Sellers, Graham, Richard S. Wright Jr., and Nicholas Haemel,OpenGL Superbible: Compre-hensive Tutorial and Reference, Seventh Edition, Addison-Wesley, 2015.Cited on p. 55
Kessenich, John, Graham Sellers, and Dave Shreiner,OpenGL Programming Guide: The Of-ficial Guide to Learning OpenGL, Version 4.5 with SPIR-V, Ninth Edition, Addison-Wesley,2016.Cited on p. 27, 39, 41, 55, 96, 173, 174
Rost, Randi J., Bill Licea-Kane, Dan Ginsburg, John Kessenich, Barthold Lichtenbelt, HughMalan, and Mike Weiblen,OpenGL Shading Language, Third Edition, Addison-Wesley, 2009.Cited on p. 55, 200 好家伙,我特么直接好家伙 看来我可以接下一章了 [赞同]
页:
[1]