WHCSRL 技术网

Chapter One DirectX Computer Graphics (Part I)

计算机图形学是一种使用数学算法将二维或三维图形转化为计算机显示器的栅格形式(像素单位)的科学。计算机图形学的研究对象是图形(3D模型),我们需要使用这些图形来模拟现实世界的物体。构成图形的要素有两类,一是形状的点线面等几何要素,二是反映物体表面的属性,如颜色等非几何要素。在计算机中表示图形的方法通常有三种:线框,表面和实体。线框模型采用点和线的集合来描述三维图形。线框模型的优点就是结构简单,缺点不能表示物体的表面。表面模型在线框模型的基础上,增加了物体面的信息,用面的集合来表示三维图形。表面模型又分为平面模型和曲面模型。表面模型也存在一些不足,它只能表示物体的表面特征,而不能展示物体的内部属性。因此,一个用表面模型表示的三维图形只是一个空壳。实体模型则是在表面模型的基础上记录了物体的拓扑信息。

当前游戏行业广泛使用平面模型(三角面网格/四角面网格)来表示三维图形(3D模型)。三角面网格可以采用足够多的表面去逼近复杂的曲面。在三维空间里,一个三角面由三个不同位置的点构成的。每一个点都有自己的X、Y、Z坐标。两个点连接起来,构成一条线,三个点两两相连,就构成了一个三角面。大量的三角面共用顶点和边线,就构成了一个三维网格模型。从存储的角度看,三角面网格模型只是由一个个顶点组成,既没有“面”,也没有“体”。因为平面可以由三个顶点来确定,体积可以由闭合的面确定,不用额外存储信息,以此达到最小化存储的目的。所以三维模型看上去是由若干个三角面组成,实际存储时都是一些顶点而已,这些顶点包含了重要几何信息(位置坐标),还有其他非几何信息(颜色)。
 

坐标系是为了定量描述物体的位置变化。例如,经纬度是我们经常使用的地理坐标系统,它是一种利用三度空间的球面来定义地球上的空间的球面坐标系统,能够标示地球上的任何一个位置。我们使用GPS定位出来的就是经纬度。在2D和3D的游戏世界中,坐标系就是用来确定所有物体的位置变化。2D空间就是一个矩形平面,例如我们的电脑和手机屏幕,它只有宽度和高度。在2D下,采用的是笛卡尔坐标系,即x轴为水平方向,向右为正。y轴为垂直x轴,向上为正。原点位于坐标值为(x=0,y=0)处。在笛卡尔坐标系中,每一个点都可以有一个准确的x,y值来表示其位置。而3D比2D多一个深度,笛卡尔坐标系多了一个z轴,z轴同时垂直于x轴和y轴,向外为正。我们可以理解垂直于我们的电脑和手机屏幕向里/向外的方向就是z轴,但是它并不存在的,我们的电脑和手机屏幕仍然是一个2D空间。那么,如何将3D空间下的模型显示到2D平面上呢?想要在计算机屏幕上输出三维场景就要通过投影变换来降低维数。投影就是从投影中心(视点)发出射线,穿过三维物体上的每一点后,与投影平面相交所形成的交点集合,因此把三维坐标转变为二维坐标的过程称之为投影变换。这个就类似于我们在灯光下玩手影是一个道理,我们使用手来模拟物体的3D形状,但是通过灯光投影后,在墙上呈现的则是一个2D形状。投影可分为平行投影和透视投影。透视投影的特点是所有投影线都是从空间一点(视点)投射,距离视点近的物体投影大些,距离视点远的物体投影小些。透视投影模拟了人眼观察物体的过程,符合人类视觉习惯。3D游戏使用的就是透视投影(透视摄像机)。而平行投影则不会产生近大远小的效果,应为平行投影的投影线是一个与投影平面一样大的矩形平面,也就是说平行投影的形状是一个立方体,从一个面投影到对立的另一面上,这样不管物体在立方体那个位置,它在投影面上的成像都是固定不变的大小。因此2D游戏经常使用平行投影(正交摄像机)。关于透视摄像机和正交摄像机,我们后续会继续详细介绍。

投影变换

这里我们着重讲解一些3D投影,首先我们要清楚的是,3D游戏中存在以下几种坐标系,物体在不同坐标系之间呈现的时候,需要通过矩阵进行变换。请注意,这种转换并不是说物体的位置真实发生了改变,而是对应的参照物改变了,因此位置也就相对改变了。

本地坐标系,也称之为局部坐标系。我们使用三角面表示三维模型,存储的三维模型的顶点中包含了坐标信息。这个坐标信息实际就是本地坐标系,它的坐标原点在三维模型的某一个位置(可以认为物体的中心)。所有的顶点坐标信息都是基于这个原点做参照。

