(注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~)
3D图形学研究的基本内容,即给定场景的描述,包括各个物体的材质、纹理、坐标等,照相机的位置及朝向,光源等信息,计算其最终在二维的光栅化显示器上对应的显示结果。从最初的场景描述到最终的显示结果,这整个过程就是在3D渲染管线中完成的。
这里的“管线”即我们通常意义上的流水线,与工业上的生产流水线,或者是CPU指令执行的流水线具有相似的意义。如果对“流水线”概念有所了解,就会明白其直接目的就是提高执行的效率。假如没有流水线,所有的处理都放到单独一个阶段执行,那么一个顶点从开始被处理到最终处理完毕的过程中,后面的顶点只能处于等待状态。假设整个处理过程时间为T,那么系统的吞吐量就相当于1/T。 如果把整个处理过程合理地分成不同的阶段,我们假设平均分成了5个阶段(P1,P2,P3,P4,P5,且每个阶段执行时间为T/5),各个阶段可以互不影响地独立工作,即一个顶点在执行完P1进入P2阶段后下一个顶点马上可以进入P1阶段开始执行。这种情况下在任意时刻,五个阶段就可以同时工作,效率自然会大大提高。此时的吞吐量为5/T这个事实就是证明。当然,真正的管线不同的阶段执行时间一般不可能完全一样的,这时整个管线的效率瓶颈取决于时间最长的那个,假设为Tmax,则整个管线的吞吐量为1/Tmax。
在D3D11中,整个渲染管线可以分成如下几个主要阶段:
从最开始的Input Assembler阶段到最终的Output Merger阶段,每个阶段处理完的输出,传递给下一个阶段,作为其输入。
下面针对各个阶段逐个进行介绍:
1. Input Assembler Stage
这个阶段的主要目的是根据用户提供的顶点及索引信息,构建多边形,主要有点、线段、三角形。给定顶点和索引,构建多边形的方法取决于所使用的基本图元的拓扑类型(Primitive Topology)。D3D11中常见的拓扑类型有以下几种:
1. Point List:定义的每一个顶点作为一个单独的点进行绘制,如图:
2. Line Strip:所有的顶点按顺序逐个连接成线段,如图:
3. Line List:所有的顶点按顺序两两配对连接成线段,如图:
4. Triangle Strip:所有的顶点按顺序组成三角形,前三个顶点为第一个三角形,从第四个开始每个顶点与位于其前面的两个顶点组成一个三角形,如图:
5. Triangle List:类似于Line List,按顺序三三配对组成三角形,如图:
举个例子,对于下图,9个顶点及拓扑类型Triangle List,对应索引:[0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8]。
Input Assembler会三个三个地读取索引组成三角形,形成图中的七个三角形。
对于另一个图,4个顶点及拓扑类型Triangle Strip,对应索引:[1,2,0,3]。
此时,GPU读取[1,2,0]组成第一个三角形,[0,2,3]组成第二个三角形。
【注意】:从上面可以看出,对于奇数序号三角形和偶数序号三角形,读取索引顺序是不一样的。偶数三角形三个顶点索引顺序跟用户指定的一致([1,2,0]),奇数三角形前两个索引顺序与用户指定的相反([0,2,3]中的0和2与用户指定索引2,0反序)。这是由GPU自动完成的,以保证所有的三角形顶点顺序一致地按照顺时针(或逆时针)排列。
此外,三角形的奇、偶是以0为基准的。
除此之外还有Control Point PatchList类型,由于其主要用于Tessellation阶段作为控制点使用,这里不多作介绍,在后面学习到曲面细分技术时再来了解它。
2. Vertex Shader Stage
这是一个完全可编程的阶段,即完全由程序员自己来实现。该阶段的核心为模型顶点的各种空间变化。3D图形学定义了以下几种坐标系空间:
1. 模型空间:Model Space,也叫Local Space
直观的讲,这个坐标系一般以模型中心为原点,所有的模型在建模的时候给定的模型顶点坐标都以这个坐标系为基准,用户最开始所指定的顶点坐标也是位于该空间。
给出模型空间的好处在于方便建模,以及单个模型的重重利用。因为一个物体可被放置到场景的多个地方,这时每个物体的顶点坐标显然是不一样的,但可以共享同一个模型。
2. 世界空间:World Space
这个坐标系即3D场景给各个物体指定坐标的基准。场景有不同的物体具有不同的世界坐标。
3. 视角空间:View Space
这个坐标是以照相机为基准的,以照相机位置为原点,照相机朝向z轴正方向,右边为x轴正方向,上边为y轴正方向。之所以设置这个坐标系,主要是为了主便接下来的投影及裁剪操作。如果直接在世界空间下进行,由于照相机位置、朝向灵活多变,计算将会十分复杂。有了视角空间,一切计算以原点为基准,会大大方便计算。
4. 投影、裁剪空间:Projection Clip Space
这个空间即世界空间的物体被投影到相应的投影面上之后,继而进行裁剪操作所在的空间。
用户指定的所有顶点都是基于模型空间的,在Vertex Shader阶段,每个顶点要依次经历所有这些空间,最终转换为屏幕上对应的二维坐标,不同空间之间的切换称为“空间变换”,实现空间变换的基本工作即矩阵。
I. 模型、世界空间变换
从模型空间到世界的变换主要包括:缩放、旋转和平移。缩放和旋转操作通过3X3矩阵及可实现,为了实现平移操作,则需要4X4型矩阵,因此所有的空间变换统一采用4X4矩阵,且顶点坐标也采用相应的[x,y,z,w]型。大多数情况下,w=1,[x,y,z]与顶点本身坐标保持一致。此外,多出的w在投影变换中发挥了至关重要的作用。
II. 世界、视角空间变换
从世界空间到视角空间通过相应的“视角矩阵”实现。为了更好的理解视角矩阵的作用,可以这样理解:
在任意时刻,相机在世界空间都有一个位置坐标[Px,Py,Pz],同时也有相应的朝向,我们可以用三个坐标轴来确定其朝向,即U,V,W,U指向相机右侧,V指定相机上侧,W为相机注视方向。那么视角矩阵的作用,即把相机位置移回到世界坐标系原点,且三个坐标轴U,V,W与世界坐标系的X,Y,Z分别重合。这就是视角矩阵的目的!理解这一点对于自己来实现灵活的照相机非常重要,因为实现照相机最重要的一点即根据任意时刻相机的位置、朝向来计算其视角矩阵。
III. 视角、投影空间变换
从视角空间到投影、裁剪空间依靠“投影矩阵”来实现。投影有两种:正交投影和透明投影,大多数情况下,比如游戏中,用到的投影为透视投影,因为这种投影方式与人观察物体的方式是一样的。
要计算投影矩阵,首先要确定照相机的几项基本参数:近、远平面(n,f),投影平面的宽、高比(r),以及上、下视野角度大小(a)。近、远平面规定照相机能看到的最近和最远的距离。有了这些参数,所有能投影到屏幕上的点组成了如下所示的多面体:
即一个被切掉顶部的金字塔。
投影变换的效果即把这个多面体转换成长方体,长、宽分别位于[-1,1]之间,z位于[0,1]之间。真正的裁剪操作就是在这个长方体中进行的,因此将大大简化裁剪的计算。
关于投影变换,要注意一点的是,很多人误以为投影即把三维顶点投影到二维平面 上,投影变换后顶点的z坐标即被抛弃,只剩下x,y坐标用于后面的屏幕变换。实际上,投影变换后z坐标并没有消失,位于[0,1]之间。屏幕坐标的变换不再使用z坐标,但z坐标在后面的Output Merger阶段用于深度比较时发挥的关键作用。
整个顶点着色阶段到此结束。
3. Hull Shader Stage、Tessellation Stage、Domain Shader Stage
这三个阶段其实共属于Tessellation Stage,即曲面细分阶段,可选阶段。这个是D3D11中刚加入的一个高级特性,主要用于如LOD(Level of Detail)技术中。
该阶段主要的好处即在运行期可利用显卡动态的增加模型多边形的细节。例如在地形渲染中,根据距离照相机距离的远近程序采用不同的细节等级,近距离的部分采用高精度的多边形,远距离的部分采用低精度的多边形,以同时满足视觉上的要求又优化了渲染。 另一个利用曲面细分的典型例子是Displacement Mapping,这个在后面学习法线映射时会介绍。
4. Geometry Shader Stage
这个也是D3D11中加入的新特性,为可选阶段。在这个阶段中,输入为一个完整的多边形,点、线段或三角形。这个阶段直接对输入的多边形进行操作,可以把其消灭,不再往下一个阶段传输;也可以衍生出新的多边形出来。总之,该阶段操作的对象是完整的多边形,相比于顶点着色阶段,其操作的对象是单个的顶点。
5. Rasterizer Stage
这个阶段属于不可编程阶段,即完全由硬件实现,主要包括:
1. 视口变换
即把投影变换后得到的顶点的x,y坐标,根据用户设定的视口参数,变换到屏幕上对应的坐标。
2. 隐藏面消除(Backface Culling)
即使经过了裁剪操作,对于每一个物体,照相机只能看到其正对着相机的一面,对背对着相机的一面是看不到的。这一步的任务即是把看不到的这一面消除掉,以免继续对其进行处理影响效率。
为了确定一个多边形是正对着相机还是背对着相机,就要用到我们在Input Assembler Stage指定多边形(三角形)时的顶点顺序了。默认情况下,D3D规定按顺时针顺序指定的三角形属于正面,逆时针为背面。如果V0,V1,V2按顺时针排序,则该三角形为正面,将通过这一步的检测,如果为逆时针,则被抛弃。
确定顺时针还是逆时针,可以用如下方法:
给出两个向量:E0 = V1-V0 E1 = V2-V0对E0和E1进行叉乘操作N = E0 X E1。如果N指向相机,则为正面,否则为背面。
3. 顶点属性的插值计算
在Input Assembler Stage中,用户指定的仅仅是一系列的多边形,以三角形为合例,每个三角形由三个顶点组成,每个顶点包含一系列的属性,如坐标、法线、纹理坐标等等。在经过顶点着色阶段、视口变换后,这三个顶点在屏幕上都有对应的坐标。但为了显示该三角形,仅仅三个顶点显然是不够的,为了计算该三角形所覆盖的屏幕上每个像素处的属性,要进行正确的插值计算。
注意,插值计算的要求是,在世界空间中保证以三个顶点为基准进行线性计算,在二维屏幕上,以相应的投影后的三个顶点进行插值时不再是线性。插值方法如下图所示:
不过,这一点了解一下就够了,毕竟这一步插值计算是完全由硬件完成的。如果想有兴趣了解的更多一点,可以参考《Mathematics for 3D Game Programming and Computer Graphics》,针对这个有详细讲解。
6. Pixel Shader Stage
这个阶段也是完全可编程的一个阶段,也是跟顶点着色器阶段一样十分重要的阶段。因为在D3D11中,顶点着色器和像素着色器是程序员实现的两个最基本的阶段。
像素着色器,从名字上也很容易看出,是在像素级别上进行的计算。对于三维空间中投影到屏幕上的每一个多边形,针对其在屏幕上所覆盖的每一个像素,逐个进行像素着色计算。这一阶段接受的数据是经过插值计算后的顶点属性。输出的是颜色值,以提供给下一阶段处理。
像素着色器给程序员提供了相当多的灵活性和自由度,通过各种不同的算法来计算片段颜色以实现各种特效。
7. Output Merger Stage
这个阶段即输出片段的混合阶段。该阶段不可编程,但是高度可调节。程序员可以灵活地调整管线的状态来实现不同的特效。比如针对深度缓冲区、模板缓冲区,混合等可以有各种不同的状态。利用它们来实现各种特效后面也有特别详细的介绍。
D3D11整个渲染管线到这一步就结束了,小结一下:
3D渲染管线的作用就是,给定场景描述:顶点属性、索引、照相机、光源、材质等信息,计算其在光栅显示器屏幕上对应的显示结果。其主要阶段包括:Input Assembler Stage、Vertex Shader Stage、Tessellation Stage(包括三个子阶段:Hull Shader Stage、Tessellator、Domain Shader Stage)、Geometry Shader Stage、Rasterizer Stage、Pixel Shader Stage、Output Merger Stage。
3D渲染管线对于学习3D图形学还是D3D API,都相当重要。因此,请务必花时间深刻理解之,否则是不可能灵活地写Shader的。