专栏/real time rendering 4th 第三章节粗糙翻译(之后会有中英对照的精致版本)

real time rendering 4th 第三章节粗糙翻译(之后会有中英对照的精致版本)

2021年01月26日 06:44--浏览 · --点赞 · --评论
MonsterCZS
粉丝:18文章:2


阅读前须知:该文献翻译水平有限,还请多多谅解。本人制作这一个人汉化版本,主要是因为市面上的翻译版本主要有章节散乱,笼统而且不正式等问题。更重要的是大部分缺少中英对照阅读的功能。作为所谓的渲染圣经的该书,其很大的功能就是让你了解各项技术的英文名称与原理,方便你进一步阅读其他的参考文献,这一点对那些有国外求学需求的人或许更为突出。我出于对图形学的学识浅薄,并且这不是一个非常学术与严肃的翻译,因此可能会出现一些低级错误,望大佬指正。 还有,后续可能会把RTR的ray tracing等在纸质书之外的部分也翻译了,敬请期待。


章节三

图形处理单元GPU

 

显示就是计算

—Jen-Hsun Huang

 

从历史上看,图形加速始于在与三角形重叠的每个像素扫描线上插入颜色,然后显示这些值。 包括访问图像数据的功能,可以将纹理应用于表面。 添加用于内插和测试z深度的硬件,提供了内置的可见性检查。 由于它们的频繁使用,因此将此类过程专用于专用(dedicated)硬件以提高性能。 连续几代中添加了渲染管线的更多部分,以及每个部分的更多功能。 专用图形硬件相对于CPU的唯一计算优势是速度,但速度至关重要(critical)。

 

在过去的二十年中,图形硬件经历了不可思议的转变。 1999年,第一款包含硬件顶点处理功能的消费类图形芯片(chip)(NVIDIA的GeForce256)问世。NVIDIA创造(coined)了图形处理单元(GPU)一词,以将GeForce 256与以前可用的仅光栅化的芯片区分开来。 在接下来的几年中,GPU从可配置 复杂的固定功能管道到高度可编程的空白面板(slates)的实现,开发人员可以在其中实现自己的算法。 各种可编程着色器是控制GPU的主要手段。 为了提高效率, 管线的某些部分仍然是配置的,而不是可编程的,但是趋势是朝着可编程性和灵活性(flexibility)的方向发展[175]。

GPU通过专注于一组高度可并行化的狭隘的任务而获得了飞速的发展。 他们具有专用于实现z缓冲区,快速访问纹理图像和其他缓冲区以及查找例如三角形覆盖的像素的定制芯片。 这些元素如何执行其功能,在 第23章会详细描述。更重要的是要及早了解GPU如何实现其可编程着色器的并行性。

 

 

 

 

 

 

 


 

3.3节介绍了着色器的功能。目前,您需要知道的是,着色器核心是一个小型处理器,可以执行一些相对隔离的(isolated)任务,例如将顶点从其在世界上的位置转换为屏幕坐标,或者计算被像素覆盖的像素的三角形的颜色。在每帧发送到屏幕的成千上万三角形中,每秒可能有数十亿次着色器调用(invocations),即运行着色器程序的单独(separate)实例。

首先,延迟是所有处理器都面临的问题(concern)。访问数据需要花费一些时间。考虑延迟的一种基本方法是,信息离处理器越远,等待时间就越长。第23.3节更详细地介绍了延迟。存储在存储芯片中的信息将比本地寄存器中的信息花费更长的时间。 18.4.1节将更深入地讨论内存访问。关键是等待数据检索(retrieved)意味着处理器停顿(stalled)了,这将降低性能。

 

3.1 数据并行构架

不同的处理器体系结构使用各种策略来避免停顿(stall)。CPU进行了优化,以处理各种数据结构和大型代码库。 CPU可以具有多个处理器,但是每个CPU都以串行方式(serial fashion)运行代码,有限的SIMD矢量处理是次要的(minor)例外。为了最大程度地降低延迟的影响,CPU的大部分芯片都由快速本地缓存(local caches)组成,这些内存中填充了接下来可能需要的数据。 CPU还通过使用诸如分支预测(branch prediction),指令重新排序,寄存器(register)重命名和缓存预取(cache prefetching)之类的巧妙技术来避免停顿。[715]

GPU采用不同的方法。 GPU的大部分芯片区域专用于一大批称为着色器核心的处理器,通常有数千个。 GPU是流处理器,在其中依次处理相似数据的有序集合。由于这种相似性(例如,一组顶点或像素),GPU可以大规模并行地处理这些数据。另一个重要的元素是这些调用(invocations)尽可能地独立,从而它们不需要来自相邻调用的信息,并且拒绝共享各自的可写(writable)的内存位置。该规则有时会被破坏以允许新的有用功能,但是这种例外是以潜在的延迟为代价的,因为一个处理器可能会等待另一个处理器完成其工作。

GPU针对吞吐量(throughput)进行了优化,吞吐量被定义为可以处理数据的最大速率。 但是,这种快速处理具有成本。 由于专用于高速缓存存储器和控制逻辑的芯片面积较小,每个着色器内核的等待时间通常比CPU处理器遇到的等待时间长得多[462]。

假设网格已栅格化,并且两千个像素具有要处理的片段; 像素着色器程序将被调用2000次。 想象只有一个着色器处理器,这是世界上最弱的GPU。 它开始为2000的第一个片段执行着色器程序。 着色器处理器对寄存器中的值执行一些算术运算。 寄存器是本地的,可以快速访问,因此不会发生停顿。 然后,着色器处理器会执行一条指令,例如纹理访问; 例如,对于给定的表面位置,程序需要知道应用于网格的图像的像素颜色。 纹理是一个完全独立的资源,而不是像素程序本地内存的一部分,并且纹理访问可能会涉及到一定程度。 内存提取可能需要数百到数千个时钟周期,在此期间GPU处理器不执行任何操作。 此时,着色器处理器将停止运行,等待返回纹理的颜色值。

为了使这个糟糕的GPU变得可见地更好,请为每个片段提供一些用于其本地寄存器的存储空间。现在,允许着色器处理器切换并执行(execute)另一个片段,即两千个第二个片段,而不是停止纹理获取(fetch)。此切换速度非常快,除了注意第一条指令正在执行哪条指令之外,第一段或第二段中的内容均不受影响。现在执行第二个片段。与第一个相同,执行一些算术(arithmetic)函数,然后再次遇到纹理获取。着色器核心现在切换到另一个片段,第三个片段。最终,所有两千个片段都以这种方式处理。此时,着色器处理器将返回片段编号1。此时,纹理颜色已被获取并且可以使用,因此着色器程序可以继续执行。处理器以相同的方式进行处理,直到遇到另一个已知会暂停执行的指令,或者程序完成。与着色器处理器始终专注于一个片段相比,单个片段的执行时间会更长,但是整个片段的总体执行时间将大大减少。

