目录
[显示]

1.摘要

图形处理器 历史上,图形硬件加速最早从管线的最后一步,光栅化开始,逐步管线前端发展,如今已经包括了程序阶段的算法。相较于软件,硬件的优点在于速度快,速度是至关重要的。 1999年NVIDIA Geforce256是第一块包括硬件顶点处理的显卡,NIVDIA发明了新词图形处理单元(graphics processing unit,GPU)来区分GeForce 256与之前只能做光栅化的显卡。 之后的几年,GPU从可配置的复杂的固定功能管线发展到了高度可编程的“白板”,开发者可以实现他们自己的算法。多种可编程的shader是控制GPU的主要手段。

 

 

2. 3       图形处理器

历史上,图形硬件加速最早从管线的最后一步,光栅化开始,逐步管线前端发展,如今已经包括了程序阶段的算法。相较于软件,硬件的优点在于速度快,速度是至关重要的。

1999年NVIDIA Geforce256是第一块包括硬件顶点处理的显卡,NIVDIA发明了新词图形处理单元(graphics processing unit,GPU)来区分GeForce 256与之前只能做光栅化的显卡。

之后的几年,GPU从可配置的复杂的固定功能管线发展到了高度可编程的“白板”,开发者可以实现他们自己的算法。多种可编程的shader是控制GPU的主要手段。

vertex shader可以对每个顶点做多种操作;pixel shader处理每一个像素,允许对每一个像素执行着色方程; geometry shader允许GPU创建和销毁几何图元(点、线、三角形)on the fly。运算结果可以被写入高精度缓冲器并被重复使用。

2.1. 3.1         GPU管线综述

GPU实现了概念上的管线的几何和光栅器阶段,并进一步划分了多个可配置或可编程的硬件阶段。物理阶段的划分和之前提到的功能阶段有轻微的差别。

Vertex shader是完全可编程的阶段,用于实现模型视图变换、vertex shading和投影功能阶段。

Geometry shader是可选的,完全可编程的阶段。它操作图元的顶点,可以被用于处理所有图元的shading操作、销毁图元或创建新图元。

Clipping、screen mapping、triangle setup、triangle traversal阶段实现了同名的功能阶段。

Pixel shader是完全可编程的,并执行了pixel shading 功能阶段。

Merger stage是介于完全可编程和固定功能之间,虽然不是可编程的,但是它高度可配置,可以设置并执行多种操作,它实现了Merging功能阶段,负责改变颜色,Z缓冲,混合,模板等缓冲器。

2.2. 3.2         可编程的Shader阶段

现代的shader stage使用了通用shader核心(common-shader core),这表示顶点、像素、几何shader共享一个编程模型。通用shader核心是API,统一的shader是GPU的一个特性。

Shader编程使用C风格的shading语言,如HLSL,Cg,GLSL。它们被编译为独立于机器的汇编语言(也被称作中间语言,intermediate language,IL)。以前的shader models允许直接用汇编语言编写shader,到了DirectX10,汇编代码只能在debug output中看到。这种汇编语言的代码在一个独立的步骤中(通常在驱动中)被转换成真正的机器语言。这样可以在不同的硬件实现上保持兼容。这种汇编语言可以被看做定义了一个虚拟机,这个虚拟机是shading语言编译器的目标平台。

该虚拟机是一个具有多种类型寄存器和数据源的处理器,使用一个指令集编程。因为很多指令都是在短向量(short vectors)(长度最多为4)下执行,所以处理器拥有4路SIMD(single-instruction multiple-data)能力。每一个寄存器包括了4个独立的值。基础数据类型是32位单精度标量和向量,最近还加入了32位整型支持。浮点向量通常包括位置(xyzw),法向量,矩阵行,颜色(rgba)或纹理坐标(uvwq),整型通常被用于代表计数器、索引、或位掩码。聚合数据类型,例如结构体、数组和矩阵同样被支持。为了更好的支持向量操作,还支持swizzle,向量的元素可以按要求重排或复制。同样也支持masking,只使用指定的向量元素。

Programmable shader阶段又两种输入:uniform 输入,在一次draw的调用过程中值保持不变(但可以在两次draw调用间改变);varying 输入,对于每个shader处理的顶点或像素都不同。

Texture是一种特殊类型的uniform输入,过去一直认为它是覆盖在表面上的彩色图像,但是现在可以认为它是任意的包含数据的数组。

