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

图形处理单元 (chapter 3 The Graphic Processing Unit)(5-10)

[复制链接]
发表于 2023-1-15 16:09 | 显示全部楼层 |阅读模式
Real-Time Rendering 4th 中文翻译(机翻+矫正) chapter 3

目录

3.1 数据并行架构(Data-Parallel)
3.2 图形管线概述(GPU Pipeline Overview)
3.3 可编程的着色阶段(The Programmable Shader Stage)
3.4 可编程着色和API的发展(The Evolution of Programmable Shading and APIs)
3.5 顶点着色器(The Vertex Shader)
3.6 细分阶段(The Tessellation Stage)
3.7 几何着色器(The Geometry Shader)
    3.7.1 流输出(Stream Output)
3.8 像素着色器(The Pixel Shader)
3.9 合并阶段(The Merge Shader)
3.10 计算着色器 (The compute Shader)
3.5 顶点着色器(The Vertex Shader)

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

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




Image


图 3.7。 表示曲面(红色)的三角形网格(黑色,顶点法线)的侧视图。 在左侧,平滑的顶点法线用于表示光滑的表面。 在右侧,中间顶点已被复制并赋予两条法线,代表折痕。
顶点着色器是处理三角形网格的第一阶段。顶点着色器无法使用描述所形成三角形的数据。 顾名思义,它专门处理传入的顶点。 顶点着色器提供了一种方法来修改、创建或忽略与每个三角形顶点关联的值,例如颜色、法线、纹理坐标和位置。 通常,顶点着色器程序将顶点从模型空间变换到齐次裁剪空间(第 4.7 节)。 至少,顶点着色器必须始终输出该位置。

顶点着色器与前面描述的统一着色器非常相似。 传入的每个顶点都由顶点着色器程序处理,然后输出一些值,这些值在三角形或直线上进行插值。 顶点着色器既不能创建也不能销毁顶点,一个顶点生成的结果不能传递给另一个顶点。 由于每个顶点都是独立处理的,因此 GPU 上的任意数量的着色器处理器都可以并行应用于传入的顶点流.
输入组件(input assembly)通常表示 在执行顶点着色器之前发生的过程。 这是一个物理模型通常与逻辑模型不同的示例。 从物理上讲,获取数据以创建顶点可能发生在顶点着色器中,驱动程序会悄悄地为每个着色器添加适当的指令,这对程序员来说是不可见的。 接下来的章节解释了几种顶点着色器效果,例如动画关节的顶点混合和轮廓渲染。 顶点着色器的其他用途

输入组装通常呈现为在执行顶点着色器之前发生的过程。 这是一个物理模型通常与逻辑模型不同的示例。 从物理上讲,获取数据以创建顶点可能发生在顶点着色器中,驱动程序会悄悄地为每个着色器添加适当的指令,这对程序员来说是不可见的。 接下来的章节解释了几种顶点着色器效果,例如动画关节的顶点混合和轮廓渲染。 顶点着色器的其他用途
包括:


  • 对象生成,通过仅创建一次网格并通过顶点着色器对其进行变形。
  • 使用蒙皮和变形技术为角色的身体和面部制作动画。
  • 程序变形,例如旗帜、布料或水的移动[802、943]。
  • 粒子创建,通过将退化(无区域)网格沿着管道发送并根据需要为这些网格指定一个区域。
  • 镜头失真、热雾、水波纹、页面卷曲和其他效果,方法是将整个帧缓冲区的内容用作进行程序变形的屏幕对齐网格上的纹理。
  • 通过使用顶点纹理获取来应用地形高度场 [40, 1227]。
使用顶点着色器完成的一些变形如图 3.8 所示。




Image


图 3.8。 左边是一个普通的茶壶。 顶点着色器程序执行的简单剪切操作会生成中间图像。 在右侧,噪声函数创建了一个扭曲模型的场。 (图片由 FX Composer 2 制作,由 NVIDIA Corporation 提供。)
顶点着色器的输出可以用几种不同的方式使用。 通常的路径是每个实例的图元,例如三角形,然后被生成和光栅化,并且产生的单个像素片段被发送到像素着色器程序以进行继续处理。 在某些 GPU 上,数据还可以发送到曲面细分阶段或几何着色器或存储在内存中。 这些可选阶段将在以下部分中讨论。
3.6 细分阶段(The Tessellation Stage)