在此架构中,通过切换到另一个片段使GPU保持忙碌来隐藏延迟。 GPU通过将指令执行逻辑与数据分离开来,使该设计更进一步。称为单指令多数据(SIMD)的这种安排可以在固定数量的着色器程序上以锁定步骤执行同一命令。 SIMD的优势在于,与使用单独的逻辑和调度(dispatch)单元运行每个程序相比,用于处理数据和交换的硅(和功率)要少得多。将我们的2000片段示例转换为现代GPU术语,每个片段的像素着色器调用都称为线程。这种类型的线程与CPU线程不同。它由用于着色器输入值的一点内存以及着色器执行所需的任何寄存器空间组成。使用相同着色器程序的线程被分成(are bundled)几组,被NVIDIA称为warps,被AMD称为wavefront。计划通过一些SIMD处理功能,在8到64之间的任意数量的GPU着色器内核中执行扭曲/波前。每个线程都映射到SIMD通道。

假设我们有两千个线程要执行。 NVIDIA GPU的warps包含32个线程。这产生2000/32 = 62.5个warps,这意味着分配(allocated)了63个warps,其中一个warps是一半为空。warp的执行类似于我们的单个GPU处理器示例。着色器程序在所有32个处理器上以锁定步骤执行。遇到内存提取时,所有线程都会同时遇到它,因为对所有线程执行相同的指令。提取信号表明线程warp将停止,所有线程都在等待它们的(不同的)结果。与其停滞不前, 将warp换成32个线程的另一个warp,然后由32个内核执行。这种交换(swapping)的速度与我们的单处理器系统一样快,因为在将warp交换进出时,每个线程内的数据都不会被触及。每个线程都有自己的寄存器,每个线程束都跟踪其正在执行的指令。交换新线程只是将一组内核指向另一组要执行的线程即可。没有其他开销(overhead)。warp执行或交换出去,直到全部完成。参见图3.1。

在我们的简单示例中,纹理获取内存的等待时间可能导致warp掉出。实际上,因为交换的成本非常低,所以可以将warps换成较短的延迟。还有其他几种用于优化执行的技术[945],但warp交换是所有GPU使用的主要延迟隐藏机制。此过程的效率如何涉及几个因素。例如,如果线程很少,那么几乎不会创建任何warp,从而使延迟隐藏成为问题。

着色器程序的结构(construction)是影响效率的重要特征。一个主要因素是每个线程使用的寄存器(register)数量。在我们的示例中,我们假设一次可以将2000个线程全部驻留(resident)在GPU上。与每个线程相关联的着色器程序所需的寄存器越多,线程中可以驻留的线程越少,因此warp也就越少。warp不足可能意味着无法通过交换来减轻失速。驻留的warp被称为“飞行中”,这个数字称为占用率。高占用率意味着有许多可用于处理的warp,因此空闲(idle)处理器的可能性较小。占用率低通常会导致性能不佳。内存获取的频率也影响需要多少延迟隐藏。 Lauritzen [993]概述了着色器使用的寄存器数量和共享内存如何影响占用率。 Wronski [1911,1914]讨论了理想的占用率如何根据着色器执行(performs)的操作类型而变化。

影响整体效率的另一个因素是由“ if”语句和循环引起的动态分支。假设在着色器程序中出现了 “ if”语句。如果所有线程都求值并采用同一分支,则warp可以继续进行而不必担心其他分支。但是,如果某些线程甚至一个线程采用了替代路径,那么warp必须执行两个分支,从而丢弃每个特定线程不需要的结果[530,945]。这个问题称为线程发散(divergence),其中一些线程可能需要执行循环迭代或执行“if”路径,而warp中的其他线程则不这样做,从而使它们在此期间处于空闲状态。

所有GPU都实现了这些架构思想,从而导致系统受到严格的限制,但每瓦的计算能力却很大。了解此系统的运行方式将有助于您作为程序员充分利用其提供的功能。在以下各节中,我们讨论GPU如何实现渲染管线,可编程着色器如何工作以及每个GPU阶段的演变和功能。


图3.1。 简化的着色器执行示例。 三角形的图元(称为线程)被收集成warps。 每个warp显示为四个线程组,但实际上有32个线程。 要执行的着色器程序长五个指令。 四个GPU着色器处理器的集合在第一次warp时执行这些指令,直到在“ txr”命令上检测到停止条件为止,这需要时间来获取其数据。 交换第二个warp,并对其应用着色器程序的前三个指令,直到再次检测到停顿为止。 交换第三个经线并使其停止后,通过交换第一个经线并继续执行,继续执行。 如果此时尚未返回其“ txr”命令的数据,则执行将真正停止,直到这些数据可用为止。 每个warp依次完成。
图3.2。 渲染管线的GPU实现。 这些阶段根据用户对其操作的控制程度进行颜色编码。 绿色阶段是完全可编程的。 虚线表示可选阶段。 黄色阶段是可配置的,但不是可编程的,例如,可以为合并阶段设置各种混合模式。 蓝色阶段的功能完全固定。

3.2 GPU 管线总览

GPU实现了第2章中描述的概念化地几何处理,栅格化和像素处理管线阶段。这些阶段分为几个硬件阶段,这些阶段具有不同程度的可配置性(configurability)或可编程性。图3.2显示了根据各个阶段的可编程性或可配置性对其进行颜色编码的各个阶段。请注意,这些物理阶段的划分(spilt up)与第二章中介绍的功能阶段有所不同。

我们在这里描述GPU的逻辑模型,它是由API作为程序员公开给您的。正如第18章和第23章所讨论的那样,此逻辑管道(物理模型)的实现取决于硬件供应商(vendor)。通过将命令添加到相邻的(adjacent)可编程阶段,可以在GPU上执行逻辑模型中固定功能的阶段。流水线中的单个程序可以分为由独立的子单元执行的元素,也可以由单独的遍历完全执行。逻辑模型可以帮助您推断会影响性能的原因,但不要将其误认为GPU实际实现管道的方式。