虽然shader有很多种可以用不同方式的输入,但输出种类很少。这是shader和通用处理器上执行的程序的最大区别。基础的虚拟机为不同类型的输入输出提供了特殊的寄存器。

Uniform输入进入只读的静态寄存器(constant registers)或静态缓冲器(constant buffers)。常量寄存器的数量比储存varying输入的寄存器多的多。这是因为varying输入和输出需要对每个顶点或像素单独存储,但是uniform输出存储一次,并在调用draw时被所有顶点或像素重用。虚拟机同样有用于暂存空间的通用的临时寄存器(temporary register)。所有类型的寄存器可以在临时寄存器中使用整数值数组索引。

图形计算方面常用的一些操作在现代GPU上都被高效的执行了,最快的操作是标量和矢量乘法,加法,乘法加法混合操作如乘加和点积。另外一些操作,如倒数,平方根,sine,cos,指数,对数等稍微多费一些但也是相当快的。纹理操作是高效的,但是性能受到一些因素的影响,例如取得访问结果所等待的时间等。

shading语言通过操作符暴露了大多数常用的操作,例如*,+。剩下的通过固有函数暴露出来,如atan(),dot(),log()等。固有函数也存在一些更复杂的操作,如向量归一化、反射和叉积,矩阵转置和求行列式等。

术语“流控制(flow control)”指的是改变代码执行过程的分支指令。用高级语言表示的话是“if”或“case”语句,或者多种循环语句。Shader支持两种流控制,静态流控制(static flow control)基于uniform输入的值,意味着调用draw过程中代码的执行过程是不变的。静态流控制的主要好处是允许一种shader被用于多种情况(如多个光源的情况)。动态流控制(dynamic flow control)基于varying输入的值,它比静态流控制更强大但也更消耗资源,特别是code flow在不同的shader调用中不规律的改变时。如果对于某些元素走if分支,另外的一些走else分支,那么两个分支对于所有的元素都要计算(然后每个元素不使用的分支将被丢弃)。

shader程序可以在程序载入前离线编译或者在运行时编译。像任意的编译器那样,可以有优化选项来生成不同的优化级别的输出文件,编译好的shader作为一个文本字符串存储并且通过驱动传递给GPU。

2.3. 3.3         Programmable Shading的演变