细分阶段允许我们渲染曲面。 GPU 的任务是获取每个表面描述并将其转化为一组具有代表性的三角形。 此阶段是一项可选的 GPU 功能,它首先在 DirectX 11 中可用(并且是 DirectX 11 所必需的)。OpenGL 4.0 和 OpenGL ES 3.2 也支持它。

使用曲面细分阶段有几个优点。 曲面描述通常比提供相应的三角形本身更紧凑。 除了节省内存之外,此特点还可以防止 CPU 和 GPU 之间的总线成为每帧形状都在变化的动画角色或对象的瓶颈。 通过为给定视图生成适当数量的三角形,表面可以被高效的渲染。 例如,如果球离相机很远,则只需要几个三角形。 近距离观察,它可能看起来最好用数千个三角形来表示。 这种控制细节级别的能力还可以让应用程序控制其性能,例如,在较弱的 GPU 上使用较低质量的网格以保持帧速率。 通常由平面表示的模型可以转换为精细的三角形网格,然后根据需要进行变形 [1493],或者可以对它们进行细分以减少执行昂贵的着色计算的频率 [225]。
细分阶段总是由三个元素组成。 使用 DirectX 的术语,这些是hull shadertessellatordomain shader 。 在 OpenGL 中,hull shader 是控制曲面细分的着色器,domain shader 是做曲面细分评估的着色器,它们的描述性更强一些,但很冗长。 tessellator的功能固定,它在 OpenGL 中称为图元生成器,正如将会看到的那样,这确实是它的作用。

如何指定和细分曲线和曲面将会在 第17章 被讨论详细。这里我们简要总结每个细分阶段的目的。 首先,hull shader的输入是一个特殊的面片图元(patch primitive)。 这由几个定义细分曲面、B'ezier 面片或其他类型的弯曲元素的控制点组成。 hull shader有两个功能。 首先,它告诉曲面细分器应该生成多少个三角形,以及以何种配置生成。 其次,它对每个控制点进行处理。 此外,可选地,hull shader可以修改传入的面片(patch)描述,根据需要添加或删除控制点。 hull shader将其控制点集连同曲面细分控制数据一起输出到domain shader。 见图 3.9。




Image


图 3.9。 细分阶段。 hull shader接受由控制点定义的面片。 它将细分因子 (TFs,Tessallation Factors) 和类型发送到固定功能细分器。 控制点集根据hull shader的需要进行转换,并与 TFs 和相关的面片常量一起发送到domain shader。 tessellator创建顶点集及其重心坐标。 然后这些由domain shader,生成三角形网格(显示控制点以供参考)。
tessellator是管道中的固定功能阶段,仅与细分着色器一起使用。 它的任务是为domain shader添加几个新的顶点来处理。 hull shadertessellator发送有关所需细分曲面类型的信息:三角形、四边形或等值线。 等值线是一组线带,有时用于头发渲染 [1954]。 hull shader发送的其他重要值是TFs(Tessallation Factors)(OpenGL 中的细分级别)。 这些有两种类型:内部边缘和外部边缘。 这两个内部因素决定了三角形或四边形内部会进行多少细分。 外部因素决定了每个外部边缘被分割的程度(第 17.6 节)。 图 3.10 显示了增加 TFs 的示例。 通过允许单独的控件,我们可以让相邻曲面的边缘在细分中匹配,而不管内部是如何细分的。 匹配边缘避免了面片相遇处的裂缝或其他阴影伪影。 顶点被分配了重心坐标(第 22.8 节),这些值指定了所需表面上每个点的相对位置。




Image


图 3.10。 改变 TFs 的效果。 犹他茶壶由 32 个面片组成。 内部和外部镶嵌因子从左到右分别为 1、2、4 和 8。(图像由 Rideout 和 Van Gelder [1493] 的演示生成。)
hull shader总是输出一个面片,一组控制点位置。 但是,它可以通过向tessellator发送零或更小(或非数字,NaN)的外部细分级(outer tessellation level)别来表示要丢弃面片。 否则,tessellator生成一个网格并将其发送到domain shaderdomain shader的每次调用都使用来自hull shader的曲面控制点来计算每个顶点的输出值。 domain shader具有类似于顶点着色器的数据流模式,来自tessellator的每个输入顶点都经过处理并生成相应的输出顶点。 形成的三角形然后沿着管道向下传递。