顶点着色器是一个完全可编程的阶段,用于实现几何处理阶段。 几何着色器是一个完全可编程的阶段,可在图元的顶点(点,线或三角形)上运行。 它可以用于执行每个图元的着色操作,销毁图元或创建新的图元。 镶嵌级和几何着色器都是可选的,并非所有GPU都支持它们,尤其是在移动设备上。

裁剪,三角形设置和三角形遍历阶段由固定功能的硬件实现。 屏幕映射受窗口和视口设置的影响,在内部形成简单的比例并重新定位。 像素着色器阶段是完全可编程的。 尽管合并阶段不是可编程的,但是它是高度可配置的,可以设置为执行多种操作。 它实现了“合并”功能阶段,负责修改颜色,z缓冲区,混合,模板和任何其他与输出相关的缓冲区。 像素着色器的执行与合并阶段一起构成了第2章中介绍的概念性像素处理阶段。

随着时间的流逝,GPU管道已从硬编码操作演变为增加灵活性和可控制能力。 可编程着色器阶段的引入是这一发展过程中最重要的一步。 下一节将介绍各个可编程阶段的通用功能。

 

3.3 可编程着色器阶段

现代着色器程序使用统一的着色器设计。 这意味着与顶点,像素,几何和细分相关的着色器共享一个公共的编程模型。 在内部,它们具有相同的指令集体系结构(ISA)。 实现此模型的处理器在DirectX中称为通用着色器核心,据说具有此类核心的GPU具有统一的着色器体系结构。 这种架构背后的想法是着色器处理器可以在各种角色中使用,GPU可以根据需要分配它们。 例如,与每个由两个三角形组成的大正方形相比,一组带有小三角形的网格将需要更多的顶点着色器处理。 具有单独的顶点和像素着色器核心池的GPU意味着严格(rigidly)确定了使所有核心繁忙的理想工作分配。 使用统一的着色器内核,GPU可以决定如何平衡加载。

描述整个着色器编程模型已经超出了本书的范围,并且已经有许多文档,书籍和网站。着色器使用类似C的着色语言进行编程,例如DirectX的高级着色语言(HLSL)和OpenGL着色语言(GLSL)。 DirectX的HLSL可以编译为虚拟机字节码,也称为中间语言(IL或DXIL),以提供硬件独立性。中间表示(intermediate)形式还可以允许着色器程序被编译(compiled)和离线存储。驱动程序将此中间语言转换为特定GPU的ISA。控制台编程通常避免中间语言步骤,因为那时系统只有一个ISA。

基本数据类型是32位单精度浮点标量和向量,尽管向量只是着色器代码的一部分,并且如上所述在硬件中不受支持。在现代GPU上,本机还支持32位整数和64位浮点数。浮点向量通常包含诸如位置(xyzw),法线,矩阵行,颜色(rgba)或纹理坐标(uvwq)之类的数据。整数最常用于表示计数器,索引(indices)或位掩码(bitmasks)。还支持聚合(aggergate)数据类型,例如结构,数组和矩阵。

Draw call调用图形API来绘制一组图元,从而使图形管道执行并运行其着色器。 每个可编程着色器阶段都有两种类型的输入:统一输入,其值在整个绘制调用期间保持恒定(但可以在draw calls之间更改),以及变化的输入,即来自三角形顶点或栅格化的数据。 例如,像素着色器可以将光源的颜色提供为统一的值,并且三角形表面的位置的像素变化,因此它也是变化的。 纹理是一种特殊的标准输入,它曾经是应用于表面的彩色图像,但是现在可以认为是任何大型数据数组。

基础虚拟机为不同类型的输入和输出提供特殊的寄存器。 用于标准模型的可用常数寄存器的数量要比用于变化的输入或输出的寄存器的数量大得多。

图3.3。 Shader Model 4.0下的统一虚拟机体系结构和寄存器布局。 每个资源旁边都会显示最大可用数量。 用斜杠分隔的三个数字是指顶点,几何和像素着色器的限制(从左到右)。

发生这种情况是因为变化的输入和输出需要针对每个顶点或像素分别存储,因此对于需要多少个像素有一个自然的限制。 标准的输入存储一次,会使draw call中的所有顶点或像素重复使用。 虚拟机还具有用于暂存空间的通用临时寄存器。 可以使用临时寄存器中的整数值对所有类型的寄存器进行数组索引。 着色器虚拟机的输入和输出如图3.3所示。

图形计算中常见的操作可在现代GPU上高效执行。着色语言通过*和+等运算符行使了最常见的操作(例如加法和乘法)。其余的通过内在函数展现(exposed),例如atan(),sqrt(),log()以及为GPU优化的许多其他函数。还存在用于更复杂操作的功能,例如矢量归一化(normalization)和反射,叉积(cross product),矩阵转置和行列式计算。

流控制这一术语是指使用分支指令(instructions)来更改代码执行流。与流控制相关的指令用于实现高级语言构造,例如“ if”和“ case”语句(statements),以及各种类型的循环。着色器支持两种类型的流控制。静态流量控制分支基于统一输入的值。这意味着代码流在绘图调用(draw call)中是恒定的(constant)。静态(static)流控制的首要好处是允许将相同的着色器用于各种不同的情况(例如,不同数量的灯光)。没有线程分歧(divergence),因为所有调用(invocations)都采用相同的代码路径。动态流量控制基于会变化的输入值,这意味着每个图元可以不同地执行代码。这比静态流控制功能强大得多,但会降低性能,尤其是如果在着色器调用之间代码流不规律地(erratically)更改时。

 

 

 

 

 

 

3.4 可编程着色和API的演变

可编程着色框架的构想可以追溯到1984年,当时库克(Cook)的着色树[287]。 一个简单的着色器及其对应的(corresponding)着色树如图3.4所示。 RenderMan着色语言[63,1804]是在1980年代后期从这个想法发展而来的。 如今,它仍然被用于电影制作渲染,伴随着其他不断发展(evolving)的规范(specifications),例如开源着色语言(OSL)项目[608]。

       消费级图形硬件是3dfx Interactive于1996年10月1日首次成功引入的。从那一年起的时间表请参见图3.5。 他们的Voodoo图形卡能够以高品质和高性能渲染《Quake》游戏,因此很快就被采用(adoption)。 该硬件从始至终贯彻了固定功能的流水线。 在GPU原生地(natively)支持可编程着色器之前,曾有过几次尝试通过多次渲染实时实现可编程着色操作的尝试。 Quake III:Arena脚本语言1999年在该领域获得首个广泛(widespread)商业成功。如本章开头所述,NVIDIA的GeForce256是第一个被称为GPU的硬件,但它不是可编程的。但是,它是可配置的。