(本节复制自http://bbs.iieeg.com/viewthread.php?tid=416

早在1984年就出现了可编程shading框架的想法,RenderMan shading语言在80年代后期被开发出来至今仍然在电影渲染中使用。

在GPU原生支持可编程shader之前,有一些尝试是通过多遍渲染(multiple rendering passes)去实现可编程shader。在2000年,Peercy et al.描述了一个系统将RenderMan Shaders转换为多pass渲染。他们发现GPU缺少两个特性来使得这种尝试非常通用:(1)使用计算结果作为纹理坐标的能力(dependent texture reads)(2)在纹理和color buffer中支持扩展范围和精度的数据类型。

在2001年初,NVIDIA的GeForce 3是第一个支持可编程vertex shader的GPU,通过DirectX 8.0和OpenGL扩展暴露出来。这些shader使用类汇编语言编写并且在驱动中实时转换为microcode。

pixel shader也是在DirectX 8.0中被引入的,但是pixel shader SM 1.1缺乏真正的可编程性-他们只是非常有限的被支持的“程序”-被驱动转换为纹理混合状态。这些“程序”不但长度受限并且缺少上面说的两个特性 (dependent texture reads & float data)- 对真正的可编程性来说是至关紧要的。

那个时候的shader是不支持控制流的(即分支),因此条件语句必须通过计算两个部分然后在结果中选择或插值来模拟。DirectX定义了Shader Model的概念来区别具有不同shader能力的硬件。GeForce3支持vertex shader model 1.1和pixel shader model 1.1。在2001年期间,GPU向通用的pixel shader编程模型方面发展。DirectX 8.1增加了pixel shader model 1.2到1.4(每个意味着不同的硬件),进一步扩展了pixel shader的能力,增加了更多的指令和对dependent texture reads更普遍的支持。

2002年DirectX9.0发布,包含了Shader Model 2.0(以及扩展版本2.X),具有真正的可编程vertex和pixel shader。OpenGL方面,相似的功能也通过各种扩展增加进来。支持任意的dependent texture reads和16位浮点数值存储被增加进来,这样最终完成了2000年Peercy et al.定义的需求集。指令,纹理,寄存器这些受限制的shader资源都增加了,因此shader可以做更复杂的效果。控制流的支持也被加入。 shader的长度和复杂性的增长使得汇编语言模型逐渐的笨重。幸运的是,DirectX9.0也引入了一种新的shader编程语言,叫做 HLSL(High Level Shading Language)。HLSL由微软和NVIDIA合作开发,NVIDIA自己也发布了跨平台的Cg语言。差不多同时,OpenGL ARB为OpenGL发布了一个类似的语言,叫做GLSL(也叫做GLslang)。

Shader Model 3.0在2004年被引入,将一些可选的特性变成必须的,进一步增加了受限资源,为vertex shader增加了有限的texture read支持。2005年发布的Xbox 360和2006年发布的PS3都具有支持Shader Model3.0的GPU。
下一个大的进步是2007年出现的Shader Model 4.0 (包含于DirectX10.0以及OpenGL扩展),引入了几个主要的特性,例如geometry shader和stream output。SM4.0引入了一个统一化的编程模型(对于所有类型的shader:vertex,pixel,geometry),即之前介绍过的 common-shader core。受限资源进一步增加了,并且支持了整数类型(包括位操作)。并且,SM4.0只支持高级语言shader,即HLSL,GLSL,用户不能再使 用汇编语言接口了。

GPU厂商、微软和OpenGL ARB持续的进化和扩展可编程shading的能力。除了已存在的API的版本更新,一些新的编程模型,如NVIDIA的CUDA和AMD的CTM已经将 目标指向非图形化的程序。这种GPU上的一般目的计算领域(GPGPU)将在后面简要介绍。

2.3.1. 3.3.1  Shader Model的比较

比较了DirectX的shader model 2.x, 3.0, 4.0。DirectX9以及之前,IHV(独立硬件提供商)可以通过“capability bits”提供自己的GPU的特性。DirectX10开始,所有的IHV必须支持标准模型。

(略过)

2.4. 3.4         顶点Shader

顶点Shader是第一个做图像处理的阶段,在它之前做的数据操作没有意义。

一些数据流被混合在一起,变为顶点和图元的集合,在dx中被称作input assembler。例如,一个物体可以被表示成一个位置数组和一个颜色数组,input assembler将通过包含位置和颜色的顶点来创建组成这个物体的三角形(或点、线)。

Input assembler提供了实例化(instancing)操作。实例化可以通过每个实例中的一些不同的数据,在一个draw调用中绘制多个物体。

Dx10中为每个实例、图元和顶点设置了数字编号,之后的shader阶段都可以访问这个编号。在早期的shader 模型中,这个编号被加入到了模型中。

一个三角形mesh由一组顶点和描述顶点如何组成每个三角形的信息表达。顶点shader是处理三角mesh的第一个阶段。对于vertex shader来说,是不知道三角形是如何组成的,正如其名,他专门处理输入的顶点。概括的说,vs提供了一个方法去修改、创建或者忽略每个多边形的顶点所 相关的值,例如颜色、法线、纹理坐标和位置。通常,vs将顶点从模型空间变换到齐次剪裁空间;最低限度,一个vs必须总输出这个位置。

vs这个功能最初是2001年被DirectX8所引入的,因为vs是流水线的第一个阶段且使用频繁,他可以在GPU或CPU上实现,如果是CPU实现,就需要将结果转送给GPU去光栅化。这样做就会产生cpu到gpu的速度问题。目前的所有GPU都支持vs。

vertex shader非常类似于之前讨论的通用核心虚拟机(3.2节)。每个顶点被传入到vs程序中处理,然后输出很多个将在三角形或线段上插值的值(早期的 shader model也支持输出point sprite particle object的size,但是现在sprite功能是geometry shader的一部分了)。vertex shader既不能创建也不能销毁顶点,并且一个顶点计算的结果不能传递给另一个顶点。每个顶点都是独立对待的,GPU可以对输入的顶点流平行的执行任意 数量的vs。

之后的章节将讲解很多vs特效,例如shadow volume创建,vertex blending for animating joints,轮廓渲染(silhouette rendering)。另外一些vs的应用包括:
* 镜头特效(Lens effects):鱼眼效果、水底等歪曲的效果
* Object definition: 仅创建一个mesh一次,然后让他被vs变形
* Object 扭曲(twist)、弯曲(bend)、溶解(taper)操作。
* 程序控制的变形,例如旗帜,布料或水的运动。
* 图元创建:通过向流水线发送退化的mesh。这个功能在新GPU中已经被geometry shader代替。
* 卷页(page curls), 热模糊(heat haze), 水波纹(water ripples)和其他特效可以使用将整个frame buffer的内容作为一个屏幕对齐的mesh的纹理来达到,这个mesh使用程序来变形。
* Vertex texture fetch(SM3.0之上支持)可用来向顶点mesh附加纹理,允许廉价的实现海面和地形高地。

vs的输出可以有很多种不同的用途。通常的路径是对于每个被生成和光栅化的三角形,产生的独立的像素片段,发送到pixel shader程序中继续处理。在SM4.0中,数据也可以被发送到geometry shader中、流输出中或者两者都有

2.5. 3.5         几何Shader

geometry shader在2006年末随着DirectX10的发布被引入到硬件加速的图形流水线中,它在流水线中紧跟在vertex shader之后,并且是可选的。他是SM4.0的必须的部分,但是在早先的shader models中没有使用。

geometry shader的输入是一个物体和它的顶点。物体通常是一个mesh中的一个三角形,一个线段或仅仅是一个点。另外,扩展的图元可以被定义并且被geometry shader处理。特别的,一个三角形外的3个附加顶点可以被传入,一个线段的两个相邻顶点也可以使用。见图3.6。

geometry shader处理这个图元并且输出零个或多个图元。输出的形式是顶点组,线段组或者三角带组。例如,不止一个三角形带可以在一次geometry shader程序调用中输出。重要的是,geometry shader可以根本不生成任何东西。通过这种方法,一个mesh可以通过geometry shader被选择性的修改:增加新的图元和删除某些图元。

geometry shader程序被设置为输入一种类型的物体并输出一种类型的物体,输入输出的类型不需要匹配。例如,可以输入三角形并输出他的图心点。甚至如果输入和输出的物体类型是一样的,每个顶点附载的数据可以被缩减或者扩展。例如,三角形的面法线可以被计算出来并且加入到输出的顶点数据中。类似于vertex shader,geometry shader必须为每个产生的顶点输出齐次裁剪空间的位置。

geometry shader保证图元的输出顺序和输入一致。这影响到性能,因为如果很多shader平行运行,结果必须被保存和排序。作为能力和效率的折中,SM4.0限制每次执行可以生成最多1024个32bit值。所以,通过输入一个树叶,生成一千个灌木叶的做法是不可行的。将简单的表面镶嵌到更复杂的mesh也是不推荐的。这个阶段更多的是应该程序性的修改输入的数据或者做有限的拷贝,而不是大量的复制和放大。例如有一种应用是生成六个变换过的数据副本来同时渲染一个cub map的六个面(8.4.3节)。其他一些能利用geometry shader的算法有:从一个点数据创建不同尺寸的粒子组、毛皮渲染时沿着轮廓挤出鳍状物、为shadow算法找出物体边缘。

2.5.1. 3.5.1  Stream Output

标准的GPU流水线是将数据通过vertex shader,然后光栅化三角形并将数据传递到pixel shader。数据总是沿着流水线走,中间结果不能被访问。stream output的想法在SM4.0中被引入。当顶点被vertex shader(以及可选的geometry shader)处理后,数据除了被发送到光栅化阶段外,还可以被输出进入一个流,例如一个有序数组。实际上,光栅化可以整个关闭掉,这样流水线就完全当成一个非图形的流处理器使用了。按这种方法处理的数据可以被发送回流水线,因此允许迭代处理。这种类型的操作对于模拟流水或其他粒子特效非常有用(10.7 节)。

2.6. 3.6         The Pixel Shader

在vs和gs执行之后,图元被裁切(clipped)并准备光栅化,这个阶段是很固定的,不能编程(值得注意的例外是pixel shader可以指定使用哪种插值,perspective corrected or screen space, or none)。每个三角形依次处理,顶点数据在三角形内插值。pixel shader是下一个可编程阶段。在OpenGL中,被称作片元(fragmenet)shader,这在某种角度是一个更好的名字。这个意思是说,一个三角形全部或部分的覆盖每个像素,并且材质描绘为不透明的或透明的。光栅器不直接修改像素中保存的颜色,而是产生数据,描述三角形如何覆盖像素。之后在merging阶段,片元的数据被使用来修改像素中的数据。

vs的输出成为ps的输入,SM4.0中一共有16个向量可以从vs传输到ps,如果使用gs,可以输出32个向量到ps。SM3.0之后,给ps的额外的输入被加入,例如三角形的哪个面是可见的作为一个输入标志。片元的屏幕坐标对于ps也是可用的。

ps的限制是他只能影响他所处理的片元,不能将结果直接传递给相邻的像素,而是使用顶点插值的数据和存储的常量以及纹理数据来计算当前像素的结果。然而,这个限制也不是太严重,因为可以使用image processing来影响相邻的像素(10.9)。

一种情况下ps可以访问临近像素的信息(虽然是间接的),即计算梯度(gradient)或者派生信息(derivative information)。The pixel shader has the ability to take any value and compute the amount by which it changes per pixel along the x and y screen axes.这对于多种计算和纹理寻址很有用。梯度信息不能被动态控制流影响的shader访问。

ps一般是设置片元的颜色,颜色在最终的merging阶段被合并。光栅化阶段产生的深度值也可以被ps修改。stencil buffer的值是不可以修改的,会直接发送到merge阶段。从SM2.0开始,ps可以丢弃(discard)输入的片元数据,即不产生输出。这种操作是消耗性能的,因为GPU将不能进行优化。在SM4.0中,烟雾计算和alpha test从merge阶段被移动到ps中。

Ps拥有进行大量运算的能力,这种在单一rendering pass中计算任意数目值的能力促进了multiple render targets(MRT)的使用。每个片元可以生成多个向量并被保存到不同的多个buffer中,这些buffer必须是相同尺寸的,并且一些架构需要他们具有相同的bit depth(尽管如果需要格式可以不同)。PS输出寄存器的数量参考独立的buffer的数量,例如4或8。不同于color buffer,附加的target有一些限制。例如,不能使用反锯齿。尽管有这些限制,MRT功能对于更有效的执行渲染算法是一个强大的辅助。如果有很多中间结果的image需要从一批相同的数据中计算出来,只需要一个rendering pass就可以了,而不是每个pass对应一个输出buffer。另外的一个MRT相关的关键能力是从产生的image读数据作为纹理使用。

2.7. 3.7         The Merging Stage

merge阶段独立片元的深度值和颜色和frame buffer进行组合。stencil-buffer,Z-buffer操作在这阶段进行。另外一个操作是color blending。

merge阶段是不可编程的,但也不完全固定,他可以通过配置来定制操作。特别是color blending,可以使用很多不同的操作,例如乘法,加法,减法,使用颜色和alpha值;其他的操作如取最小值和取最大值也是可能的,以及还有位逻辑 操作。DirectX10增加了从pixel shader输出两个颜色和frame buffer上的颜色进行混合的功能,被称作dual-color blending。

如果采用了MRT功能,blending可以在多个buffer上进行。DirectX10.1引入了一个功能:在每个MRT buffer上进行不同的混合操作,在之前的版本中,所有的buffer只能进行相同的混合操作。(注意dual-color blending和MRT不兼容)

2.8. 3.8         特效(Effects)

一个单独的shader程序是不能单独起作用的:一个vs程序将结果提供给一个ps。两个程序都必须被载入才能工作。程序员必须将vs的输出和ps的输入 进行匹配。一个特定的渲染特效可能由任意数量的shader在几个pass中执行产生。除了shader自己,状态变量必须被正确的设置来保证 shader正确的工作。例如,渲染器的状态包括是否使用和如何使用z-buffer和stencil buffer,一个片元如何影响已经存在的像素值(e.g. replace, add, or blend)。
由于以上的原因,多个组织都开发了特效语言(effects language),例如HLSL FX, CgFX, COLLADA FX。一个特效文件试图将执行某个渲染算法需要的所有相关信息包装起来。一般它会定义一些全局参数,可以被程序赋值。例如,一个单独的effect file可能定义渲染塑料材质需要的vs和ps,它可能暴露一些参数例如塑料颜色和粗糙度,这样渲染每个模型的时候可以改变效果而仅仅使用同一个特效文件。

(下面以一个HLSL FX为例,介绍了一下。略过)

一个effect file中能存储很多techniques。这些techniques通常是一个相同effect的变体,每种对应于一个不同的shader model(SM2.0,SM3.0等等)。

======

待续