世界坐标系,就是游戏世界坐标系。所有的三维模型都要在游戏世界拥有自己的位置坐标,这个坐标就是基于世界坐标系原点做参照。将三维模型放置到游戏世界某一个位置的过程,就是将三维模型中顶点的本地坐标转换成世界坐标的过程。这种坐标系转换是通过变换矩阵实现的。矩阵(Matrix)是一个按照长方阵列排列的数字集合,属于高等代数学。我们可以简单的理解一个M x N维的矩阵就是一个m行和n列的二维数字数组。三维模型变换矩阵是一个4 x 4方阵。三维模型变换过程就是表示三维模型顶点集合的规范化齐次坐标矩阵与某一个变换矩阵相乘,它的本质就是三维模型的顶点从一个位置变换到另一个位置。三维模型变换分为平移变换,缩放变换和旋转变换,因此也就对应了平移变换矩阵,缩放变换矩阵和旋转变换矩阵三种类型。三维模型的动画也都是通过这三种变换矩阵来实现的。

观察者坐标系,也可以理解为摄像机坐标系,或者视图坐标系。我们看到的游戏世界的三维模型,都是通过摄像机采集的,就如同我们自己的眼睛看到这个世界是一样的。摄像机的移动会影响到我们看到游戏世界的内容,也就是说世界空间中的所有三维模型物体都跟随着摄像机一同进行变换。这种变换称之为取景变换(也叫视角变换),就是将三维模型的世界坐标转换成观察者的坐标。观察者坐标系的原点就是摄像机的位置。这个过程需要一个取景变换矩阵(视图矩阵)来完成。

在观察者坐标系中,我们的最终任务是要获取3D场景的2D表示,也就是我们上文提到的透视投影。投影中心(视点)就是摄像机,投影平面也称之为投影窗口(正方形平面)。透视投影是按照三维模型近大远小的原则进行投影,距离摄像机近的就大一些,距离摄像机远的就小一些。透视投影中有一个视域体的概念,也称之为平截台体,它就是一个由视点连接投影窗口(正方形平面)形成的一个正四棱锥。在正四棱锥内的三维模型物体将会投影到投影窗口中。这个过程需要一个投影变换矩阵来完成。投影变换矩阵就是将三维模型的观察者坐标转换为二维平面坐标。在这个过程中丢失z方向的坐标信息会以另一种方式保存。

视口坐标系,就是屏幕上的游戏窗口,它的原点在游戏窗口右上角。视口变换就是将投影窗口(二维平面)的物体坐标转换到屏幕上的一块矩形坐标区域中,本质是一个放缩+平移的过程(2D变换2D过程),这个过程需要视口变换矩阵来完成。

备注:以上所有变换矩阵的数学推导过程都不需要大家理解,在实际开发过程中,我们都可以通过API中方法创建矩阵并对模型进行变换,这些方法的参数基本都是符合人性思维的。例如平移变换的话,只需要给出目标坐标值,就能创建平移矩阵并对模型进行平移变换。在比如取景变换,只需要给出摄像机的位置以及观察点即可,投影变换矩阵则是通过弧度和远近面来定义视域体(正四棱锥)即可,都非常的容易理解。

光照和材质

在计算机图形学当中,我们的最终目的就是在屏幕上绘制图像,而这些图像的表示方式就是颜色。我们的电脑屏幕和手机屏幕都是由像素点组成的,像素点越多,屏幕显示越清晰。每个像素点的颜色在计算机中一般使用RGB来表示,也就是说颜色由红色(Red),绿色(Green)和蓝色(Blue)组成。这三个分量的混合决定了最终的颜色。在三维模型的顶点中就有颜色属性的存储,我们就可以使用RGB来定义顶点的颜色值。但是,这种简单的表达方式并不能模拟绘制出现实世界丰富多彩的颜色。我们知道在自然界中,物体的颜色是靠它所反射的太阳光的颜色决定的,比如,某物体大量反射光线中的红色部分,那么我们看到的这个物体就呈现红色。因此,我们需要尽可能的模拟这种物理现象,才能绘制出接近现实世界的图像。真实感图形绘制是计算机图形学的一个重要组成部分,它综合利用数学、物理等知识在计算机图形设备上生成像彩色照片那样的具有真实感的图形。

为了能够真实模拟物体的表面细节,我们需要在游戏世界里创建光源,物体根据自身的材质和纹理图案来确定最终的显示效果。我们首先说一下光源,光源的本质就是颜色。光源由Ambient color(环境色),Diffuse color(漫反色),以及Specular color(高光色)三种颜色所组成。也就是说,我们定义一个光源的时候,需要指定三种类型的颜色值。这三种颜色的光对应物体的影响效果是不一样的。计算机中的光源就是根据光在物体上的反射效果而定义。