虽然这个系统听起来很复杂,但它的结构是为了提高效率,而且每个着色器都可以相当简单。 传递到hull shader的面片通常会进行很少的修改或不进行修改。 该着色器还可以使用面片的估计距离或屏幕大小来动态计算 TFs,如地形渲染 [466]。 或者,hull shader可以简单地为应用程序计算和提供的所有面片传递一组固定值。 tessellator执行一个复杂但功能固定的过程,即生成顶点、给它们位置并指定它们形成的三角形或线。 这个数据放大步骤是在着色器之外执行的,以提高计算效率 [530]。 domain shader 获取为每个点生成的重心坐标,并在面片的评估方程中使用这些坐标来生成位置、法线、纹理坐标和其他所需的顶点信息。 有关示例,请参见图 3.11。




Image


图 3.11。 左边是大约 6000 个三角形的底层网格。 在右侧,使用 PN 三角形细分对每个三角形进行细分和置换。 (图片来自 NVIDIA SDK 11 [1301] 样本,由 NVIDIA Corporation 提供,模型来自 4A Games 的 Metro 2033。)
3.7 几何着色器(The Geometry Shader)

几何着色器可以将一些图元转换为其他图元,这是细分阶段无法做到的。 例如,通过让每个三角形创建线边,可以将三角形网格转换为线框视图。 或者,这些线可以用面向观察者的四边形代替,因此可以制作具有较厚边缘的线框渲染 [1492]。 随着 DirectX 10 的发布,几何着色器于 2006 年底添加到硬件加速图形管线中。它位于管线中的曲面细分着色器之后,其使用是可选的。 虽然它是 Shader Model 4.0 的必需部分,但它并未在早期的着色器模型中使用。 OpenGL 3.2 和 OpenGL ES 3.2 也支持这种类型的着色器。

几何着色器的输入是单个对象及其关联的顶点。 该对象通常由带状三角形、线段或简单的点组成。 扩展图元可以由几何着色器定义和处理。 特别地,可以传入三个额外的三角形外部的顶点,并且可以使用折线上的两个相邻顶点。 见图 3.12。 借助 DirectX 11 和 Shader Model 5.0,您可以传入更精细的面片,最多可达 32 个控制点。 也就是说,曲面细分阶段对于面片生成来说更有效 [175]。




Image


图3.12。几何着色器程序的几何着色器输入具有某种单一类型:点、线段、三角形。最右边的两个基本体包括与直线和三角形对象相邻的顶点。更复杂的面片类型是可能的。
几何着色器处理这个图元并输出零个或多个顶点,这些顶点被视为点、折线或三角形带。 请注意,几何着色器根本不会生成任何输出。 这样,可以通过编辑顶点、添加新图元和移除其他图元来选择性地修改网格。

几何着色器设计用于修改传入数据或制作有限数量的副本。 例如,一种用途是生成六个转换后的数据副本以同时渲染立方体贴图的六个面; 请参阅第 10.4.3 节。 它还可用于高效创建cascaded shadow maps(有兴趣的可以点这个·链接,看看这个算法)以生成高质量阴影。 其他利用几何着色器的算法包括从点数据创建可变粒子,沿着轮廓挤压鳍(extruding fins)以进行毛皮渲染,以及为阴影算法寻找对象边缘。 有关更多示例,请参见图 3.13。 这些和其他用途将在本书的其余部分进行讨论。




Image


图 3.13。 几何着色器 (GS) 的一些用途。 在左侧,使用 GS 动态执行元球等值面细分。 中间使用GS做线段的分形细分并流出,GS生成billboards用于闪电的展示。 在右侧,布料模拟是通过使用带流输出的顶点和几何着色器执行的。 (图片来自 NVIDIA SDK 10 [1300] 样本,由 NVIDIA 公司提供。)
DirectX 11 添加了几何着色器使用实例化的能力,其中几何着色器可以在任何给定图元上运行一定次数 [530, 1971]。 在 OpenGL 4.0 中,这是用调用计数指定的。 几何着色器还可以输出最多四个流。 可以将一个流向下发送到渲染管道以进行进一步处理。 所有这些流都可以选择性地发送到流输出渲染目标。