图3.4。 一个简单的黄铜着色器的着色树及其相应的着色器语言程序。 (在库克[287]之后。)
在2001年初,NVIDIA的GeForce 3是第一个支持可编程顶点着色器[1049]的GPU,可通过DirectX 8.0和OpenGL扩展进行展现。 这些着色器以一种类似于(asSMebly-like)汇编的语言进行编程,该语言被驱动程序即时转换为微代码。 像素着色器也包含在DirectX 8.0中,但是像素着色器没有达到实际的可编程性-驱动程序将受支持的有限“程序”转换为纹理混合状态,然后将其连接到硬件“寄存器组合器”中。 这些“程序”不仅受限于长度(不超过12条指令),而且缺少重要的功能性。 Peercy等人[1363]确定了相关的纹理读取(reads)和浮点数据对于真正的可编程性至关重要性,这项结果来自他们对RenderMan的研究。 
着色器在那个时侯不允许进行流控制(分支),因此必须通过计算两个项以及在结果之间进行选择或插值来模拟条件(conditionals)。 DirectX定义了着色器模型(Shader Model,SM)的概念,以区分具有不同着色器功能的硬件。 2002年,包括Shader Model 2.0在内的DirectX 9.0发行(release)了,它具有真正可编程的顶点和像素着色器。在OpenGL下使用各种扩展名(extensions)同样彰显了类似的功能。添加了对任意相关纹理读取的支持以及16位浮点值的存储,最终完成了Peercy等人确定的一组要求。着色器资源(如指令,纹理和寄存器)的限制被增加了,因此着色器变得能够(capable)具有更复杂的效果。还增加了对流量控制的支持。着色器的长度和复杂性不断增长,使得汇编编程模型变得越来越繁琐(cumbersome)。幸运的是,DirectX 9.0还包含HLSL。这种着色语言是由Microsoft与NVIDIA合作开发的。大约在同一时间,OpenGL ARB(架构审查委员会)发布了GLSL,一种与OpenGL相当相似的语言[885]。这些语言在很大程度上受到C编程语言的语法(syntax)和设计理念的影响,其中包括来自RenderMan着色语言的元素。 
Shader Model 3.0于2004年推出,并添加了动态流控制,使着色器功能显著地更加强大。 它还将可选功能转变为需求,进一步增加了资源限制,并增加了对顶点着色器中纹理读取的有限支持。 当在2005年末(微软的Xbox 360)和2006年末(Sony Computer Entertainment的PLAYSTATION 3系统)推出新一代游戏机时,它们都配备了Shader Model 3.0级GPU。 任天堂的Wii主机是最后一世代的著名的(notable)固定功能GPU之一,该GPU最初于2006年底交付。纯粹意义的固定功能管线在这之后消失了。 着色器语言已经发展到可以使用各种工具来创建和管理它们的程度。 图3.6显示了使用库克(Cook)的着色树概念的一种此类工具的屏幕截图。
图3.6。 用于着色器设计的可视化着色器图形系统。 各种操作封装(encapsulated)在功能框中,可在左侧选择。 选中后,每个功能框都有可调参数,如右图所示。 每个功能框的输入和输出相互链接以形成最终结果,如中间网格的右下方所示。 (摘自“ mental mill”,mental images inc。) 
 

可编程性的下一步又迈进了2006年底。DirectX10.0 [175]中包含的Shader Model 4.0引入了几个主要功能,例如几何体着色器和流输出。 Shader Model 4.0包括适用于所有着色器(顶点,像素和几何图形)的统一编程模型,这是前面描述的统一着色器设计。 资源限制进一步增加,并增加了对整数数据类型(包括按位运算)的支持。 OpenGL 3.3中GLSL 3.30的引入提供了类似的着色器模型。

2009年发布了DirectX 11和Shader Model 5.0,其中添加了细分阶段着色器和计算着色器,也称为DirectCompute。该版本还专注于更有效地支持CPU多处理,这是第18.5节中讨论的主题。 OpenGL在版本4.0中添加了细分,并在4.3中添加了计算着色器。 DirectX和OpenGL的发展不同。两者都设置了特定版本发行所需的特定级别的硬件支持。 Microsoft控制DirectX API,因此直接与独立硬件供应商(IHV)(例如AMD,NVIDIA和Intel)以及游戏开发人员和计算机辅助设计软件(CAD)公司合作,以确定要展现的功能。 OpenGL由非营利组织Khronos Group管理的硬件和软件供应商联盟开发。由于涉及的公司数量众多,因此API功能通常在DirectX中引入OpenGL之后的某个时间出现。但是,OpenGL允许供应商(vendors)特制的扩展或更广泛的(consortium)扩展,这些扩展允许在版本正式支持之前使用最新的GPU功能。

API的下一个重大变化是由AMD在2013年推出了MantleAPI。Mantle与电脑游戏开发商DICE合作开发,其目的是消除(strip out)大部分图形驱动程序的不必要开销(overhead),并将此控件直接交给开发人员。除了这种重构之外,还进一步支持有效的CPU多处理。这类新的API集中在大大减少CPU在驱动程序上花费的时间以及更有效的CPU多处理器支持(第18章)。在Mantle中开创的构想被Microsoft采纳,并在2015年以DirectX 12的形式发布。请注意,DirectX 12并不专注于公开新的GPU功能-DirectX 11.3公开了相同的硬件功能。两种API均可用于将图形发送到虚拟现实系统,例如Oculus Rift和HTC Vive。但是,DirectX 12是API的彻底重新设计,可以更好地映射到现代GPU架构。低开销(overhead)的驱动程序对于以下应用程序很有用:CPU驱动程序成本引起瓶颈,或者使用更多CPU处理器进行图形处理可能会提高性能[946]。从较早的API移植(porting)可能很困难,并且单纯的实现可能会导致性能降低[249、699、1438]。

苹果在2014年发布了自己的低开销API(称为Metal)。Metal首次在iPhone 5S和iPad Air等移动设备上可用,一年后可以通过OS X El Capitan访问较新的Macintoshes。除效率外,减少CPU使用率还可以节省功耗,这是移动设备上的重要因素。该API具有自己的着色语言,适用于图形和GPU计算程序。

