|
“The display is the computer.” —Jen-Hsun Huang 从历史上看,图形加速始于每条像素扫描线覆盖到三角形后,对颜色进行插值并显示颜色值的时候。此中包罗访谒图像数据的功能,可以将纹理应用于概况。此中还添加了用于插值和深度检测的硬件,提供了内置的可见性查抄。由于它们的频繁使用,所以让此类过程使用专用硬件以提高性能。在后续几代硬件中,添加了衬着管线的更多部门,以及每个部门的更多功能。专用图形硬件相对于CPU的独一计算优势是速度,但速度至关重要。
在过去的二十年里,图形硬件经历了一次令人难以置信的改变。第一款包含硬件顶点措置的消费级图形芯片(NVIDIA 的 GeForce256)于 1999 年发布。NVIDIA 缔造了图形措置单元(GPU)这个术语来区分 GeForce 256 和之前仅提供光栅化功能的芯片,这个术语也传布下来。在接下来的几年里,GPU 从可配置的复杂固定功能管线实现演变为高度可编程的空白画板,开发人员可以在此中实现本身的算法。各种类型的可编程着色器是控制 GPU 的主要手段。为了提高效率,管线中某些部门仍然是可配置而非可编程,但趋势是朝着可编程性和灵活性的标的目的成长。
GPU 之所以能够拥有如此高的速度,是因为它专注于一组狭窄的、高度可并行化的任务。它们拥有专用的芯片,用于实现 z 缓冲区、快速访谒纹理图像和其他缓冲区、并找到哪些像素被三角形覆盖等。这些元素如何执行它们的功能在第 23 章中介绍。更重要的是要早点了解 GPU 如何实现其可编程着色器的并行性。
第 3.3 节解释了着色器的功能。此刻,您需要知道的是,着色器核心是一个小型措置器,它执行一些相对独立的任务,例如将顶点从世界坐标系中的位置转换为屏幕坐标,或计算被三角形覆盖的像素的颜色。每帧发送到屏幕上的三角形数量达到数千甚至数百万个,每秒钟都有数十亿次着色器调用,即着色器法式的运行实例。
首先,延迟是所有措置器都面临的问题。访谒数据需要一按时间。关于延迟的基本思路是信息离措置器越远,等待时间就越长。第 23.3 节更详细地介绍了延迟。存储在内存芯片中的信息比存储在当地寄存器中的信息访谒时间更长。第 18.4.1 节更深入地讨论了内存访谒。关键点是等待数据检索意味着措置器停滞不前,这会降低性能。
3.1 数据并行式架构(Data-Parallel Architectures)
分歧的措置器架构使用分歧的策略来避免停顿。CPU被优化以措置各种数据布局和大型代码库。CPU可以有多个措置器,但每个措置器都以基本串行的方式运行代码,有限的SIMD向量措置是个小例外。为了最大限度地减少延迟的影响,CPU芯片的大部门由快速的当地缓存组成,这些缓存存储了接下来可能需要的数据。CPU还通过使用诸如分支预测、指令重排、寄存器重定名和缓存预取等巧妙技术来避免停顿。
GPU采用分歧的方式。GPU芯片的大部门面积都用于一个大型措置器调集,称为着色器核心,凡是数量达到数千个。GPU是一种流措置器,此中有序的相似数据集依次被措置。由于这种相似性(例如一组顶点或像素),GPU可以以大规模并行的方式措置这些数据。另一个重要因素是这些调用尽可能是独立的,这样它们就不需要来自相邻调用的信息,而且不共享可写入的内存位置。有时会打破这条法则以允许新的和有用的功能,但这样的例外会付出潜在延迟的代价,因为一个措置器可能会等待另一个措置器完成其工作。
GPU针对吞吐量(其定义为数据能够被措置的最大速率)进行了优化。然而,这种快速措置是有代价的。由于芯单方面积顶用于缓存内存和控制逻辑的部门较少,每个着色器核心的延迟凡是比CPU措置器遇到的延迟要高得多。
假设一个网格被光栅化,有两千个像素的片元需要措置;像素着色器法式将被调用两千次。想象只有一个着色器措置器,世界上最弱的GPU。它开始执行第一个片段的着色器法式。着色器措置器对寄存器中的值执行几个算术运算。寄存器是当地的而且快速访谒,因此不会发生停顿。然后,着色器措置器遇到诸如纹理访谒之类的指令;例如,对于给定的概况位置,法式需要知道应用于网格的图像的像素颜色。纹理是一个完全独立的资源,并不是像素法式当地内存的一部门,而且纹理访谒可能涉及一些操作。内存获取可能需要数百到数千个时钟周期,在此期间GPU措置器什么都不做。此时,着色器措置器将停顿,等待返回纹理颜色值。
为了将这个糟糕的GPU变得更好,为每个片元提供一点存储空间来存储它的当地寄存器。此刻,着色器措置器不再在纹理获取时停顿,而是允许切换并执行另一个片元——第二个片元。这种切换非常快速,对第一个或第二个片元中没有任何影响,除了记录执行了第一个指令之外。此刻执行第二个片元。与第一个不异,执行几个算术函数,然后再次遇到纹理获取。着色器核心此刻切换到另一个片元,第三个。最终以这种方式措置所有两千个片元。此时着色器措置器返回到第一个片元。此时纹理颜色已经被获取并可用于使用,因此着色器法式可以继续执行。措置器以不异的方式进行操作,直到遇到另一个已知会阻止执行的指令或法式完成。 单个片元的执行时间会比着色器措置器专注于该任务要长,但整体上整个片元的执行时间将大大缩短。
在这种架构中,通过让GPU通过切换到另一个片元以保持忙碌来隐藏延迟。GPU将这种设计进一步成长,将指令执行逻辑与数据分隔。称为单指令流大都据流(SIMD),这种放置在固定数量的着色器法式上锁定执行不异的命令。SIMD的长处是,与使用单独的逻辑和调剂单元运行每个法式对比,需要更少的芯片(和功率)用于措置数据和切换。将我们的两千个片元的示例转换为现代GPU术语,每个片元的像素着色器调用称为线程。这种类型的线程与CPU线程分歧。它包罗一些内存,用于着色器的输入值以及着色器执行所需的任何寄存器空间。使用不异着色器法式的线程被绑缚成组,NVIDIA称之为warp,AMD称之为wavefront。warp/wavefront由一些GPU着色器核心(从8到64个)使用SIMD措置进行调剂执行。每个线程都映射到一个SIMD通道。
假设我们有两千个线程要执行。NVIDIA GPU上的warp包含32个线程。这发生了2000/32 = 62.5个warp,这意味着分配了63个warp,此中一个warp是半空的。一个warp的执行类似于我们单个GPU措置器的示例。着色器法式在所有32个措置器上锁定执行。当遇到内存获取时,所有线程同时遇到它,因为所有线程都执行不异的指令。获取的信号暗示这组线程将停顿,所有线程都在等待它们(分歧)的成果。它们不是真的停顿,而是这组warp会被切换为另一组32个线程的warp,然后由32个核心执行。与我们单措置器系统一样快速交换,因为在线程交换进出时不会触及每个线程内部的数据。每个线程都有本身的寄存器,而且每个warp都跟踪它正在执行哪条指令。交换一个新的warp只是将一组核心指向要执行的另一组线程,没有其他开销。warp会一直执行或交换直到全部完成。
在我们简单的示例中,纹理的内存获取延迟可能会导致warp交换出去。实际上,由于交换成本如此之低,warp可能会因更短的延迟而被交换出去。还有一些其他技术用于优化执行[1],但warp-swapping是所有GPU使用的主要隐藏延迟机制。这个过程的效率涉及几个因素。例如,如果线程非常很少,只能创建很少的warp,就会使隐藏延迟成为问题。
着色器法式的布局是影响效率的重要特征。一个主要因素是每个线程使用的寄存器数量。在我们的示例中,我们假设两千个线程可以同时驻留在GPU上。与每个线程相关联的着色器法式需要的寄存器越多,则线程数越少,因此可以驻留在GPU中的warp也越少。warp短缺可能意味着无法通过交换来缓解停顿。驻留的warp称为“in flight”,这个数字称为占用率。高占用率意味着有许多warp可用于措置,因此空闲措置器的可能性低。低占用率凡是会导致性能不佳。内存获取频率也会影响需要隐藏多少延迟。Lauritzen[2]概述了着色器使用寄存器和共享内存数量如何影响占用率。Wronski[3] [4]讨论了抱负占用率如何按照着色器执行操作类型而变化。
影响总体效率的另一个因素是动态分支,由“if”语句和循环引起。假设在着色器法式中遇到一个“if”语句。如果所有线程都评估并采纳不异的分支,则warp可以继续执行,而不必考虑其他分支。但是,如果一些线程甚至一个线程选择了分歧的分支,则warp必需执行两个分支,丢弃每个特定线程不需要的成果[5] [6]。这个问题称为线程发散,此中少数几个线程可能需要执行循环迭代或执行其他warp中的线程不需要的“if”路径,在此期间其他线程就会处于空闲状态。
所有的GPU都实现了这些架构思想,从而形成了具有严格限制但每瓦特计算能力巨大的系统。理解这个系统的运作方式将辅佐您作为法式员更有效地操作它提供的能力。在接下来的章节中,我们将讨论GPU如何实现衬着管道,可编程着色器如何运行,以及每个GPU阶段的演变和功能。
图3.1.简化的着色器执行示例
三角形的片元,也是线程,被收集到warps中。每个warps显示为四个线程,但实际上有32个线程。要执行的着色器法式长5条指令。四个GPU着色器措置器的调集为第一个warp执行这些指令,直到在“txr”命令上检测到停滞条件,该命令需要时间来获取其数据。第二个warp被交换进来,并将着色器法式的前三条指令应用于它,直到再次检测到停滞。在第三个warps被交换进来并停滞后,执行继续通过交换进入第一个warp并继续执行。如果此时其“txr”命令的数据尚未返回,则执行真正地停滞直至这些数据可用。每个warp依次完成。
图3.2.GPU实现的衬着管线。各个阶段的颜色编码按照用户对其操作的控制程度而定。绿色阶段是完全可编程的。虚线显示可选阶段。黄色阶段是可配置但不成编程的,例如,可以为合并阶段设置各种混合模式。蓝色阶段在其功能上完全固定。
3.2 GPU管线总览(GPU Pipeline Overview)
GPU实现了第2章中描述的概念几何措置、光栅化和像素措置流程阶段。这些被分为几个硬件阶段,可配置性或可编程性分歧。图3.2按照它们的可编程性或可配置性用分歧颜色显示了各个阶段。请注意,这些物理阶段与第2章中介绍的功能阶段有所分歧。
我们在这里描述了GPU的逻辑模型,即API向作为法式员的您公开的模型。正如第18章和第23章所讨论的,这些逻辑流程的实现,即物理模型,取决于硬件供应商。在逻辑模型中固定功能的阶段可以通过向相邻可编程阶段添加命令来在GPU上执行。管线中的单个法式可能被拆分成由独立子单元执行的元素,或者完全由独立通道执行。逻辑模型可以辅佐您揣度影响性能的因素,但不应将其误认为是GPU实际实现流程的方式。
顶点着色器是一个完全可编程的阶段,成果会被用于实现几何措置阶段。几何着色器是一个完全可编程的阶段,它对基元(点、线或三角形)的顶点进行操作。它可以用来执行每个基元的着色操作,销毁基元或创建新的基元。细分阶段和几何着色器都是可选的,且并非所有GPU都撑持它们,出格是在移动设备上。
裁剪、三角形设置和三角形遍历阶段由固定功能硬件实现。屏幕映射受窗口和视口设置的影响,内部形成简单的缩放和从头定位。像素着色器阶段是完全可编程的。虽然合并阶段不成编程,但它高度可配置,而且可以设置为执行各种各样的操作。它实现了“合并”功能阶段,负责改削颜色、z缓冲区、混合、模板和任何其他与输出相关的缓冲区。像素着色器执行与合并阶段一起形成了第2章中提到的概念性像素措置阶段。
随着时间的推移,GPU流程已经从硬编码操作向更强大的灵活性和控制性演变。引入可编程着色器阶段是这一演变中最重要的一步。下一节将描述各种可编程阶段的共同特征。
3.3 可编程着色器阶段(The Programmable Shader Stage)
现代着色器法式使用统一着色器设计。这意味着顶点、像素、几何和曲面细分相关的着色器共享一个通用的编程模型。在内部,它们具有不异的指令集架构(instruction set architecture, ISA)。实现这种模型的措置器在DirectX中称为通用着色器核心(common-shader core),具有这种核心的GPU被称为具有统一着色器架构。这种类型架构背后的想法是,着色器措置器可用于多种角色,GPU可以按照需要分配这些角色。例如,一组具有微小三角形的网格将需要比由两个三角形组成的大正方形更多的顶点着色器措置。如果GPU具有分手的顶点着色器核心池和像素着色器核心池,就意味着为了保持所有核心忙碌,抱负的负载分配是严格预先确定的。使用统一着色器核心,GPU可以决定如何平衡这种负载。
描述整个着色器编程模型远远超出了本书的范围,已经有许多文档、册本和网站这样做了。着色器使用类似C语言的着色语言编程,如DirectX的高级着色语言(High-Level Shading Language, HLSL)和OpenGL着色语言(OpenGL Shading Language, GLSL)。DirectX的HLSL可以编译为虚拟机器字节码,也称为中间语言(IL或DXIL),以提供硬件独立性。中间暗示还可以允许离线编译和存储着色器法式。这种中间语言由驱动法式转换为特定GPU的ISA。控制台编程凡是避免中间语言法式,因为系统只有一个ISA。
基本数据类型是32位单精度浮点标量和向量,尽管向量只是着色器代码的一部门,在上述硬件中不撑持。在现代GPU上,32位整数和64位浮点数也得到了原生撑持。浮点向量凡是包含诸如位置(xyzw)、法线、矩阵行、颜色(rgba)或纹理坐标(uvwq)之类的数据。整数最常用于暗示计数器、索引或位掩码。也撑持聚合数据类型,如布局、数组和矩阵。
draw call 会调用图形API来绘制一组图元,从而使图形流水线执行并运行其着色器。每个可编程着色器阶段都有两种类型的输入:统一输入(uniform inputs),其值在整个绘制调用期间保持不变(但可以在绘制调用之间更改),以及可变输入,即来自三角形顶点或光栅化的数据。例如,像素着色器可以将光源的颜色作为统一值提供,而三角形概况的位置每个像素城市改变,因此是可变的。纹理是一种特殊的统一输入,它曾经总是应用于概况的彩色图像,但此刻可以被认为是任何数据的大型阵列。
底层虚拟机为分歧类型的输入和输出提供了特殊寄存器。可用于统一输入的常量寄存器数量比可用于可变输入或输出的寄存器数量要多得多。这是因为可变输入和输出需要为每个顶点或像素单独存储,因此对所需数量有一个自然的限制。统一输入只存储一次,并在绘制调用中的所有顶点或像素中反复使用。虚拟机还具有通用临时寄存器(temporary registers),是用于暂存的空间。所有类型的寄存器都可以使用临时寄存器中的整数值进行数组索引。着色器虚拟机的输入和输出可以在图3.3中看到。
图3.3. 在Shader Model 4.0下的统一虚拟机架构和寄存器布局。每个资源旁边的是最大可用数量。三个数字用斜杠分隔,暗示顶点、几何和像素着色器的限制(从左到右)。
在现代GPU上,图形计算中常见的操作可以高效执行。着色语言开放了最常见的运算操作(例如加法和乘法),通过诸如*和+之类的运算符。其余部门通过内置函数(intrinsic functions)开放,例如atan()、sqrt()、log()等,这些函数针对GPU进行了优化。用于更为复杂操作的函数也存在着,例如向量归一化和反射、叉积以及矩阵转置和行列式计算。
流控制(flow control)一词指的是使用分支指令来改变代码执行流程。与流控制相关的指令用于实现高级语言布局,如“if”和“case”语句,以及各种类型的循环。着色器撑持两种类型的流控制。静态流控制分支基于统一输入的值。这意味着代码的流程在整个绘制调用期间保持不变。静态流控制的主要好处是允许在分歧情况下使用不异的着色器(例如,分歧数量的光源)。由于所有调用都采用不异的代码路径,因此没有线程发散的问题。动态流控制基于可变输入的值,这意味着每个片段都可以以分歧的方式执行代码。这比静态流控制更强大,但可能会影响性能,出格是如果着色器调用之间的代码流程发生犯警则变化。
3.4 可编程着色和API的演变(The Evolution of Programmable Shading and APIs)
可编程着色框架的想法可以追溯到1984年 Cook 的 shade trees [287]。图3.4显示了一个简单的着色器及其对应的shade tree。RenderMan着色语言[63, 1804]是在20世纪80年代末从这个想法中成长而来的。它此刻仍用于电影制作衬着,以及其他不竭演变的规范,如Open Shading Language (OSL) 项目[608]。
消费级图形硬件初度成功推出是在1996年10月1日由 3dfx Interactive 公司推出。参见图3.5,从这一年开始的时间表。他们的Voodoo图形卡能够以高质量和高性能衬着游戏Quake,从而迅速被采用。这种硬件在整个过程中都实现了固定功能流水线。在GPU原生撑持可编程着色器之前,曾有多次测验考试通过多次衬着通道来实现实时可编程着色操作。Quake III: Arena 脚本语言是1999年在这一范围中初度获得广泛商业成功。如本章开头所述,NVIDIA的 GeForce256 是第一款被称为GPU的硬件,但它不成编程。然而,它是可配置的。
图3.4. 简单铜着色器的着色树(Shade tree)及其相应的着色器语言法式。(After Cook [287] )
图3.5. 一些API和图形硬件发布的时间表
在2001年初,NVIDIA的GeForce 3是第一款撑持可编程顶点着色器的GPU [1049],通过DirectX 8.0和OpenGL的扩展公开。这些着色器使用类似汇编语言的语言编程,由驱动法式在运行时转换为微代码。DirectX 8.0中也包含了像素着色器,但像素着色器并未达到实际可编程性——有限的“法式”被驱动法式转换为纹理混合状态,然后将硬件“寄存器组合器”连接在一起。这些“法式”不仅长度有限(12条指令或更少),而且缺乏重要功能。Peercy等人 [1363] 从对RenderMan的研究中发现,依赖纹理读取和浮点数据对于真正的可编程性至关重要。
当时的着色器不允许流控制(分支),因此必需通过计算两个项并选择或插值成果来模拟条件。DirectX 定义了着色器模型(Shader Model, SM)的概念,以区分具有分歧着色器能力的硬件。2002年发布了DirectX 9.0,包罗Shader Model 2.0,它具有真正可编程的顶点和像素着色器。类似功能也在OpenGL中使用各种扩展进行开放。添加了对任意依赖纹理读取和存储16位浮点值的撑持,最终完成了Peercy等人确定的一组要求。着色器资源(如指令、纹理和寄存器)的限制增加,因此着色器能够实现更复杂的效果。也添加了对流控制的撑持。着色器长度和复杂度的增长使汇编编程模型变得越来越繁琐。幸运的是,DirectX 9.0还包罗HLSL。这种着色语言由Microsoft与NVIDIA合作开发。大约在同一时间,OpenGL的架构审查委员会(Architecture Review Board, ARB)发布了GLSL,一种与OpenGL相当类似的语言 [885]。这些语言受到C编程语言的语法和设计哲学的重大影响,并包含了RenderMan着色语言的元素。
Shader Model 3.0于2004年推出,增加了动态流控制,使着色器更加强大。它还将可选功能转换为要求,进一步增加了资源限制,并在顶点着色器中添加了对纹理读取的有限撑持。当新一代游戏机在2005年底(微软的Xbox 360)和2006年底(索尼电脑娱乐的PLAYSTATION 3系统)推出时,它们都配备了Shader Model 3.0级此外GPU。任天堂的Wii游戏机是最后一款值得注意的固定功能GPU,最初于2006年底发发售。纯粹的固定功能流水线此时已经消掉。着色语言已经演变到一个程度,可以使用各种东西来创建和打点它们。图3.6显示了使用 Cook 的 shade tree 概念的这样一个东西的屏幕截图。
图3.6. 这是一个用于着色器设计的可视化着色器图系统。各种操作被封装在函数框中,在左侧可选。被选中时,每个函数框都有可调参数,在右侧显示。每个函数框的输入和输出彼此连接,形成最终成果,在中心框架的右下角显示。(来自“mental mill”,mental images inc.的屏幕截图)
可编程性的下一个重大法式也呈此刻2006年底。Shader Model 4.0,包含在DirectX 10.0 [175] 中,引入了几个主要功能,如几何着色器和流输出。Shader Model 4.0 包罗了所有着色器(顶点、像素和几何)的统一编程模型,即前面描述的统一着色器设计。资源限制进一步增加,并添加了对整数数据类型(包罗位运算)的撑持。OpenGL 3.3 中 GLSL 3.3 的引入提供了类似的着色器模型。
在2009年,DirectX 11和Shader Model 5.0发布,增加了曲面细分阶段着色器和计算着色器,也称为DirectCompute。此版本还着重于更有效地撑持CPU多进程,这是第18.5节讨论的主题。OpenGL在4.0版本中添加了曲面细分,在4.3版本中添加了计算着色器。DirectX和OpenGL的成长分歧。两者都设定了特定版本发布所需的硬件撑持程度。微软控制DirectX API,因此直接与独立硬件供应商(如AMD、NVIDIA和Intel)以及游戏开发商和计算机辅助设计软件公司合作,确定要公开的功能。OpenGL由一个硬件和软件供应商的财团开发,由非营利性的Khronos集团打点。由于涉及的公司众多,API功能凡是在OpenGL版本发布后一段时间才呈现。然而,OpenGL允许扩展,供应商特定或更通用的扩展,允许在正式发布撑持之前使用最新的GPU功能。
API的下一个重大变化是由AMD在2013年推出Mantle API引领的。Mantle的想法是剥离大部门图形驱动法式的开销,并将这种控制直接交给开发人员,这一想法是与游戏开发商DICE合作开发的。除了这种重构外,还进一步撑持有效的CPU多进程。这类新API的重点是大大减少CPU在驱动法式中花费的时间,以及更有效地撑持CPU多措置器(第18章)。Mantle中开创性的想法被微软采纳,并于2015年发布为DirectX 12。注意,DirectX 12并不专注于公开新的GPU功能——DirectX 11.3公开了不异的硬件功能。两种API都可以用来将图形发送到虚拟现实系统,如Oculus Rift和HTC Vive。然而,DirectX 12是对API的一次彻底从头设计,它更好地映射到现代GPU架构。低开销驱动法式对于CPU驱动法式成本导致瓶颈,或使用更多CPU措置器进行图形措置的情况,都可以有效地提升性能 [946]。从早期API移植可能很困难,而且简单实现可能会导致性能降低 [249、699、1438]。
苹果公司在2014年发布了本身的低开销API,称为Metal。Metal最初可用于移动设备,如iPhone 5S和iPad Air,一年后通过OS X El Capitan在较新的Macintosh上获得访谒权限。除了效率外,减少CPU使用量还可以节省电力,这对移动设备来说是一个重要因素。这个API有本身的着色语言,用于图形和GPU计算法式。
AMD将其Mantle工作捐赠给了Khronos集团,该集团在2016年初发布了本身的新API,称为Vulkan。与OpenGL一样,Vulkan可以在多个操作系统上运行。Vulkan使用一种新的高级中间语言SPIRV,用于着色器暗示和通用GPU计算。预编译的着色器是可移植的,因此可以在任何撑持所需功能的GPU上使用[885]。Vulkan也可以用于非图形GPU计算,因为它不需要显示窗口[946]。Vulkan与其他低开销驱动法式的一个显著区别是,它旨撑持更广泛的平台和系统(从工作站到移动设备)。
在移动设备上,凡是使用OpenGL ES。 “ES”代表嵌入式系统,因为这个API是考虑到移动设备而开发的。当时的尺度OpenGL在某些调用布局中相当笨重和迟缓,而且需要撑持很少使用的功能。2003年发布的OpenGL ES 1.0是OpenGL 1.3的精简版本,描述了一个固定功能管线。尽管DirectX的发布与撑持它们的图形硬件同时进行,但为移动设备开发图形撑持并未以不异方式进行。例如,2010年发布的第一代iPad实现了OpenGL ES 1.1。2007年发布了OpenGL ES 2.0规范,提供可编程着色。它基于OpenGL 2.0,但没有固定功能组件,因此与OpenGL ES 1.1不兼容。OpenGL ES 3.0于2012年发布,提供了多方针衬着(multiple render targets)、纹理压缩、变换反馈、实例化和更广泛的纹理格式和模式等功能,以及着色语言改良。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。
WebGL出格适用于尝试功能或在课堂上使用:
- 它是跨平台的,在所有个人计算机和几乎所有移动设备上都能工作。
- 驱动法式的批准由浏览器措置。即使一个浏览器不撑持特定的GPU或扩展,凡是另一个浏览器也会撑持。
- 代码是解释的,而不是编译的,只需要一个文本编纂器进行开发。
- 大大都浏览器都内置了调试器,可以查抄任何网站运行的代码。
- 法式可以通过上传到网站或Github等来部署。
高级场景图和效果库,如three.js [218],提供了轻松访谒各种更复杂效果的代码,如暗影算法、后措置效果、基于物理的着色和延迟衬着。
3.5 顶点着色器(The Vertex Shader)
顶点着色器是图3.2中所示功能管线的第一阶段。虽然这是法式员直接控制的第一阶段,但值得注意的是,在这个阶段之前会发生一些数据操作。在DirectX称之为输入装配器(input assembler) [175, 530, 1208] 的处所,可以将几个数据流汇合在一起,形成发送到管线的顶点和图元调集。例如,一个对象可以由一个位置数组和一个颜色数组暗示。输入装配器将通过创建具有位置和颜色的顶点,来创建该对象的三角形(或线条或点)。第二个对象可以使用不异的位置数组(以及分歧的模型变换矩阵)和分歧的颜色数组来暗示。数据暗示在第16.4.5节中详细讨论。输入装配器还撑持实例化。这允许一个对象被绘制多次,每个实例都有一些可变数据,所有这些都只需一次绘制调用。实例化的使用在第18.4.2节中介绍。
三角形网格由一组顶点暗示,每个顶点都与模型概况上的特定位置相关联。除了位置外,每个顶点还有其他可选属性,如颜色或纹理坐标。概况法线也在网格顶点处定义,这似乎是一个奇怪的选择。从数学上讲,每个三角形都有一个明确定义的概况法线,直接使用三角形的法线进行着色似乎更有意义。然而,在衬着时,三角形网格凡是用来暗示底层曲面,顶点法线用来暗示这个曲面的标的目的,而不是三角形网格本身的标的目的。第 16.3.4 节将讨论计算顶点法线的方式。图 3.7 显示了两个暗示曲面的三角形网格的侧视图,一个平滑,一个带有锐利的折痕。
图 3.7. 两个三角形网格(黑色,带顶点法线)暗示曲面(红色)的侧视图。左边使用平滑的顶点法线来暗示平滑的曲面。右边中间的顶点被复制并给出了两个法线,暗示一个折痕。
顶点着色器是措置三角形网格的第一个阶段。描述形成三角形的数据对顶点着色器不成用。顾名思义,它只措置传入的顶点。顶点着色器提供了一种改削、创建或忽略与每个三角形顶点相关联的值(如颜色、法线、纹理坐标和位置)的方式。凡是,顶点着色器法式将顶点从模型空间转换为齐次裁剪空间(第 4.7 节)。顶点着色器必需至少输出位置属性。
顶点着色器与前面描述的统一着色器非常相似。每个传入的顶点都由顶点着色器法式措置,然后输出一些值,这些值在三角形或线段长进行插值。顶点着色器既不能创建也不能销毁顶点,一个顶点生成的成果不能传递给另一个顶点。由于每个顶点都是独立措置的,因此可以并行应用 GPU 上的任意数量的着色器措置器来措置传入的顶点流。
输入装配凡是被描述为在顶点着色器执行之前发生的过程。这是一个物理模型与逻辑模型分歧的例子。在物理上,获取数据并创建顶点可能发生在顶点着色器中,驱动法式会静默地将每个着色器的前面附加适当的指令,且对法式员不成见。
接下来的章节将解释几种顶点着色器效果,如用于动画关节的顶点混合和轮廓衬着。顶点着色器的其他用途包罗:
- 对象生成,通过只创建一次网格并让它由顶点着色器变形
- 使用蒙皮和变形技术来动画角色的身体和面部
- 法式化变形,如旗帜、布料或水的运动 [802, 943]
- 粒子创建,通过将退化(无面积)网格发送到管线并按照需要赐与面积
- 透镜畸变、热晕、水波纹、页面卷曲和其他效果,通过使用整个帧缓冲区的内容作为屏幕对齐网格上的纹理进行法式化变形。
- 使用顶点纹理获取应用地形高度场 [40, 1227]
图 3.8 展示了一些使用顶点着色器完成的变形。
顶点着色器的输出可以以几种分歧的方式使用。凡是的路径是为每个实例的图元(例如三角形)生成和光栅化,然后将发生的单个像素片元发送到像素着色器法式进行进一步措置。在一些 GPU 上,数据也可以发送到细分阶段或几何着色器或存储在内存中。这些可选阶段将不才面的章节中讨论。
图3.8. 在左边,是一个普通的茶壶。顶点着色器法式执行的简单剪切操作发生了中间的图像。在右边,噪声函数创建了一个场用于扭曲模型。(图片由 FX Composer 2 生成,由 NVIDIA 公司提供。)
3.6 曲面细分阶段(The Tessellation Stage)
细分阶段允许我们衬着曲面。GPU 的任务是将每个概况描述转换为一组代表性的三角形。这个阶段是一个可选的 GPU 功能,它初度可用是在 DirectX 11 中(而且是必需的)。它也被 OpenGL 4.0 和 OpenGL ES 3.2 撑持。
使用细分阶段有几个长处。曲面描述凡是比提供相应的三角形本身更紧凑。除了节省内存,这个功能还可以防止 CPU 和 GPU 之间的总线成为每帧形状变化的动画角色或对象的瓶颈。概况可以通过为给定视图生成适当数量的三角形来高效衬着。例如,如果一个球离摄像机很远,只需要几个三角形。近距离时,它可能看起来最好用数千个三角形暗示。这种控制细节级此外能力也可以让应用法式控制其性能,例如,在较弱的 GPU 上使用低质量网格以保持帧率。凡是由平面暗示的模型可以转换为精细的三角形网格,然后按需扭曲 [1493],或者它们可以被细分以便更少地执行昂贵的着色计算 [225]。
细分阶段总是由三个元素组成。使用 DirectX 的术语,这些是hull shader、tessellator和domain shader。在 OpenGL 中,hull shader是细分控制着色器(tessellation control shader),domain shader是细分计算着色器(tessellation evaluation shader),这是它们更具描述性、更冗长的暗示。固定功能细分器在 OpenGL 中称为图元生成器(primitive generator),正如我们将看到的,这正是它所做的事情。
如何指定和细分曲线和曲面在第17章中详细讨论。在这里,我们简要总结每个曲面细分阶段的目的。首先,输入到hull shader的是一个特殊的 patch 图元。它由几个控制点组成,定义了细分曲面、贝塞尔patch 或其他类型的曲线元素。hull shader有两个功能。首先,它告诉曲面细分器应该生成多少个三角形,以及配置方式。其次,它对每个控制点进行措置。此外,hull shader还可以选择性地改削传入的patch的描述,按照需要添加或删除控制点。hull shader将其控制点调集与细分控制数据一起输出到domain shader。参见图3.9。
图3.9. 曲面细分阶段。hull shader接收由控制点定义的patch。它将细分因子(TFs)和类型发送到固定功能的曲面细分器。控制点集由hull shader按需转换,并与TFs和相关patch常量一起发送到domain shader。曲面细分器创建顶点调集及其重心坐标。然后由domain shader措置,生成三角形网格(参考控制点)
曲面细分器(tessellator)是流水线中的一个固定功能阶段,仅用于曲面细分着色器。它的任务是为domain shader添加几个新顶点。hull shader向曲面细分器发送有关所需细分曲面类型的信息:三角形、四边形或等值线。等值线是线条调集,有时用于头发衬着 [1954]。曲面细分器发送的另一个重要值是细分因子(OpenGL中的细分级别)。它们分为两种类型:内部和外部边缘。两个内部因素决定三角形或四边形内部发生多少细分。外部因素决定每个外部边缘被分割多少次(第17.6节)。图3.10显示了增加细分因子的示例。通过允许单独控制,我们可以使相邻曲面的边缘在细分中匹配,而不管内部如何细分。匹配边缘避免了patch相遇处呈现裂缝或其他暗影伪影。顶点被分配重心坐标(第22.8节),这些值指定了所需曲面上每个点的相对位置。
图3.10. 改变细分因子的效果。犹他茶壶由32个patch组成。从左到右,内部和外部细分因子分袂为1、2、4和8。(图片由Rideout和Van Gelder [1493]的演示生成。)
hull shader总是输出一个patch,一组控制点位置。但是,它可以通过向曲面细分器发送零或更小(或非数字,NaN)的外部细分级别,来丢弃这个patch。否则,曲面细分器会生成一个网格并将其发送到domain shader。来自hull shader的曲面控制点由domain shader的每次调用用于计算每个顶点的输出值。domain shader具有类似于顶点着色器的数据流模式,每个来自曲面细分器的输入顶点都被措置并生成相应的输出顶点。然后将形成的三角形传递到流水线的下一阶段。
虽然这个系统听起来很复杂,但它是为了效率而这样构建的,每个着色器都可以相当简单。传递到hull shader的patch凡是会经历很少或没有改削。该着色器还可以使用patch的估计距离或屏幕大小来实时计算细分因子,例如用于地形衬着[466]。或者,hull shader可能只是传递应用法式计算并提供的所有patch的固定值集。曲面细分器执行一个复杂但固定功能的过程,生成顶点,给它们定位,并指定它们形成的三角形或线条。这个数据放大法式是在着色器外部执行的,以提高计算效率[530]。domain shader获取了每个点生成的重心坐标,并在patch的计算方程中使用它们来生成位置、法线、纹理坐标和其他所需的顶点信息。请参见图3.11中的示例。
图3.11. 左边是大约6000个三角形的底层网格。右边,每个三角形都使用PN三角形细分进行镶嵌和位移。(图片来自NVIDIA SDK 11 [1301]示例,由NVIDIA Corporation提供,模型来自4A Games的Metro 2033。)
图3.12. 几何着色器法式的输入是某种单一类型:点、线段、三角形。最右边的两个图元包罗与线和三角形对象相邻的顶点。更复杂的patch类型也是可能的。
参考
- ^Kubisch, Christoph, “Life of a Triangle—NVIDIA’s Logical Pipeline,” NVIDIA GameWorks blog, Mar. 16, 2015. Cited on p. 32
- ^Lauritzen, Andrew, “Future Directions for Compute-for-Graphics,“ SIGGRAPH Open Problems in Real-Time Rendering course, Aug. 2017.
- ^Wronski, Bartlomiej, “Assassin's Creed: Black Flag--Road to Next-Gen Graphics,“ Game Developers Conference, Mar. 2014. https://bartwronski.files.wordpress.com/2014/03/ac4_gdc_notes.pdf
- ^Wronski, Bartlomiej, “GCN--Two Ways of Latency Hiding and Wave Occupancy,“ Bart Wronski blog, Mar. 27, 2014. https://bartwronski.com/2014/03/27/gcn-two-ways-of-latency-hiding-and-wave-occupancy/
- ^Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,“ The ryg blog, July 9, 2011. https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-graphics-pipeline-2011-index/
- ^Kubisch, Christoph, “Life of a Triangle--NVIDIA's Logical Pipeline,“ NVIDIA GameWorks blog, Mar. 16, 2015. https://developer.nvidia.com/content/life-triangle-nvidias-logical-pipeline
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|