不论是构建自己的引擎还是改进或使用引擎,都需要了解关于引擎的基本概念。与游戏引擎基本概念同样重要的是D3D的基础知识。本节将介绍D3D的基础知识,以及推出第一个D3D程序所需的知识。这一节主要是理论,下一节将介绍实践练习。
图形硬件
学习图形编程首先需要对图形所涉及的硬件进行充分的了解。因为与其说D3D是一个游戏平台,还不如说是硬件接口,你在D3D上所做的所有操作实际上都是为了操纵图形硬件本身,如果不了解图形硬件,很难运用好D3D。
图像编程中,我们最感兴趣的硬件就是GPU,即图形处理单元。GPU和CPU结构上稍有不同,但主要区别还是它们的用途。CPU执行运算并控制整个计算机;GPU对图形执行运算,并将图形定向输出到显示器。
图形编程除了拥有自己独立的处理器(GPU)外,还有一块称为显存的独立物理内存与之一起工作。显存通常不在主板上,而是在显卡上,因此GPU可以快速对其进行访问。显存用于存储当前屏幕上的图像,以及可能用于编译下一张图像的所有数据。
就物理硬件而言,我们只需要知道这些即可。在大多数情况下,D3D负责管理何时将数据存储在系统内存还是存储在显存,程序员不需要关心。
Directx图形基础架构(DXGI)
Directx图形基础架构是一个组件,它是所有最新版本的Direct3D的基础。它的任务是处理一些基本任务,例如在屏幕上显示图像、找出监视器和显卡可以处理的分辨率。DXGI实际上不是Directx3D的一部分,它是Direct3D和其他图形组件的基础,充当了Direct3D和硬件之间的接口。
有很多直接处理DXGI的方法,但是我们并不需要涉及这些方法。重要的是知道这个组件的存在,因为Direct3D的某些部分专门处理DXGI。
交换链
GPU在其内存中包含一个指向像素缓冲区的指针,该缓冲区包含当前正在屏幕上显示的图像。当你需要渲染某些东西(例如3D模型或图像)时,GPU会更新这个缓冲区数组,并将此信息发送到显示器进行显示。然后,显示器从上到下重新绘制屏幕,用新图像替换旧图像。
但是这里存在一个小问题,因为大多数显示器的刷新速度在60HZ到100HZ,达不到实时渲染的刷新速度。如果正在刷新显示器的时候,另一个模型被渲染到GPU的缓冲区,那么该缓冲区中的旧图像就被丢弃,而保存新图像。最终显示在屏幕上的图像将被分成两部分,顶部显示的是旧图像而显示的是新图像,这种现象称为“撕裂”(tearing)。
为了避免这种情况,DXGI实现了一种称为交换(swapping)的机制。DXGI不会将图像直接渲染到正在屏幕上显示的缓冲区(称为前缓冲区),而是将图像绘制到一个辅助的像素缓冲区(称为后缓冲区)上。所有的图像首先绘制到后缓冲区上,绘制完成后,DXGI将使用后缓冲区的内容更新前缓冲区,并丢弃前缓冲区中的旧图像。
但是,这样做只是在一定程度上扩增了缓冲区的空间,但仍然会产生“撕裂”,因为在显示器刷新时前缓冲区还是可能会发生图像传输(GPU的速度远比显示器快)。为了避免这种情况(也就是使整个过程更快),DXGI为每个缓冲区(前后)设置了一个指针,并简单切换它们的值,这样后缓冲区就变成了前缓冲区(反之亦然),并且不会发生“撕裂”。
当然,我们还可以添加额外的后缓冲区来使我们的游戏具有更好的性能。
上述的这种设置称为“交换链”(swap chain),因为这里它是一个缓冲区链,在每次渲染新帧时都会交换指针位置。
渲染管道
渲染管道是图形编程中经常听到的东西,这是因为渲染管道是一切发生的地方。渲染管道是在屏幕上生成一张渲染3D图像的过程,它像流水线一样,一步一步,其中包含了在GPU上执行的步骤。
我们不会在这里介绍所有的步骤,只会介绍其中一部分,了解这些步骤之后才能上手编程,后续我们会介绍其余步骤。“输入组装”阶段是流水线的第一步,它的作用就是“咀嚼食物”,它从显存中收集你想要渲染的3D模型的信息,然后编译这些信息并准备进行渲染;“光栅化”阶段负责确定要在后缓冲区上绘制图像的位置,更具体的讲,它负责确定哪些确切像素将被绘制以及它们将是什么颜色;“输出合并”阶段是管道的最后一步,它的工作是将各个模型图像组合成一个完整的图像,并将该图像正确放置在后缓冲区上。
坐标系
如果不了解3D的基本数学知识,就无法进行3D编程。我们在这里不会介绍所有大学代数的知识,而只要理解3D坐标系的概念。在3D渲染中,我们应该知道两种坐标系。
1、笛卡尔坐标系:笛卡尔坐标系也可以理解为2D坐标系,换句话说,它是平面上准确定位一个点的系统。
2、3D坐标系:实际上,3D坐标系只是对2D坐标系进行了扩展,在2D坐标系的基础上,添加一个垂直于x轴和y轴的第三轴(Z轴),这样就具有了3D坐标。
3D几何
现在,我们将介绍如何将3D坐标系应用到游戏以及游戏编程。如果3D坐标系中的点表示空间中的位置,那么我们可以形成一组精确的位置,这些位置最终构成一个3D模型。当然,设置这么多的点会占用大量的内存空间,因此采用了一种更加简便的方法:采用三角形代替点集。我们之所以采用三角形,是因为三角形可以定位出几乎所有能够想象到的任何形状,例如立方体和球体,如下图所示:
由于可以有效利用三角形创建3D模型,因此Direct3D仅围绕三角形进行设计,然后将三角形组合成其他形状。我们是通过称为顶点集(Vertices)的东西去建立一个三角形,顶点集(Vertices)是顶点(vertex)的复数,而一个顶点是3D空间中一个确切的点,它由x、y、z三个值组成。在Direct3D中,我们对这个顶点进行了扩展,增加了这个顶点的各种属性,称为“3D空间中精确点的位置和属性”。
一个三角形由三个顶点组成,这三个顶点在程序中按顺时针顺序定义。编码后,这三个顶点形成一个平面,然后可以根据需要对其进行旋转、纹理化、定位和修改。
上图中显示的三角形由三个点组成:可以发现,这三个顶点的z值均为1,这是因为我们不是在讨论3D对象,而是一个2D平面的三角形,所以Z取什么值都没有本质的区别。
x = 0, y = 5, z = 1
x = 5, y = -5, z = 1
x = -5, y = -5, z = 1
要制作实际的3D模型,我们需要组合三角形。举一个简单的例子,立方体的每一面都是由两个三角形放置在一起构成的。然而,反复定义游戏中每个三角形的3D坐标太繁琐,我们要做的是创建一个顶点列表,其中包括了每个顶点的坐标和信息,以及它们进入的顺序。
图元(primitive)
图元是3D环境中的单个元素,它可以是一个三角形、一条直线、一个点或者其他任何元素。下面将介绍如何通过组合图元来创建3D对象:
1、点列表
点列表是顶点的列表,这些顶点在屏幕上显示为单个的点。这种方式对于渲染3D星空、创建虚线、在小地图上显示位置等方面十分有用。下图显示了如何在屏幕上显示“点列表”:
2、线列表
线列表也是一个顶点列表,这些顶点在每个奇数编号的顶点与下一个顶点之间创建单独的线段。这种方式可以用于多种效果,例如3D网格、大雨、轨迹线等。下图显示了如何在屏幕上显示“线列表”(这组顶点和上一组顶点相同):
3、线带
线带与线列表相似,但不同之处在于,此列表中的所有顶点均通过线段进行连接。这对于创建许多线框图像很有用,例如线框地形、草和其他不基于模型的对象。这种方式在程序调试中也十分有用。下图显示了如何在屏幕上显示线带:
4、三角形列表
三角形列表也是一个顶点列表,其中每三个顶点组构成一个单独的三角形。这种方式可以用于多种效果,例如力场、爆炸、物体拼接等。下图显示了如何在屏幕上显示“三角形列表”:
5、三角带
三角带也是一个顶点列表,这些顶点创建了一系列相互连接的三角形。这是处理3D图形时最常用的方法。这种方式主要是为你的游戏创建3D模型。下图显示了如何在屏幕上显示三角带。请注意,前三个顶点创建了一个三角形,然后每个顶点根据前两个顶点创建另一个三角形。
图元怪癖
在绘制图元时,只是了显示图元的一侧。其实可以显示正反两侧,但一个模型通常是封闭的,你看不到内部。如果一个模型是完全封闭的,则只需要绘制每个三角形的一侧。毕竟,绘制图元的两侧将花费两倍的时间。
一个三角形图元仅当其顶点按顺时针顺序给出时才绘制。如果你翻转了顶点顺序,就会变成逆时针顺序,因此未显示三角形:
有一种简单的方法来显示图元的两侧,即将图元显示两次,顺时针方向给出一个图元,逆时针方向给出一个图元。