AMD将其Mantle工作捐赠给了Khronos Group,后者于2016年初发布了自己的新API,名为Vulkan。与OpenGL一样,Vulkan可在多个操作系统上工作。 Vulkan使用一种称为SPIRV的新高级中间语言,该语言可用于着色器表示和一般GPU计算。预编译的(precompiled)着色器是可移植的,因此可以在支持所需功能的任何GPU上使用[885]。 Vulkan也可以用于非图形GPU计算,因为它不需要显示窗口[946]。 Vulkan与其他低开销驱动程序的显着区别是,它旨在与从工作站到移动设备的各种系统一起使用。

在移动设备上,规范是使用OpenGL ES。 “ ES”代表嵌入式系统,因为此API是为移动设备而开发的。当时的标准OpenGL在其某些调用结构中相当庞大(bulky)且缓慢,并且需要支持很少使用的功能。 OpenGL ES 1.0于2003年发布,是OpenGL 1.3的简化版本,描述了固定功能的管道。虽然DirectX的发布与支持它们的图形硬件的发布是同步的,但是开发针对移动设备的图形支持的方式却并不相同。例如,2010年发布的第一台iPad实施了OpenGL ES 1.1。 OpenGL ES 2.0规范于2007年发布,提供了可编程的着色。它基于OpenGL 2.0,但没有固定功能组件,因此与OpenGL ES 1.1不向后兼容(compatible)。 OpenGL ES 3.0于2012年发布,提供多种功能,例如多个渲染目标,纹理压缩(compression),变换反馈,实例化,更广泛的纹理格式和模式,以及着色器语言改进。 OpenGL ES 3.1添加了计算着色器,而3.2添加了几何和细分着色器,以及其他功能。第23章将更详细地讨论移动设备架构。 OpenGL ES的一个分支是基于浏览器的API WebGL,可通过JavaScript调用。该API的第一版发布于2011年,可在大多数移动设备上使用,因为它的功能等效于OpenGL ES 2.0。与OpenGL一样,扩展允许访问更高级的GPU功能。 WebGL 2假定支持OpenGL ES 3.0。

OpenGL ES的一个分支是基于浏览器的API WebGL,可通过JavaScript调用。该API的第一版发布于2011年,可在大多数移动设备上使用,因为它的功能等效(equivalent)于OpenGL ES 2.0。与OpenGL一样,扩展允许访问更高级的GPU功能。 WebGL 2假定支持OpenGL ES 3.0。

WebGL特别适合在教室中试用功能或使用:

•它是跨平台的,可在所有个人计算机和几乎所有移动设备上使用。

•驱动程序批准由浏览器处理。即使一个浏览器不支持特定的GPU或扩展,通常另一个浏览器也支持。

•代码是解释性的(interpreted),而不是编译的,并且仅需要文本编辑器即可进行开发。

•大多数浏览器都内置了调试器,可以检查在任何网站上运行的代码。

•可以通过将程序上传到网站或Github来进行部署。

更高级别的场景图形和效果库(例如three.js [218])使您可以轻松访问代码,以获取各种更复杂的效果,例如阴影算法,屏幕后处理效果,基于物理的着色和延迟渲染。

 

 

 

 

 

3.5顶点着色器

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

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

图3.7。 三角形曲面(黑色,具有顶点法线)的侧视图,代表曲面(红色)。 在左侧,平滑的顶点法线用于表示平滑表面。 在右侧,中间顶点已被复制并指定了两个法线,表示折痕。

顶点着色器是处理三角形网格的第一阶段。 顶点着色器无法获得描述形成了哪些三角形的数据。 顾名思义,它专门(exclusively)处理传入的顶点。 顶点着色器提供了一种修改,创建或忽略与每个三角形的顶点关联的值的方法,例如其颜色,法线,纹理坐标和位置。 通常,顶点着色器程序会将顶点从模型空间转换为同构剪辑空间(第4.7节)。 顶点着色器至少必须始终输出此位置。

顶点着色器与前面描述的统一着色器几乎相同。 传入的每个顶点都由顶点着色器程序处理,该程序然后输出在三角形或直线上插值的多个值。 顶点着色器既不能创建也不可以破坏顶点,并且一个顶点生成的结果不能传递到另一顶点。 由于每个顶点都是独立处理的,因此可以将GPU上任意数量的着色器处理器并行应用于传入的顶点流。

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

•对象生成,通过仅创建一次网格并使其由顶点着色器变形即可。

•使用蒙皮和变形技术对角色的身体和面部进行动画处理。

•程序变形,例如旗帜,布料或水的移动[802, 943]。

•通过发送退化的(无区域)网格沿管线生成粒子,并根据需要为其分配区域。

•通过将整个帧缓冲区的内容用作经受程序变形(deformation)的屏幕对齐(aligned)网格上的纹理,可以使镜头变形(Lens distortion),热雾,水波纹,页面卷曲和其他效果。

•通过使用顶点纹理获取[40,1227]应用地形(terrain)高度场。

 

使用顶点着色器完成的一些变形如图3.8所示。 顶点着色器的输出可以通过几种不同的方式使用。 然后为每个实例的图元(例如三角形)生成和光栅化常用路径,并将生成的各个像素片段发送到像素着色器程序以进行继续处理。 在某些GPU上,数据也可以发送到细分阶段或几何着色器,或存储在内存中。 这些可选阶段将在以下各节中讨论。








图3.8。 左边是一个普通的茶壶。 由顶点着色器程序执行的简单剪切拉伸()操作将生成中间图像。 在右侧,噪声函数会创建一个使模型失真的字段。 (图像由FX Composer 2制作,由NVIDIA Corporation提供。) 

3.6细分阶段

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

使用细分阶段有几个优点。曲面描述通常比提供相应的三角形本身更紧凑。除了节省内存外,此功能还可以防止CPU和GPU之间的总线成为其形状在改变每一帧的动画角色或对象的瓶颈。通过为给定视图生成适当数量的三角形,可以有效地渲染表面。例如,如果一个球远离相机,则仅需要几个三角形。近距离观察时,最好用数千个三角形来表示。这种控制细节水平的能力还可以使应用程序控制其性能,例如,在较弱的GPU上使用较低质量的网格以保持帧速率。通常用平坦表面表示的模型可以转换为三角形的细网格,然后根据需要进行变形[1493],或者可以对其进行细分,以便更不频繁地执行昂贵的着色计算[225]。