几何着色器保证以与输入相同的顺序从基元输出结果。 这会影响性能,因为如果多个着色器核心并行运行,则必须保存和排序结果。 这个和其他因素不利于几何着色器用于在单个调用中复制或创建大量几何体 [175,530]。
发出 draw call 后,管道中只有三个地方可以在 GPU 上创建工作:光栅化、曲面细分阶段和几何着色器。 其中,考虑到所需的资源和内存时,几何着色器的行为是最不可预测的,因为它是完全可编程的。 在实践中,几何着色器通常用处不大,因为它不能很好地映射到 GPU 的优势。 在一些移动设备上,它是在软件中实现的,因此不鼓励使用它 [69]。
3.7.1 流输出(Stream Output)

GPU 管道的标准用法是通过 顶点着色器 发送数据,然后光栅化生成的三角形,并在像素着色器中处理这些三角形中的像素。 以前都是数据一直通过pipeline,中间结果访问不到。 流输出的思想是在Shader Model 4.0中引入的。 在顶点着色器(以及可选的曲面细分和几何着色器)处理顶点之后,除了发送到光栅化阶段之外,它们还可以在流中输出,即有序数组。 事实上,可以完全关闭光栅化,然后将管道纯粹用作非图形流处理器。 以这种方式处理的数据可以通过管道发回,从而允许迭代处理。 这种类型的操作可用于模拟流水或其他粒子效果,如第 13.8 节所述。 它还可以用于为模型蒙皮,然后让这些顶点可用于重用(第 4.4 节)。

流输出仅以浮点数的形式返回数据,因此它可能会产生明显的内存开销。 流输出适用于图元,而不是直接适用于顶点。 如果网格沿着管道发送,每个三角形都会生成自己的一组三个输出顶点。 原始网格中的任何顶点共享都将丢失。 出于这个原因,更典型的用途是仅将顶点作为点集图元通过管道发送。 在 OpenGL 中,流输出阶段称为变换反馈(transform feedback),因为它的大部分使用重点是变换顶点并返回它们以供进一步处理。 保证图元按照输入的顺序发送到流输出目标,这意味着将保持顶点顺序 [530]。
3.8 像素着色器(The Pixel Shader)

在顶点、曲面细分和几何着色器执行它们的操作之后,图元被裁剪并设置为光栅化,如前一章所述。 流水线的这一部分在其处理步骤中相对固定,即不可编程但有些可配置。 遍历每个三角形以确定它覆盖了哪些像素。 光栅化器也可以粗略地计算三角形覆盖每个像素单元区域的程度(第 5.4.2 节)。 部分或完全重叠像素的这片三角形称为片段(fragment)。

三角形顶点处的值(包括 z 缓冲区中使用的 z 值)针对每个像素在三角形表面进行插值。 这些值被传递给像素着色器,然后由像素着色器处理片段。 在 OpenGL 中,像素着色器被称为片段着色器,这也许是一个更好的名称。 为了保持一致性,我们在整本书中都使用“像素着色器”。 沿着管道发送的点和线图元也会为覆盖的像素创建片段。
在三角形上执行的插值类型由像素着色器程序指定。 通常我们使用透视校正插值(感兴趣可以点这个链接了解一下),这样像素表面位置之间的世界空间距离随着物体在距离上的后退而增加。 一个例子是渲染延伸到地平线的铁轨。 铁路枕木在铁轨距离较远的地方间隔更近,因为每个连续的像素接近地平线的距离更长。 其他插值选项可用,例如屏幕空间插值,其中不考虑透视投影。 DirectX 11 可以进一步控制何时以及如何执行插值 [530]。

在编程术语中,顶点着色器程序的输出,在三角形(或直线)上进行插值,有效地成为像素着色器程序的输入。 随着 GPU 的发展,其他输入也被加入进来。 例如,片段的屏幕位置可用于 Shader Model 3.0 及更高版本中的像素着色器。 此外,三角形的哪一侧可见是通过一个输入标志来标明的。 此知识对于在单个通道中在每个三角形的正面和背面渲染不同的材质非常重要。
有了输入,像素着色器通常会计算并输出片段的颜色。 它还可能会产生一个不透明度值(α值)并可选择修改其 z 深度。 在合并期间,这些值用于修改存储在像素中的内容。 在光栅化阶段生成的深度值也可以被像素着色器修改。 模板缓冲区(stencil buffer)值通常是不可修改的,而是传递到合并阶段。 DirectX 11.3 允许着色器更改此值。 在 SM 4.0 [175] 中,雾计算和 alpha 测试等操作已经从合并操作转变为像素着色器的计算。

