【题外话】html
最近要作一个3D动画演示的程序,因为比较熟悉C#语言,再加上XNA对模型的支持比较好,故选择了XNA平台。不过从网上找到不少XNA的入门文章,发现大都须要一些3D基础,而我以前并无接触过游戏以及3D相关的开发,因此我来从另外一个角度整理下入门XNA。本文尽可能少涉及3D及数学方面的知识,由于同类文章介绍的挺多的。windows
【系列索引】框架
【文章索引】ide
虽然微软在推出了XNA 4.0以后就再也没有升级过XNA的版本,但XNA仍是在.NET平台上比较方便的3D框架。因为我使用的是VS2013,而XNA 4.0的安装程序只认VS2010,因此须要安装一个使用VS2010 Shell的程序(好比我使用的是SQL Server 2012 Management Studio,固然也能够安装VS2010 Express)才能经过XNA 4.0的安装。安装完后会自动在VS2010下添加相关扩展模板,但不会在更高版本添加,能够参考 http://ryan-lange.com/xna-game-studio-4-0-visual-studio-2012/ 将扩展模板添加到VS2012或2013中(VS2013须要将其中的版本号改成12.0)。visual-studio
XNA 4.0相对以前的3.1作了不少修改,不只代码上进行了不少调整,默认建立的项目也与以前的不一样。建立XNA 4.0的Windows Game项目后,默认会建立两个项目,分别是WindowsGame以及WindowsGameContent,前者存放程序的逻辑代码,然后者则存放程序所须要的资源(模型、纹理等等),与其余项目不一样的是,XNA项目增长了一个Content Reference内容引用,能够将逻辑与资源拆分红不一样的项目,即由逻辑代码的项目引用资源项目,固然也能够合并在一块儿。学习
在建立的WindowsGame项目中,与其余Windows程序同样都包含一个Program.cs,除此以外同时还有一个Game.cs。须要说明的是,与日常开发Windows应用以基于事件的方式不一样,开发XNA(以及其余游戏框架)的应用是以基于轮询的方式。在Game.cs文件中,除了构造方法外还会生成如下几个方法,其执行顺序和微软给出的说明以下:ui
其中程序的主要部分就是Update()和Draw()两个方法,整个程序在运行时,几乎就是这两个方法在不断重复执行。须要说明的是,对于XNA,在默认状况下,执行一次Update()和Draw()是要控制在必定时间的(默认为1/60s,即60FPS),若是执行一次Update()和Draw()的时间小于这个时间将会进行等待,若是超过这个时间则会跳帧(不执行Draw()),固然也能够修改Game类中的TargetElapsedTime来改变这个时间,或者修改IsFixedTimeStep=false使得程序帧数能多大就多大。this
对于二维的画面,咱们能够直接使用屏幕的坐标系;而对于三维的画面,咱们还须要将三维世界投影到二维的屏幕上。那么,咱们就须要一个计算如何将三维世界投影到二维屏幕的工具,那么摄像机就是实现这个功能的。实际上,这里的摄像机与咱们平时拿摄像机录像是同样的,在屏幕上显示的内容就是摄像机录下的内容。
首先须要说明的是,在XNA中使用的右手坐标系(与Direct3D中使用的左手坐标系Z轴相反),也就是说按正常方向去看的话,向右是X轴正方向,向上是Y轴正方向,而指向本身的(向外)是Z轴正方向,以下图。
在三维框架中,不少信息的存储和表示都是用四维矩阵(Matrix类)来的。因此要表示一个摄像机,一般由两个矩阵组成,分别是 视图矩阵(View Matrix) 和 投影矩阵(Projection Matrix),其中视图矩阵表示了摄像机的位置、摄像机的朝向以及摄像机的上方向;而投影矩阵则表示了摄像机的视角以及视觉范围。虽然听上去很复杂,可是XNA提供了直接由具体的参数建立矩阵的方法。
对于视图矩阵的建立,可使用以下的方法:
Matrix.CreateLookAt(Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector);
其中cameraPosition为摄像机在空间内的三维坐标;cameraTarget为摄像机所指向目标的三维坐标;cameraUpVector则代表了哪一个方向是摄像机的向上方向(若是摄像机正着放的话,那么Y轴正方向为摄像机的向上方向)。
而对于投影矩阵的建立,则可使用以下的方法:
Matrix.CreatePerspectiveFieldOfView(float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance);
其中fieldOfView表示的是摄像机的视角弧度,范围为(0, Pi),一般为Pi/4(45°);aspectRatio为摄像机的长宽比,一般为屏幕的长宽比;nearPlaneDistance与farPlaneDistance则为当摄像机多近(远)时没法拍摄到物体。在下图中表示了fieldOfView与nearPlaneDistance和farPlaneDistance的关系,能够看到摄像机经过视角角度与距离能够产生一个近剪裁面和远剪裁面,而最终能投影的部分就是处在这两个平面之间的物体。
对于三维模型,XNA平台支持两种格式,分别是.x与.fbx文件,其中后者在不少软件中都支持,好比Maya、MotionBuilder等等。对于模型的导入,只须要将模型文件拖到Content项目中便可。不过须要说明的是,为了保证效率等,XNA程序在运行时并非调用的fbx等模型文件,而是经过Content Pipeline内容管道进行处理,编译成扩展名为.xnb的一种中间格式,在程序运行时程序实际调用的为这些中间格式,以下图。
Content Pipeline中主要有两个重要的部分,分别是Importer以及Content Processor。其中Importer负责将导入的资源文件解析为XNA能够识别的XNA Game Studio Content Document Object Model (DOM)。系统已经支持了不少的文件格式,好比三维模型支持.fbx和.x,纹理支持.bmp、.dds、.dib、.hdr、.jpg、.pfm、.png、.ppm、.tga等文件等,详情能够参考这里。若是须要的文件格式在XNA框架中不支持,能够本身写新的Importer来支持更多的格式。
而Processor则根据前者解析后的内容存储为Output Type,以后再编译成.xnb文件,在默认状况下使用系统自带的Processor已经足够了,不过当想存储XNA默认没有存储的内容时则须要本身扩展Processor。
虽然上述说了这么多,但加载资源则只须要一行代码便可解决。在上述的Game类中提供了一个ContentManager的实例,名为Content,咱们可使用其来加载咱们的模型。ContentManager提供了一个名为Load的泛型方法,将资源类型以及资源的相对路径传入便可读取。好比咱们将名为dude.fbx的文件拖到Content项目中,而后只需在上述提到的LoadContent方法中添加以下的一行代码(须要在Game类中定义一个名为model的Model类型):
protected override void LoadContent() { // TODO: use this.Content to load your game content here this.model = this.Content.Load<Model>("dude"); }
而若是要将模型绘制到屏幕上,只要调用Model对象的Draw方法便可。不过Draw方法须要提供 World世界矩阵 以及 View视图矩阵 和 Projection投影矩阵,对于后两个矩阵咱们上文已经说明,而世界矩阵与视图矩阵相似,可使用以下的方法建立:
Matrix.CreateWorld(Vector3 position, Vector3 forward, Vector3 up);
其中position与up均与以前的CreateLookAt相似,为模型在世界中所处的三维坐标和哪一个方向是模型的向上方向;而forward则不一样,为模型的朝向向量,其仅仅表明方向。固然咱们也能够经过建立不一样的平移、旋转等矩阵,而后相乘获得世界矩阵。
接下来咱们能够在上述提到的Draw方法中添加以下的代码:
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here Matrix world = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up); Matrix cameraView = Matrix.CreateLookAt(new Vector3(120, 120, 120), Vector3.Zero, Vector3.Up); Matrix cameraProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.GraphicsDevice.Viewport.AspectRatio, 10.0F, 10000.0F); this.model.Draw(world, cameraView, cameraProjection); base.Draw(gameTime); }
其中Vector3.Zero、Forward、Up为系统预设的几个常量,分别为(0, 0, 0)、(0, 0, -1)以及(0, 1, 0);而经常使用弧度值在MathHelper中也能够找到,好比Pi、PiOver2(90度弧度)、PiOver4(45度弧度)等等;GraphicsDevice.Viewport.AspectRatio为显示区域的宽高比。
这样咱们就能够建立一个在(120, 120, 120)坐标上,朝向坐标原点的摄像机,并在坐标原点建立一个模型(因为dude模型的正面是朝Z轴负方向的,因此这里咱们选用的Z轴负方向为模型的朝向)。
文中提到的dude.fbx从微软提供的sample中得到:http://xbox.create.msdn.com/en-US/education/catalog/sample/skinned_model
本文全部代码能够从以下地址下载:http://files.cnblogs.com/mayswind/XNA_Sample_1.zip
虽然如今使用XNA的人愈来愈少了,可是这个有点相似我的学习笔记的文章仍是要正式开坑了。
【相关连接】