细分阶段始终由三个元素组成。使用DirectX的术语(terminology),它们是外壳 (hull)着色器,曲面细分器和域着色器。在OpenGL中,外壳着色器是细分控制着色器,而领域着色器是细分评估着色器,虽然繁琐(verbose),但更具描述的准确性(descriptive)。固定功能细分器在OpenGL中被称为图元生成器,就如可以看到,它确实是这么工作的。

在第17章中详细讨论了如何具体化和细分曲面和曲线。在这里,我们简要概述了每个细分阶段的目的。 首先,外壳(hull)着色器的输入是一个特殊的修补(patch)程序基元。 它由几个控制点组成,这些控制点定义了细分曲面,B'ezier贝塞尔面片或其他类型的弯曲元素。 外壳着色器具有两个功能。 首先,它告诉细分器应生成多少个三角形以及以何种配置生成。其次,它对每个控制点执行处理。 同样,可选地,外壳着色器可以修改传入(incoming)的补丁说明,根据需要添加或删除控制点。外壳着色器将其控制点集以及细分控制数据输出到域着色器。 参见图3.9。

 

 

图3.9。 细分阶段。 外壳着色器采用由控制点定义的面片。 它将细分因子(TF)和类型发送给固定功能细分器。 控制点集由外壳着色器根据需要进行转换,并与TF和相关的面片程序常量一起发送到域着色器。细分对象将创建一组顶点及其重心(barycentric)坐标。 然后由域着色器对其进行处理,从而生成三角形网格(显示控制点以供参考)。

细分器是管线中的固定功能阶段,仅与细分着色器一起使用。它的任务是添加多个新顶点以供域着色器处理。外壳着色器向细分器发送有关所需细分曲面类型的信息:三角形,四边形(quadrilateral)或等值线(isoline)。等值线是线带的集合(strips),有时用于渲染头发[1954]。外壳着色器发送的其他重要值是曲面细分因子(OpenGL中的曲面细分级别)。它们有两种类型:内边缘和外边缘。这两个内部因素决定了三角形或四边形内部发生了多少细分。外部因素决定每个外部边缘被分割多少(第17.6节)。图3.10显示了增加细分因子的示例。通过允许使用单独的控件,我们可以使相邻曲面的边缘在细分中匹配,而不考虑内部是如何细分的。匹配的边缘可避免在面片相遇之处出现裂缝或其他着色瑕疵。顶点被分配了重心坐标(第22.8节),这些值具体化了所需曲面上每个点的相对位置。

 

 



 

外壳着色器始终输出面片,一组控制点位置。但是,它可以通过向细分器发送零或更低(或非数字,NaN)的外部细分水平来发出要舍弃这些片面。否则,细分器将生成网格并将其发送到域着色器。领域着色器的每次调用(invocation)都使用来自外壳着色器的曲面的控制点来计算每个顶点的输出值。域着色器具有类似于顶点着色器的数据流模式,其中处理来自细分的每个输入顶点并生成相应的输出顶点。然后将形成的三角形沿管线向下传递。

图3.10。 改变细分因子的效果。 犹他州茶壶由32个小块组成。 内,外细分因子从左到右分别为1、2、4和8(Rideout和Van Gelder [1493]通过演示生成的图像。)

 尽管此系统听起来很复杂,但为提高效率而采用这种结构,每个着色器可能都非常简单。传递到外壳着色器中的面片通常很少或根本不会遭到(undergo)修改。该着色器还可以使用面片的估计(estimated)距离或屏幕大小来动态即时(on the fly)计算细分因子,就像地形渲染一样[466]。或者,外壳着色器可以简单地为应用程序计算和提供的所有面片传递一组固定的值。细分器执行一个涉及但固定功能的过程,生成顶点,为其指定位置并指定它们形成的三角形或直线。此数据扩大化(amplification)步骤是在着色器外部执行的,以提高计算效率[530]。域着色器采用为每个点生成的重心坐标,并在面片的评估方程式中使用它们,以生成位置,法线,纹理坐标以及所需的其他顶点信息。有关示例,请参见图3.11。

图3.11。 左侧是大约6000个三角形的基础网格。 在右侧,使用PN三角形细分对每个三角形进行细分和置换。 (图像由NVIDIA公司提供,来自NVIDIA SDK 11 [1301]的示例,由4A Games在Metro 2033中提供。)

 

图3.12。 几何着色器程序的几何着色器输入为某些单一类型:点,线段,三角形。 最右边的两个图元包括与直线和三角形对象相邻的顶点。 更详细的面片类型都是可能的。

 

 

3.7几何着色器

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

几何着色器的输入是单个对象及其关联的顶点。对象通常由带状(strip),线段或简单的点中的三角形组成。扩展图元可以由几何着色器定义和处理。特别是,可以传入三角形外部的三个附加顶点,并且可以使用折线上的两个相邻顶点。参见图3.12。使用DirectX 11和Shader Model 5.0,您可以传入多达32个控制点的更详尽的面片程序。也就是说,细分阶段更高效地面片生成[175]。

几何着色器处理此图元并输出零个或多个顶点,这些顶点被视为点,折线或三角形带。 请注意,几何着色器根本无法生成任何输出。 通过这种方式,可以通过编辑顶点,添加新图元并删除其他图元来选择性地修改网格。

几何着色器设计用于修改传入的数据或制作有限数量的副本。 例如,一种用途是生成六个转换后的数据副本,以同时渲染立方体贴图的六个面; 参见第10.4.3节。 它也可以用来有效地创建级联的(cascaded)阴影贴图,以生成高质量的阴影。 利用几何着色器的其他算法包括从点数据创建变量化的粒子,沿轮廓(silhouettes)拉伸鳍以进行毛发渲染以及为阴影算法找到对象边缘。 有关更多示例,请参见图3.13。 这些和其他用途将在本书的其余部分中讨论。

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

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


图3.13。 几何着色器(GS)的某些用途。 在左侧,使用GS实时进行元球等值面(isosurface)细分。 在中间,使用GS完成线段的分形细分并将其流出,并且GS生成广告牌以显示雷电。 在右侧,通过使用流出的顶点和几何着色器执行布料模拟。 (图片由NVIDIA公司提供,来自NVIDIA SDK 10 [1300]示例。)

发出绘制调用后,管线中只有三个位置可以在GPU上创建工作:栅格化,细分阶段和几何体着色器。 其中,考虑到所需的资源和内存,几何着色器的行为是最不可预测的,因为它是完全可编程的。 实际上,几何着色器通常使用很少,因为它无法很好地匹配到GPU的强项。 在某些移动设备上,它是通过软件实现的,因此在此积极地建议不要使用它[69]。

 

 