漫反射(Diffuse Light)表示光源在物体表面的反射光中那些向空间各方向均匀反射出去的光,这些光常称为漫反射光。我们所说的物体颜色基本上指的就是漫反射产生的的颜色。

如果光朝一特定方向进行反射的话,那么这个区域就会非常的亮,这也是我们物理学上的镜面反射。例如,光照射一个光滑的金属球时会在球面上形成一块特别亮的区域,也就是高光效果。这种效果则是由高光色(Specular Light)来决定的。对于较光滑物体,其镜面反射光的高光区域小而亮;相反,粗糙表面的镜面反射光呈发散状态,其高光区域大而不亮。

一般情况,光的反射不会发生一次。例如,一束光可以通过一面镜子反射照亮其他的物体。也就是说,物体除了受到光源的直接影响之外,还会受到从周围环境来的反射光(例如来自地面、天空、墙壁等的反射光)的间接影响,这些光常统称为环境光(Ambient Light)。

当有了光照,还有另一个决定物体颜色的重要因素——材质。每一个物体都有它独特的材质,比如说,光滑的玻璃,粗糙的木头,坚硬的岩石等。这些物体通过发射不同的光来呈现不同的表面效果。我们在定义光源的时候,通过三种反射效果的颜色来构造光。同样的,材质也分成环境光反射、漫反射和高光反射三个部分。在进行光照计算时,物体对环境光的反射率与光源的环境光结合,对漫反射光的反射率与光源的漫反射光结合,对镜面光的反射率与光源的镜面反射光结合。材质的颜色与光源的颜色是一一对应关系。

对于光源,R、G、B值等于R、G、B对其最大强度的百分比。若光源颜色的R、G、B值都是1.0,则是最强的白光;若值变为0.5,颜色仍为白色,但强度为原来的一半,于是表现为灰色;若R=1.0,G=0.0,B=0.0,则光源为红色。我们就是使用这种方式,分别使用三组RGB值来定义漫反射光,高光和环境光。

对于材质,R、G、B值为材质对光的R、G、B成分的反射率。比如,一种材质的R=1.0,G=0.5,B=0.0,则材质反射全部的红色,一半的绿色,不反射蓝色。同理,我们使用这种方式,分别使用三组RGB值来定义漫反射光的反射,高光反射和环境光发射。

光照模型就是一个计算公式,若光源颜色为(LR, LG, LB),材质颜色为(MR, MG, MB),那么,最终到达眼睛的光的颜色为(LR*MR, LG*MG, LB*MB)。当然,这个公式并不是这么简单。一般情况下,影响物体最终颜色的首先就是漫反射光,其次是环境光,高光只会影响物体产生的亮点。需要注意的是,光照模型在游戏运行中的性能开销非常大。

纹理映射

物体表面细节分为两类:1.表面的颜色纹理,颜色纹理取决于物体表面的光学属性(反色率)2.表面的几何纹理,几何纹理由物体表面的微观几何形状决定(凹凸)。仅仅使用上述的光照模型我们很难去绘制出现实世界物体的微观细节。纹理贴图(纹理映射)在计算机图形学中就是把一张2D图像包裹到3D物体的表面。纹理贴图给物体提供了丰富的细节,用简单的方式模拟出了复杂的外观。这个包裹的过程就是映射的过程。比如绘制一面砖墙,就可以用一幅真实的砖墙图像作为纹理贴到一个矩形上,就绘制成了一个砖墙的图形。这里,我们只需要了解这个映射的原理是什么。首先纹理贴图是一个正方形(大部分情况),在纹理贴图的2维坐标系中,左上角是原点(0,0)位置,右下角是(1.0, 1.0)位置。纹理中每一个像素点都有一个纹理坐标(UV坐标)。在三维模型的顶点中同样对应着一个属性,就是UV坐标值,也就是说我们可以将三维模型上的每一个顶点都对应上纹理贴图上的一个点,这样纹理贴图就会按照这种映射关系被附属在三维模型上面了。纹理贴图映射其实就是使用图像来表现物体的颜色,当然最终呈现的色彩还要考虑到光照模型。

纹理映射技术使用非常的广泛,有时候我们可以将光源信息存储到纹理贴图中,这种纹理贴图称之为“光照贴图”。我们将光照贴图和物体的纹理贴图同时施加到物体表面,就能让物体在没有光源的情况下展示出有光源照射的相同效果。我们讲过,光照模型在游戏运行中的性能开销非常大,而这种“光照贴图”则是一种比较折中的解决方案。除了“光照贴图”之外,还有类似于“凹凸贴图”,“高光贴图”等等,实现不同的视觉效果。

游戏动画