像素着色器还具有丢弃传入片段的独特能力,即不生成任何输出。 图 3.14 显示了如何使用片段丢弃的一个示例。 裁剪平面功能曾经是固定功能管道中的可配置元素,后来在顶点着色器中指定。 有了片段丢弃可用,这个功能就可以在像素着色器中以任何需要的方式实现,例如决定剪切体积是否应该被“And(&)”或“Or(|)”在一起。




Image


图 3.14。 用户定义的剪裁平面。 在左侧,单个水平裁剪平面将对象切片。 在中间,嵌套的球体被三个平面剪裁。 在右侧,只有在所有三个裁剪平面之外的球体表面才会被裁剪。 (来自 three.js 示例 webgl 裁剪和 webgl 裁剪交集 [218]。)
最初,像素着色器只能输出到合并阶段,以供最终显示。 随着时间的推移,像素着色器可以执行的指令数量大幅增加。 这种增加引发了多渲染目标 (MRT) 的想法。 不是将像素着色器程序的结果发送到颜色和 z 缓冲区,而是可以为每个片段生成多组值并保存到不同的缓冲区,每个缓冲区称为渲染目标(render target)。 渲染目标通常具有相同的 x 和 y 维度; 一些 API 允许不同的大小,但渲染区域将是其中最小的。 一些架构要求每个渲染目标都具有相同的位深度,甚至可能具有相同的数据格式。 根据 GPU,可用渲染目标的数量为四个或八个。

即使有这些限制,MRT 功能仍然是更有效地执行渲染算法的有力帮助。 单个渲染通道可以在一个目标中生成彩色图像,在另一个目标中生成对象标识符,在第三个中生成世界空间距离。 这种能力还产生了一种不同类型的渲染管线,称为延迟着色,其中可见性和着色在单独的通道中完成。 第一遍在每个像素处存储有关对象位置和材料的数据。 然后连续的通道可以有效地应用照明和其他效果。 此类呈现方法在第 20.1 节中描述。
像素着色器的限制是它通常只能在传递给它的片段位置写入渲染目标,而不能从相邻像素读取当前结果。 也就是说,当像素着色器程序执行时,它不能将其输出直接发送到相邻像素,也不能访问其他人最近的更改。 相反,它计算仅影响其自身像素的结果。 然而,这种限制并不像听起来那么严重。 在一个通道中创建的输出图像可以在后面的通道中由像素着色器访问其任何数据。 可以使用第 12.1 节中描述的图像处理技术来处理相邻像素。

像素着色器无法知道或影响相邻像素结果的规则也有例外。 一是像素着色器可以在计算梯度或导数信息期间立即访问相邻片段的信息(尽管是间接的)。 因为像素着色器提供了沿屏幕的 x 和 y 的每个像素的任何插值变化量。 这些值对于各种计算和纹理寻址很有用。 这些梯度对于纹理过滤(第 6.2.2 节)等操作特别重要,(在这些操作中)我们想知道有多少图像覆盖了一个像素。 所有现代 GPU 都通过以 2 × 2 为一组(称为 quad )处理片段来实现此功能。 当像素着色器请求梯度值时,返回相邻片段之间的差异。 见图 3.15。 一个统一的核心具有访问相邻数据的能力——保存在同一个 warp 上的不同线程中——因此可以计算在像素着色器中使用的梯度。 此实现的一个后果是无法在受动态流控制影响的着色器部分访问梯度信息,即“if”语句或具有可变迭代次数的循环。 一组中的所有片段必须使用相同的指令集进行处理,以便所有四个像素的结果对计算梯度都有意义。 这是即使在离线渲染系统中也存在的基本限制 [64]。




Image


图 3.15。 在左边,一个三角形被栅格化为四边形,一组 2 × 2 像素。 然后右侧显示了用黑点标记的像素的梯度计算。 为四边形中的四个像素位置中的每一个显示 v 的值。 请注意,其中三个像素未被三角形覆盖,但它们仍由 GPU 处理,以便可以找到梯度。 x 和 y 屏幕方向的梯度是通过使用它的两个四边形邻居为左下像素计算的。
DirectX 11 引入了一种缓冲区类型,允许对任何位置进行写入访问,即无序访问视图 (UAV)。 最初仅适用于像素和计算着色器,对UAVs的访问扩展到 DirectX 11.1 [146] 中的所有着色器。 OpenGL 4.3 将其称为着色器存储缓冲区对象 (SSBO)。 这两个名称都以自己的方式进行描述。 像素着色器以任意顺序并行运行,并且此存储缓冲区在它们之间共享。