3.7.1流输出

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

流输出仅以浮点数的形式返回数据,因此可能会产生明显的内存开销。流输出在图元上起作用,而不是在顶点上起作用。如果将网格沿管线发送,则每个三角形将生成自己的三个输出顶点集。原始网格中共享的所有顶点都将丢失。因此,更典型的用途是仅通过管道发送顶点作为点集图元。在OpenGL中,流输出阶段称为变换反馈,因为它的大部分使用重点是变换顶点并将其返回以进行进一步处理。保证按输入顺序将基元发送到流输出目标,这意味着将保持顶点顺序[530]。

 

 

3.8像素着色器

如上一章所述,在顶点,镶嵌和几何体着色器执行完操作之后,将裁剪并设置图元以进行栅格化。 流水线的这一部分在其处理步骤中是相对固定的,即,不是可编程的而是有些可配置的。 遍历每个三角形以确定其覆盖哪些像素。 光栅化器还可以粗略计算出三角形覆盖每个像素的像元区域的数量(第5.4.2节)。 部分或完全重叠像素的三角形称为片元。

三角形顶点的值(包括z缓冲区中使用的z值)在每个像素的三角形表面上插值。这些值将传递到像素着色器,然后由其处理片段。在OpenGL中,像素着色器称为片段着色器,这也许是一个更好的名称。为了保证一致性,我们在本书中始终使用“像素着色器”。沿管线发送的点和线图元也会为所覆盖的像素创建片段。


 

跨整个三角形执行的插值类型由像素着色器程序指定。通常,我们使用透视校正内插法,以便像素表面位置之间的世界空间距离随着对象后退距离的增加而增加。一个示例是渲染延伸到地平线的铁轨。铁轨在铁轨较远的地方间隔更近,因为每个接近像素的行进距离都越来越远。其他插值选项也可用,例如屏幕空间插值,其中不考虑透视投影。 DirectX 11可进一步控制何时以及如何执行插值[530]。

用编程术语来说,顶点着色器程序的输出(插在三角形(或线)上)有效地成为了像素着色器程序的输入。 随着GPU的发展,其他输入也已公开。 例如,片段的屏幕位置可用于Shader Model 3.0及更高版本中的像素着色器。此外,三角形的哪一侧可见是输入标志。 该知识对于一次通过每个三角形的正面和背面渲染不同的材质非常重要。

图3.14。 用户定义的剪切平面。 在左侧,单个水平裁剪平面将对象切片。 在中间,嵌套的球体被三个平面修剪。 在右侧,仅当球体的曲面在所有三个剪切平面的外部时才对其进行剪切。 (来自Three.js示例中的webgl裁剪和webgl裁剪交集[218]。)

 

有了输入,通常像素着色器会计算并输出片段的颜色。它还可能会产生不透明度值,并可以选择修改其z深度。在合并期间,这些值用于修改存储在像素处的内容。光栅化阶段生成的深度值也可以由像素着色器修改。模板缓冲值通常是不可修改的,而是传递到合并阶段。 DirectX 11.3允许着色器更改此值。雾计算和alpha测试等操作已从SM 4.0中的合并操作变为像素着色器计算[175]。

像素着色器还具有丢弃传入片元(即不生成任何输出)的独特功能。图3.14显示了如何使用碎片丢弃的一个示例。剪切平面功能曾经是fixed function管道中的可配置元素,后来在顶点着色器中指定。有了片元丢弃功能之后,就可以用像素着色器中所需的任何方式来实现此功能,例如确定剪切量应一起进行“与”运算或“或”运算。

最初,像素着色器只能输出到合并阶段,以进行最终显示。 随着时间的推移,像素着色器可以执行的指令数量已大大增加。 这种增加引起了多个渲染目标(MRT)的想法。 不仅可以将像素着色器程序的结果仅发送到颜色和z缓冲区,还可以为每个片段生成多组值并将其保存到不同的缓冲区,每个缓冲区称为渲染目标。 渲染目标通常具有相同的x和y维度; 有些API允许使用不同的大小,但是渲染区域将是其中最小的。 一些架构要求渲染目标必须具有相同的位深,甚至可能具有相同的数据格式。 取决于GPU,可用的渲染目标数量为四个或八个。

即使有这些限制,MRT功能仍可以有效地执行渲染算法。一次渲染过程可以在一个目标中生成彩色图像,在另一个目标中生成对象标识符,在第三个目标中生成世界空间距离。此功能还引起了另一种类型的渲染管道,称为延迟着色,其中可见性和着色是在单独的通道中完成的。第一遍存储有关对象在每个像素处的位置和材质的数据。然后,连续通过可以有效地施加照明和其他效果。此类渲染方法在第20.1节中进行了描述。

像素着色器的局限性在于,它通常只能在传递给目标的片段位置上写入渲染目标,而不能从相邻像素读取当前结果。也就是说,执行像素着色器程序时,它无法将其输出直接发送到相邻像素,也无法访问其他人的最新更改。而是,它计算仅影响其自身像素的结果。但是,此限制并不像听起来那样严重。一遍创建的输出图像可以在下一遍中由像素着色器访问其任何数据。可以使用第12.1节中所述的图像处理技术来处理相邻像素。

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

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

图3.15。 在左侧,三角形被栅格化为四边形,每组2×2像素。 然后,在右侧显示带有黑点标记的像素的梯度计算。 针对四边形中四个像素位置的每一个,显示了v的值。 请注意,三角形中没有覆盖三个像素,但是GPU仍对它们进行了处理,以便可以找到渐变。 通过使用左下像素的两个四边形邻居,可以计算x和y屏幕方向上的渐变。 

通常需要某种机制来避免数据争用情况(也称为数据危险),在这种情况下,两个着色器程序都在“竞相”以影响相同的值,从而可能导致随机的结果。 例如,如果像素着色器的两次调用试图在大约相同的时间添加到相同的检索值,则可能会发生错误。 两者都将检索原始值,都将在本地对其进行修改,但是无论哪个调用最后写入其结果都将抹去另一个调用的作用-仅会发生一次加法。 GPU通过使用着色器可以访问的专用原子单元来避免此问题[530]。 但是,原子意味着某些着色器可能在等待访问另一个着色器进行读/修改/写操作的存储位置时停滞。