传统的动画,一般是对一个物体对象进行位移、旋转、缩放,然后把动作以关键帧的数据形式记录下来,在播放的时候按照关键帧时间对物体对象进行位移、旋转、缩放,并在关键帧与关键帧之间做插值运算,最终形成一套连续的完整的动作动画。动画就是把每帧的顶点数据输出到文件中,所以3D模型的一个动作就是一系列的模型。这样做的虽然简单,但是会因为产生大量的动画文件。后来人们根据人体动作的原理创建了骨骼动画。骨架由一系列具有层次关系的关节(骨骼)组成,是一种树结构,该结构中存在一个是根关节,其它关节是根关节的子孙。父关节运动能影响子关节运动,但子关节运动对父关节不产生影响。这个其实就是模拟我们人体骨骼的运动,所以这种动画称之为骨骼动画。骨骼的本质是什么?它就是变换矩阵。我们之前讲过模型的平移,旋转和缩放变换,这些都是通过矩阵来实现的。动画的形成也就是通过对模型的其中一部分进行平移,旋转和缩放变换。

骨架被外部皮肤包围,皮肤由多边形(顶点)构成。骨架中的每一根骨骼都会影响皮肤的形状和位置。而皮肤则可以由多个骨骼共同影响,只是影响的权值是不一样的。当移动骨骼时,会将骨骼的变换矩阵施加给与骨骼绑定的皮肤顶点,皮肤顶点的改动就更改模型的几何形状,从而显示出动画效果。皮肤其实就是存储顶点的同时,还是存储了影响它的骨骼。

骨骼动画的特点是,需要做动画的物体对象本身不记录位移、旋转、缩放信息,而是通过“骨骼”记录动画信息(按照时间记录骨骼变换矩阵),然后物体对象本身只记录受到骨骼物体影响的权重(皮肤)。在播放的时候,通过骨骼物体的关键帧和物体对象记录的权重,让动画重现。也就是说,一个带骨骼动画的模型需要包含三部分信息:骨架,皮肤和帧动画。骨架就是一个骨骼层次结构,例如一个人体骨骼结构可能包括头骨骼,胸骨骼,上臂骨骼,下臂骨骼,大腿骨骼,小腿骨骼等等。皮肤则是模型的顶点信息和影响该顶点的骨骼信息。帧动画就是按照时间来记录骨架中各个骨骼的变换矩阵。在三维模型当中。bone就是骨骼,皮肤就是skin mesh,也叫蒙皮网格。骨骼动画都是在3ds max或者maya中完成的,我们只需要根据图形API进行文件解析,并播放对应的动画即可。

游戏API和引擎

目前游戏领域有两个图形API,一个是OpenGL,一个是DirectX。前者是一项开源的标准,主要用于图形应用和3D游戏。DirectX则是微软制定的API标准。由于OpenGL跨平台和免费性,得到很多显卡制造商的支持。在端游开发方面,DirectX占据绝对的优势。但是随着移动互联网的发展,OpenGL则是Android和ios的首选。但是OpenGL只是图形函数库,不能用于输入,声音,网络的开发,需要借助第三方来完成。DirectX并不是一个单纯的图形API,它是由微软公司开发的用途广泛的API,它包含有Direct Graphics、Direct Input、Direct Play、Direct Sound、Direct Show等多个组件,它提供了一整套的多媒体接口方案。

2002年,微软发布DirectX9.0(DirectX 9.0b和DirectX 9.0c)。这是DirectX发展历程中的重要转折点。2006年,微软发布Windows Vista操作系统,内置了DirectX 10。2009年,微软发布Windows 7操作系统,内置了DirectX 11。2014年,微软发布了最新的DirectX 12。

从技术层面讲,游戏产品本质上是一种以游戏引擎技术为核心的模拟仿真软件。3D游戏引擎的架构设计非常复杂,设计到很多系统协调工作才能使用户获得最佳的交互控制和感官体验。这些系统包括图形渲染系统,资源管理系统,音视频系统,物理模拟系统,网络通信系统,脚本系统和人工智能系统等等。此外,游戏引擎还需要为策划和美工等非编程人员提供辅助工具,包括地图编辑器,关卡编辑器,脚本编辑器,特效编辑器等等。


Unity 3D引擎和 Unreal Engine(虚幻引擎)是当前最为流行的游戏引擎。Cocos引擎也是一款非常流行的国产游戏引擎。当然还有一些开源的游戏引擎,比如OGRE引擎,Cryengine引擎。很多大的公司都有自己的游戏引擎,但基本上都是自己公司使用,不对外开放的。我们的游戏开发课程主要是Unity 3D引擎的学习,以及使用Unity 3D来开发游戏项目。Unreal Engine引擎会在后期中介绍。个人认为,Unreal Engine才是最棒的游戏引擎,值得学习!

 本课程的所有代码案例下载地址:

workspace.zip

备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!

推荐阅读