通常需要一些机制来避免数据竞争条件(data race condition)(又名数据危害),其中两个着色器程序“竞争”以影响相同的值,可能导致任意结果。 例如,如果像素着色器的两次调用试图在大约同一时间添加到相同的检索值,则可能会发生错误。 两者都将检索原始值,都将在本地修改它,但是无论哪个调用最后写入其结果都会消除另一个调用的贡献——只会发生一次添加。 GPU 通过拥有着色器可以访问的专用原子单元来避免这个问题 [530]。 然而,原子性意味着一些着色器可能会在等待访问正在被另一个着色器读取/修改/写入的内存位置时停止。
虽然原子避免了数据危害,但许多算法需要特定的执行顺序。 例如,您可能想要绘制一个更远的透明蓝色三角形,然后再用红色透明三角形覆盖它,将红色混合在蓝色之上。 一个像素可能有两个像素着色器调用,每个三角形一个,以红色三角形的着色器在蓝色三角形之前完成的方式执行。 在标准管道中,片段结果在处理之前在合并阶段进行排序。 DirectX 11.3 中引入了光栅化顺序视图 (ROV) 以强制执行顺序。 这些就像 UAVs, 它们可以由着色器以相同的方式读取和写入。 关键区别在于 ROV 保证以正确的顺序访问数据。 这大大增加了这些着色器可访问缓冲区的实用性 [327、328]。 例如,ROVs 使像素着色器可以编写自己的混合方法,因为它可以直接访问和写入 ROV 中的任何位置,因此不需要合并阶段 [176]。 代价是,如果检测到乱序访问,像素着色器调用可能会停止,直到处理完之前绘制的三角形。
3.9 合并阶段(The Merge Shader)

如第 2.5.2 节所述,合并阶段是将各个片段(在像素着色器中生成)的深度和颜色与帧缓冲区(framebuffer)组合的地方。 DirectX 将此阶段称为输出合并**(output merger); OpenGL 将其称为逐样本操作(per-sample operations)。 在大多数传统流水线图(包括我们自己的流水线图)上,此阶段是模板缓冲(stencil-buffer)和 z-缓冲区(z-buffer)操作发生的地方。 如果片段可见,则在此阶段发生的另一个操作是颜色混合。 对于不透明表面,不涉及真正的混合,因为片段的颜色只是替换了之前存储的颜色。 片段和存储颜色的实际混合通常用于透明度和合成操作(第 5.5 节)。

想象一下,光栅化生成的片段通过像素着色器运行,然后在应用 z-buffer 时发现被一些先前渲染的片段隐藏。 在像素着色器中完成的所有处理都是不必要的。 为了避免这种浪费,许多 GPU 在执行像素着色器之前执行一些合并测试 [530]。 片段的 z-深度(以及正在使用的任何其他内容,例如模板缓冲或剪切(scissoring))用于测试可见性。 如果隐藏,片段将被剔除。 此功能称为 early-z [1220、1542]。 像素着色器能够更改片段的 z-depth 或完全丢弃片段。 如果发现像素着色器程序中存在任何一种类型的操作,则 early-z 通常无法使用并被关闭,通常会降低流水线的效率。 DirectX 11 和 OpenGL 4.2 允许像素着色器强制开启 early-z 测试,尽管有许多限制 [530]。 有关早期 z 和其他 z 缓冲区优化的更多信息,请参见第 23.7 节。 有效地使用 early-z 会对性能产生很大影响,这将在第 18.4.5 节中详细讨论。
合并阶段占据固定功能阶段(例如三角形设置)和完全可编程着色器阶段之间的中间地带。 虽然它不是可编程的,但它的操作是高度可配置的。 特别是可以设置颜色混合来执行大量不同的操作。 最常见的是涉及颜色和 alpha 值的乘法、加法和减法的组合,但其他运算也是可能的,例如最小值和最大值,以及按位逻辑运算。 DirectX 10 添加了将来自像素着色器的两种颜色与帧缓冲区颜色混合的功能。 此功能称为双源颜色混合(dual source-color blending),不能与多个渲染目标结合使用。 MRT 不支持混合,DirectX 10.1 引入了在每个单独的缓冲区上执行不同混合操作的功能。