尽管原子避免了数据危害,但是许多算法都需要特定的执行顺序。例如,您可能需要绘制一个距离更远的透明蓝色三角形,然后再将其覆盖在红色透明三角形上,然后将红色混合在蓝色上面。一个像素可能对一个像素进行两次像素着色器调用,每个三角形调用一次,以这样一种方式执行:红色三角形的着色器在蓝色之前完成。在标准管道中,片元结果将在合并阶段进行排序,然后再进行处理。在DirectX 11.3中引入了栅格化程序顺序视图(ROV)以强制排列执行顺序。这些就像UAV一样。着色器可以以相同的方式读取和写入它们。关键区别在于ROV保证以正确的顺序访问数据。这大大增加了这些着色器可访问缓冲区的实用性[327、328]。例如,ROV使像素着色器可以编写自己的混合方法,因为它可以直接访问和写入ROV中的任何位置,因此不需要合并阶段[176]。代价是,如果检测到乱序访问,像素着色器调用可能会停顿,直到处理了先前绘制的三角形。


 


3.9 混合阶段

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

想象一下,通过光栅化生成的片段通过像素着色器运行,然后在应用zbuffer时被某些先前渲染的片段隐藏。这样就不需要在像素着色器中进行所有处理。为了避免这种浪费,许多GPU在执行像素着色器之前执行一些合并测试[530]。片段的z深度(以及其他正在使用的东西,例如模板缓冲区或剪断(scissoring))用于测试可见性。如果隐藏该片段,则将其剔除。此功能称为Early-z [1220,1542]。像素着色器具有更改片段的z深度或完全丢弃片段的能力。如果发现像素着色器程序中存在这两种类型的操作,则通常无法使用Early-Z,然后通常将其关闭,这通常会使管线效率降低。 DirectX 11和OpenGL 4.2允许像素着色器强制进行早期z测试,尽管有很多限制[530]。有关早期z和其他z缓冲区优化的更多信息,请参见第23.7节。有效使用Early-z会对性能产生很大影响,这将在18.4.5节中详细讨论。

合并阶段占据了固定功能阶段(例如三角形设置)和完全可编程着色器阶段之间的中间地带。尽管它不是可编程的,但其操作是高度可配置的。可以特别设置颜色混合以执行大量不同的操作。最常见的是涉及颜色和Alpha值的乘法,加法和减法的组合,但是其他操作(例如最小值和最大值)以及按位逻辑运算也是可能的。 DirectX 10添加了将像素着色器中的两种颜色与帧缓冲区颜色混合的功能。此功能称为双源颜色 混合,不能与多个渲染目标一起(conjunction)使用。 MRT确实支持混合,DirectX 10.1引入了在每个单独的缓冲区上执行不同混合操作的功能。

如上一节末尾所述,DirectX 11.3提供了一种通过ROV进行混合编程的方法,尽管这是以性能为代价的。 ROV和合并阶段均保证绘制顺序,也就是输出不变性。不论生成像素着色器结果的顺序如何,API要求都按照输入结果的顺序对结果进行排序,并将其发送到合并阶段,即对象按对象,三角形按三角形。

 


 

 

 

3.10 计算着色器

除了实现传统的图形管线之外,GPU还可以用于更多用途。在许多领域中,非图形用途广泛,例如计算股票期权的估计价值和训练用于深度学习的神经网络。以这种方式使用硬件称为GPU计算。诸如CUDA和OpenCL之类的平台可作为大型并行处理器来控制GPU,而无需真正的需求或访问特定于图形的功能。这些框架通常使用带有扩展功能的语言(例如C或C ++)以及为GPU制作的库。

DirectX 11中引入了计算着色器,它是GPU计算的一种形式,因为它是未锁定在图形管道中某个位置的着色器。它与渲染过程密切相关,因为它是由图形API调用的。它与顶点,像素和其他着色器一起使用。它使用与管道中使用的统一着色器处理器池相同的池。与其他着色器一样,它是着色器,因为它具有一组输入数据,并且可以访问缓冲区(例如纹理)以进行输入和输出。在计算机着色器中,warp和线程更明显。例如,每个调用都会获得一个可以访问的线程索引。还有一个线程组的概念,它由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],以及可以承担(bear)一组GPU处理器的任何其他任务。 Wihlidal [1884]讨论了计算着色器如何比曲面细分外壳着色器更有效。其他用途请参见图3.16。

至此,我们对GPU的渲染管线实现的回顾结束了。 有多种方法可以使用和组合GPU功能来执行各种与渲染相关的过程。 调整以利用这些功能的相关理论和算法是本书的中心主题。 现在,我们将重点放在变换和着色上。


图3.16。 计算着色器示例。 左侧是计算着色器,用于模拟受风影响的头发,并使用细分阶段渲染头发本身。 在中间,计算着色器执行快速模糊操作。 在右侧,模拟了海浪。 (图片来自NVIDIA SDK 11 [1301]示例,由NVIDIA Corporation提供。)

 

 

进一步的阅读和资源

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

要学习着色器编程的形式方面,需要花费一些工作。 诸如OpenGL Superbible [1606]和OpenGL Programming Guide [885]之类的书都包含有关着色器编程的资料。 较老的书籍OpenGL Shading Language [1512]并未涵盖较新的着色器阶段,例如几何和细分着色器,但确实专注于与着色器相关的算法。 有关最新和推荐的图书,请参见本书的网站realtimerendering.com。

 


投诉或建议

玻璃钢生产厂家台州特色玻璃钢雕塑订做价格浙江走廊商场美陈厂家直销广东led发光玻璃钢雕塑现货玻璃钢雕塑历史名人玻璃钢雕塑内部支架示意图佛山玻璃钢松鼠雕塑玻璃钢雕塑设计哪家便宜商场巨型蛋糕模型美陈稳定的玻璃钢雕塑卡通江门玻璃钢造型雕塑厂时尚玻璃钢泡沫雕塑定制广东玻璃钢卡通雕塑辽阳玻璃钢雕塑周年商场美陈现价玻璃钢大型雕塑寿命深圳欧式人物玻璃钢雕塑玻璃钢卡通雕塑断了怎么处理韶关玻璃钢香蕉雕塑珠海玻璃钢西部牛仔雕塑上海欧式玻璃钢雕塑价位泰州玻璃钢花盆厂家玻璃钢心形雕塑南京玻璃钢雕塑摆件制造苏州景观灯玻璃钢花盆铜玻璃钢卡通雕塑制作厂家深圳园林玻璃钢雕塑定制人物玻璃钢雕塑一般多少钱佛山玻璃钢三国人物雕塑沧州人物玻璃钢雕塑安装长兴玻璃钢雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化