找回密码
 立即注册
查看: 366|回复: 0

第三章 图形处理器(下)

[复制链接]
发表于 2021-7-30 07:54 | 显示全部楼层 |阅读模式
3.7 几何着色器(The Geometry Shader)
几何着色器可以将图元转换为其他图元,而细分阶段无法完成。例如,三角形网格可以被变换为线框视图通过为每个三角形创建线边(line edges)。或者,这些线可以被面向观察者的四边形所代替,可以把线框渲染有较厚的边缘。几何着色器于2006年底发布的DirectX 10加入到硬件加速图形管道中。它在管道中的位置位于细分着色器(tessellation shader)之后,它的使用是可选的。虽然是着色器模型4.0中必须的部分,在先前的着色器模型中没有使用。OpenGL 3.2和OpenGL ES 3.2也支持这个类型的着色器。
几何着色器的输入是单个物体及其关联的顶点。物体通常由三角形带,线段或简单的一个点组成。扩展的图元可以由几何着色器定义和处理。 尤其是,可以传入三角形外部的三个附加顶点,并且折现(polyline)上两个相邻的点可以被使用。参见图3.12。使用DirectX 11和着色器模型5.0,你可以传递更详尽的patches,最多包含32个控制点。那就是说,细分阶段(tessellation stage)对于patch生成更高效。
几何着色器处理该图元并输出零个或多个顶点,这些顶点被视为点,折线或三角形带。 注意几何着色器可以生成什么都没有的输出。 这样,网格可以有选择性的修改通过编辑顶点,添加新图元和删除其他图元。
几何着色器设计用于修改输入数据或进行有限数量的拷贝。 例如,一种用途是生成六个变换后的数据拷贝同时渲染立方体贴图的六个面; 参见第10.4.3节。 也可以是用于高效创建级联阴影贴图以生成高质量阴影。利用几何着色器的其他算法包括从点数据创建大小可变的粒子,沿着轮廓拉伸鳍状物(fins)以进行毛发渲染,以及为阴影算法查找物体的边缘。 有关更多示例,请参见图3.13。 本书的其余部分将讨论这些和其他用途。
DirectX 11为几何着色器增加了使用实例化的能力,其中几何着色器可以在任意给定的图元上运行设定的次数。在OpenGL 4.0中,这是通过调用次数指定的。几何着色器也可以最多输出四个流。以在渲染管道上发送一个流,用于进一步处理。多有这些流都可以选择被发送到流输出渲染目标。
几何着色器保证和图元输入相同的顺序输出结果。这会影响性能,因为如果几个着色器核心并行运行,结果必须被保存和排序。这个和其它因素不利于被用于在单一调用中复制或者创建大量几何体的几何着色器。
发出绘图调用后,在GPU上管道中只有三个地方可以进行创建工作:栅格化,细分阶段和几何着色器。其中,当考虑到需要的资源和内存,几何着色器的行为是最不可预测的,因为它是完全可编程的。在实践中,几何着色器通常使用很少,因为它无法很好地映射到GPU的优势上。在一些移动设备上,几何着色器以软件的方式实现,因此不鼓励在那里使用。
3.7.1 流输出
GPU管道的标准使用是发送数据经过顶点着色器,然后栅格化生成的三角形,并在像素着色器中对其进行处理。过去因为数据总是通过管道传递,中间结果无法被访问。流输出(stream output)的想法是在Shader Model 4.0中引入的。当顶点被顶点着色器处理以后(或者,可选的,细分和几何着色器),除了被发送到光栅化阶段,还可以输出到一个流中(即有序数组)。光栅化事实上可以被完全关闭,然后管道用于纯粹的非图形处理器。以这种方式处理的数据可以通过管道发送回去,从而允许迭代处理。这种类型的操作可用于模拟流水或其他粒子效果,如第13.8节所讨论的。它也可以用于一个模型的蒙皮然后重用这些顶点(4.4节)。
流输出仅以浮点数的形式返回数据,因此它可以有明显的内存消耗。 流输出作用于图元,而不是直接作用于顶点。 如果将网格沿管道发送,则每个三角形都会生成自己的三个输出顶点的集合。原始网格中共享的所有顶点都将丢失。由于这原因是更典型的用法是顶点仅仅作为点集图元在管道中发送图元。在OpenGL中,流输出阶段称为变换反馈,因为其大部分用途的重点是转换顶点并将其返回以进一步处理。保证图元以它们被输入的顺序发送到流输出目标,这意味着顶点的顺序将保持不变。
3.8 像素着色器
在顶点,细分,和集合着色器执行它们的操作后,裁剪和设置(set up)图元以进行光栅化,如上一章所述。管道的这部分在他的处理步骤中相对固定,即,不可编程,但有些可配置。遍历每个三角形来决定它覆盖的像素。光栅化器还可以粗略计算出三角形覆盖的每个像素单元格多少面积(5.4.2节)。这个三角形部分或完全和像素重叠被叫做片段(fragment)。
三角形顶点的值,包括用于z-buffer的z值,为每个像素在整个三角形表面插值。这些值被传递到像素着色器,然后处理片段。在OpenGL中像素着色器被称为片段着色器,这或许是个更好的名字。为了统一我们在整本书中使用“像素着色器”。沿着管道发送的点和线图元也会为它覆盖的像素创建片段。
跨越三角形执行的插值类型通过像素着色器程序指定。通常我们使用透视正确的插值,这样当一个物体后退时像素表面位置间的世界空间距离会增加。一个示例是渲染延伸到地平线的铁轨。当向接近地平面的每个连续的像素行进越多的距离,在铁轨越远的地方,铁轨枕木在空间上越紧密。还有其它插值选项可用,例如屏幕空间插值,在那里没有把透视投影考虑在内。DirectX 11可进一步控制何时以及如何执行插值。
在编程术语中,顶点着色器程序的输出,是在三角形(或线)上插值,称为像素着色器程序的输入。随着GPU的演化,其它的输入已经公开。例如,在着色器模型3.0和后续的版本中,像素着色器片段的屏幕位置是可用的。同样,三角形哪一面是可见的是一个输入标识。这个标识对于在一个通道(pass)中每个三角形前面和背面渲染不同的材质是重要的。
有了输入,通常像素着色器计算和输出一个片段的颜色。它还可能会产生不透明度值,并可以选择修改其z深度。在合并期间,这些值用于修改存储在像素处的内容。光栅化阶段生成的深度值也可以被像素着色器修改。模板缓冲区的值通常是不可修改的,而是传递给合并阶段。DirectX 11.3允许着色器修改这个值。在SM4.0中,比如雾计算和alpha测试的运算已经从合并操作移到像素着色器计算。
像素着色器还具有丢弃传入片段的独特功能,即不产生任何输出。如何使用丢弃片段的例子如图3.14所示。裁剪平面功能以前是固定功能管道中的可配置元素,后来在顶点着色器中指定。随着片段丢弃可用,这个功能可以在像素着色器中以任意期望的方式实现,例如确定是否裁剪体应该"与"或"或"一起用。
最初,像素着色器只能输出到合并阶段,最终显示。 随着时间的推移像素着色器可以执行的指令数量已大大增加。这种增加引起了多个渲染目标(MRT)的想法。 代替将像素着色器程序的结果仅发送到颜色和z缓冲区,可以为每个片段生成一组值并将其保存到不同的缓冲区,每个被称为一个渲染目标。渲染目标通常具有相同的x和y尺寸;一些API允许使用不同的大小,但是渲染区域将是其中最小的。一些架构要求每个渲染目标具有相同的位深度,并且可能甚至是相同的数据格式。 取决于GPU,可用的渲染目标是4或8个,取决于GPU。
即使有这些限制,在更有效地执行渲染算法上MRT功能是一个强大的辅助。一次渲染通道可以在一个目标中生成一个颜色图像,在另一个目标中生成物体标识,在第三个目标中生成世界空间的距离。此功能还引发了另一种类型的渲染管道,称为延迟渲染,其中可见性和着色在单独的通道中完成。第一个通道存储物体在每个像素位置和材质的数据。后续的通道可以有效地应用照明和其它效果。这种类型的渲染方法在20.1节描述。
像素着色器的局限性在于它通常只能写入渲染目标交给它的片段位置,而无法从相邻的像素读取当前结果。也就是说,当像素着色器程序执行时,它不能把它的输出直接发送到相邻的像素,也无法访问其他像素的最新更改。而是计算结果仅影响其自身像素。然而,此限制并不像听起来那么严重。一个通道创建的一个输出图像可以被后来通道的像素说色其访问它的任意数据。相邻像素可以使用图像处理技术处理,在12.1节描述。
像素着色器无法知道或影响相邻像素的结果的规则是有例外的。像素着色器可以立即访问相邻片段(尽管是间接的)的信息的一个例子是在计算梯度或微分信息时。像素着色器提供了沿屏幕x和y轴每个像素变化的任意插值量。对于各种计算和纹理寻址这样的值很有用。这些渐变特别对于诸如纹理过滤(第6.2.2节)之类的操作很重要,当我们想要知道一张图像的多少覆盖了一个像素。所有现代GPU均实现此功能通过将片段分为2×2组进行处理,称为四边形(quad)。当像素着色器请求一个梯度(gradient)值,则返回相邻片段之间的差。看图3.15。统一核心由访问相邻数据的能力──保存在相同warp的不同线程中──因此可以计算像素着色器用到的梯度(gradients)。这种实现的一个结果是梯度(gradient)信息不能被受动态流控制(dynamic flow control)的着色器访问,即,"if"语句或可变迭代次数的循环。一组中所有的片段必须使用相同的指令集处理,以便所有四个像素的结果对于计算梯度有意义。这是一个基础限制,甚至存在于离线渲染系统中。
DirectX 11引入了一种缓冲区类型,该类型允许写访问任何位置,即无序访问视图(UAV)。最初仅适用于像素和计算着色器,在DirectX 11.1中,UAVs被扩展到所有的着色器中。OpenGL 4.3称之为着色器存储哦缓冲区对象(SSBO)。这两个名称以其自己的方式进行描述。像素着色器以任意顺序并行运行,并且此存储缓冲区在它们中是共享的。
通常需要某种机制来避免数据竞速情况(也就是数据冒险),两个着色器程序都“竞相”以影响相同的值,可能导致任意结果。 例如,如果一个像素着色器尝试两个调用,相加两个同时获取的值,在大约相同的时间,可能会发生错误。都将获取到原始值,都将本地修改它,但是无论哪个调用写入它的结果,后面的都会擦除另一个调用的贡献──只有一个加会发生。GPU通过着色器可以访问的专用的原子单元来避免这个问题。然而,原子一位置一些着色器或许会拖延(stall)当它们等待访问一个正在被另一个着色器读/改/写的内存位置。
虽然原子避免了数据危害,但许多算法要求特定的顺序执行。例如,你可能想绘制更远的透明蓝色三角形在红色透明三角形覆盖它之前,将红色何在在蓝色上面。一个像素可能有两个像素着色器调用,每个三角形一次,以这种方式执行红色三角形着色器会先于蓝色完成。在标准的管道中,片段结果在合并阶段被处理之前被排序。光栅化器顺序视图(ROVs)在DirectX 11.3中引入为了加强执行的顺序。这些就像UAVs;它们可以被着色器以相同的方式读取和写入。关键区别在于ROV保证以正确的顺序访问数据。这大大增加了这些着色器可访问缓冲区的有用性。例如,ROV让像素着色器写它自己的混合方法称为可能,因为它可以直接访问并且写入ROV中的任意位置,因此不再需要合并阶段。代价是,如果检测到乱序访问,像素着色器调用可能会拖延(stall)直到先前的三角形绘制被处理。
3.9 合并阶段
如第2.5.2节所述,合并阶段是单个片段(在像素着色器生成)的深度和颜色被帧缓冲区组合在一起的地方。DirectX把这个阶段叫做输出合并器(output merger);OpenGL将其称为每个样本操作。在大多数传统管道图(包括我们自己的)上,此阶段是发生模板缓冲区和z缓冲区操作的位置。如果片段是可见的,另一个发生在这个阶段的操作是颜色混合。对于不透明的表面没有真正的混合,片段的颜色简单地替换先前保存的颜色。真正的片段和保存颜色的缓和常用于透明度和合成操作(5.5节)。
想象一下,通过光栅化生成的片段通过像素着色器的运行,发现被先前渲染的片段隐藏,当应用zbuffer后。所有在像素着色器中已经完成的处理都是不必要的。为了避免这种浪费,需要GPU执行一些合并测试在像素着色器执行之前。片段的z-depth(或者其它正在使用的,如模板缓冲区或裁剪(scissoring))用于测试可见性。片段会被剔除如果隐藏的话。这个功能称为early-z。像素着色由改变片段的z-depth或者完全丢弃片段的能力。如果任意类型的操作发现存在于像素着色器程序中,early-z就不能使用就会被关闭,通常会使管道效率降低。DirectX 11和OpenGL 4.2允许像素着色器强制执行Early-Z测试,尽管有很多限制。有关early-z和其它z-buffer优化的更多信息,请参见第23.7节。有效地使用early-z可以很大提升性能,更多细节再18.4.5节讨论。
合并阶段占据固定功能阶段,如三角形设置,和完全可编程的着色器阶段的中间地带。尽管它不是可编程的,它的操作是高度可配置的。尤其颜色混合可以被设置执行许多不同的操作。最常用的组合是颜色和alpha值得乘,加,和减,但是其它得操作是可能的,如最小值和最大值,还有位逻辑操作。DirectX 10新增了混合像素着色器颜色和帧缓冲颜色的功能。这个能力被叫做双源颜色混合(dual source-color blending)并且不能与多个渲染目标一起使用。MRT否则支持混合,DirectX 10.1引入再每个单独的缓冲区执行不同混合操作的能力。
如上一节末尾所述,DirectX 11.3提供了一种方法混合可编程通过ROV,尽管以性能位待机。ROV和合并阶段都保证绘制顺序,也就是输出不变性。而不管像素着色器结果生成的顺序,API要求结果按输入顺序排序并发送到合并阶段,一个对象一个对象,一个三角形一个三角形。
3.10 计算着色器
除了实现传统的图形管线外,GPU还可以用于更多用途。还有许多非图形学的使用如计算股票期权的估计值和为深度学习训练神经网络。用这种方式使用硬件被称为GPU计算。平台比如CURA和OpenCL将GPU作为大型并行处理器进行控制,没有真正需要或访问图形专用的功能。这些框架通常使用如C或具有扩展功能的C++语言,以及为GPU制作的库。
DirectX 11中引入了计算着色器,它是GPU计算的一种形式,在于它是一种没有锁定在图形管道的一个位置的着色器。它是与渲染过程紧密相关,因为它是由图形API调用的。它与顶点,像素和其他着色器一起使用。它依赖同样的池和那些在管道中使用的统一着色器处理器。像其他的着色器一样它具有一组输入数据,并且可以访问缓冲区(例如纹理)进行输入和输出。Warps和线程在计算着色器中更明显。例如,每个调用都会获得一个可以访问的线程索引。还有一个概念线程组,在DirectX 11中由1到1024个线程组成。这些线程组由x,y和z坐标指定,主要是为了简化着色器代码的使用。每个线程组都有少量的内存,这些内存在线程之间共享。在DirectX 11,这个数量是32kB。计算着色器由线程组执行,这样就保证了该组中的所有线程可以同时运行。
计算着色器的一个重要优点是它们可以访问在GPU上生成的数据。从GPU向CPU发送数据会产生延迟,因此如果处理和结果可以保留在GPU上,则可以提高性能。后处理是对渲染图像进行某种方式的修改,是计算着色器的常见用法。共享内存意味着采样图像像素的中间结果可以与相邻线程共享。用一个计算着色器以确定图像的分布或平均亮度,例如,已发现其运行速度是对像素执行此操作的速度的两倍着色器。
计算着色器还可用于粒子系统,网格处理(例如面部动画,剔除,图像过滤,改进深度精度,阴影,景深和任何其他任务,其中可以使用一组GPU处理器。 Wihlidal讨论了如何计算着色器可能比曲面细分的hull shaders更有效。 参见图3.16看用途。
至此,我们对GPU的渲染管道实现的审查结束了。那里有多种方法可以使用和组合GPU功能来执行各种与渲染相关的处理。相关理论和算法以利用这些能力是本书的中心主题。现在我们的重点继续移动到变换和着色。
进一步于都和资源
Giesen的图形管线之旅详细讨论了GPU的许多方面,解释了一些元素为什么以这种方式工作。Fatahalian和Bryant在一系列详细的演讲幻灯片集中讨论了GPU并行性。当专注于使用CUDA进行GPU计算,Kirk和Hwa的书的入门部分讨论了GPU的演变和设计哲学。
要学习着色器编程的形式方面,需要花费一些工作。 书OpenGL Superbible和OpenGL Programming Guide包含有关着色器编程的资料。 旧书OpenGL Shading Language不能涵盖较新的着色器阶段,例如几何和细分着色器,但确实专注于与着色器相关的算法。 请参阅本书的网站,http://realtimerendering.com,以获取最新书籍和推荐书籍。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 01:37 , Processed in 0.121637 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表