正如在上一节末尾提到的,DirectX 11.3 提供了一种通过 ROVs 进行混合的可编程方式,尽管这是以性能为代价。 ROVs 和合并阶段都保证绘制顺序,也就是输出不变性。 无论像素着色器结果的生成顺序如何,API 要求结果按输入顺序排序并 一个对象接一个对象的/一个三角形接一个三角形的 发送到合并阶段。
3.10 计算着色器 (The compute Shader)

GPU 不仅可以用于实现传统的图形管道。 在计算股票期权的估计值和训练神经网络以进行深度学习等领域中有许多非图形用途。 以这种方式使用硬件称为 GPU 计算。 CUDA 和 OpenCL(Open Computing Language) 等平台用于将 GPU 作为大规模并行处理器进行控制,而无需或无法访问特定于图形的功能。 这些框架通常使用带有扩展的 C 或 C++ 等语言,以及为 GPU 制作的库。

在 DirectX 11 中引入的计算着色器是 GPU 计算的一种形式,因为它是一种未锁定在图形管道中某个位置的着色器。 它与渲染过程密切相关,因为它由图形 API 调用。 它与顶点、像素和其他着色器一起使用。 它利用与管道中使用的相同的统一着色器处理器池。 它和其他着色器一样是着色器,因为它有一些输入数据集并且可以访问缓冲区(例如纹理)以进行输入和输出。 线程束(warps) 和线程在计算着色器中更明显。 例如,每次调用都会获得一个它可以访问的线程索引。 还有线程组的概念,在 DirectX 11 中由 1 到 1024 个线程组成。这些线程组由 x、y 和 z 坐标指定,主要是为了在着色器代码中使用简单。 每个线程组都有少量内存供线程共享。 在 DirectX 11 中,这相当于 32 kB。 计算着色器由线程组执行,因此组中的所有线程都保证并发运行 [1971]。
计算着色器的一个重要优势是它们可以访问在 GPU 上生成的数据。 从 GPU 向 CPU 发送数据会产生延迟,因此如果处理和结果可以保留在 GPU 上,则可以提高性能 [1403]。 以某种方式修改渲染图像的后处理是计算着色器的常见用途。 共享内存意味着采样图像像素的中间结果可以与相邻线程共享。 例如,已经发现使用计算着色器来确定图像的分布或平均亮度的运行速度是在像素着色器上执行此操作的两倍 [530]。

计算着色器也可用于粒子系统、网格处理,例如面部动画 [134]、剔除 [1883、1884]、图像过滤 [1102、1710]、提高深度精度 [991]、阴影 [865]、景深 [ 764],以及一组 GPU 处理器可以承担的任何其他任务。 Wihlidal [1884] 讨论了计算着色器如何比曲面细分外壳着色器更高效。 有关其他用途,请参见图 3.16。




Image


图 3.16。 计算着色器示例。 在左侧,计算着色器用于模拟受风影响的头发,头发本身使用曲面细分阶段进行渲染。 在中间,计算着色器执行快速模糊操作。 右边是模拟海浪。 (图片来自 NVIDIA SDK 11 [1301] 样本,由 NVIDIA 公司提供。)
我们对 GPU 渲染管线实现的回顾到此结束。 有许多方法可以使用和组合 GPU 函数来执行各种与渲染相关的过程。 为利用这些能力而调整的相关理论和算法是本书的中心主题。 我们的重点现在转移到变换和着色。
进一步阅读和资源

Giesen 的图形管道之旅 [530] 详细讨论了 GPU 的许多方面,解释了为什么元素以它们的方式工作。 Fatahalian 和 Bryant [462] 的课程在一系列详细的讲座幻灯片集中讨论了 GPU 并行性。 虽然专注于使用 CUDA 进行 GPU 计算,但 Kirk 和 Hwa 的书 [903] 的介绍部分讨论了 GPU 的发展和设计理念。

学习着色器编程的正式方面需要一些工作。 OpenGL Superbible [1606] 和 OpenGL Programming Guide [885] 等书籍包含有关着色器编程的资料。 较早的书 OpenGL Shading Language [1512] 没有涵盖较新的着色器阶段,例如几何和曲面细分着色器,但确实专注于与着色器相关的算法。 查看本书的网站 http://realtimerendering.com,了解最近和推荐的书籍。
本文使用 Zhihu On VSCode 创作并发布

本帖子中包含更多资源

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

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

本版积分规则

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

GMT+8, 2024-11-24 12:34 , Processed in 0.094431 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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