我又开始了,接下来开始我的 XNA 3D 模型入门讲解:
创建XNA Game项目(此步骤自行操作),然后添加一个 Game Component,命名为Camera,修改默认代码如下:
public class Camera : Microsoft.Xna.Framework.GameComponent { //Camera matrices public Matrix view { get; protected set; } public Matrix projection { get; protected set; } public Camera(Game game, Vector3 pos, Vector3 target, Vector3 up) : base(game) { view = Matrix.CreateLookAt(pos, target, up); projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, (float)Game.Window.ClientBounds.Width /(float)Game.Window.ClientBounds.Height, 1, 3000); } public override void Initialize() { base.Initialize(); } public override void Update(GameTime gameTime) { base.Update(gameTime); } } }
在Game类添加代码:
public Camera camera { get; protected set; }
在Initialize中添加代码(实例化摄像机对象,同时将组件添加到组件集合):
camera = new Camera(this, new Vector3(0, 0, 50),Vector3.Zero, Vector3.Up); Components.Add(camera);
添加模型到项目:
在Content项目中添加一个Models文件夹,然后添加一个3D模型,如果你没有模型,可以下载源码,然后找到后缀名为.x的文件.
添加绘制3D模型的类:
class BasicModel { public Model model { get; protected set; } protected Matrix world = Matrix.Identity; public BasicModel(Model m) { model = m; } public virtual void Update() { } public void Draw(Camera camera) { Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); //遍历模型中的每个ModelMesh foreach (ModelMesh mesh in model.Meshes) { //遍历ModelMesh有对应该关系的BasicEffect对象 foreach (BasicEffect be in mesh.Effects) { be.EnableDefaultLighting(); be.Projection = camera.projection; be.View = camera.view; be.World = GetWorld() * mesh.ParentBone.Transform; } mesh.Draw(); } } public virtual Matrix GetWorld() { return world; } }
代码讲解:
第一个变量是Model类型的。Model类用来代表内存中的3D模型,这就好比你之前用Texture2D类来代表内存中的2D图像。
第二个变量是Matrix,代表该特定模型的世界(world)。该矩阵代表在哪绘制,如何旋转和缩放模型等。
给BasicModel类添加一个构造方法。该构造方法只需要一个Model类型的参数,并把该参数赋给模型成员。
创建一个空的虚方法Update,它能够被BasicModel派生类重载,用于定制更新过程中应该执行的动作。
然后需要绘制3D模型,有点麻烦哦,看我慢慢道来:
这些场景,以.X为后缀名,每一个都包含不止一个物体。这些物体,也称之为网格(meshes),存放在模型中。在XNA中,Model类用ModelMesh对象来表示这些网格。Model类的Meshes属性包含有一系列ModelMesh对象。
网格包含有用于绘制该网格所需的材质、颜色和纹理等信息。网格的各个部分没有必要使用同样的颜色和纹理,你可以让一个网格包含多种材质。为了存放这些数据,网格由好几部分构成。ModelMesh类在一个名为MeshParts的
属性里面存放了一系列的ModelMeshParts。每个MeshParts均包含绘制MeshPart所需的材质和绘制MeshPart所需的Effect对象。
默认情况下,Model的MeshPart所用到的Effect是BasicEffect类型的。BasicEffect派生自Effect,它给你提供一种不需要独自创建HLSL效果文件就能在3D绘制物体的一种方法。
最后,每一个ModelMesh都有一个变换(transformation),能移动网格到模型内合适的位置。
为了搞清这一切是怎么工作的,想象一辆带方向盘的汽车模型。用一个Model来代表整个场景(汽车和方向盘一起)。Model的Meshes属性可能有两个不同的ModelMesh对象,一个是汽车的、一个是方向盘的。每一个Meshes都有
一个变换能把物体放在正确的位置,汽车有可能摆放在某个地方(比如说原点),方向盘相应地摆在某个合适的位置。除此之外,每个Meshes含有一种改变网格样子的材质(比如说汽车可能用到那些使它看起来呈现闪红的材质,方向盘可
能用到那些暗黑色的材质),也含有绘制ModelMesh所需的Effect。
绘制的代码见Draw方法中:
前两行代码处理一些叫做骨骼(bones)的东西。骨骼用来把物体放置在模型空间内合适的位置。通常情况下,你的代码不会受到这些变换的影响,除非你的模型含有多个网格(飞船模型没有)。只是为了防止给定的模型有多个部分,你需要添加
前两行代码。
接着,代码遍历模型中的每个ModelMesh以及每个与ModelMesh有对应该关系的BasicEffect对象,应用默认的光照,设置被绘制的对象的Projection、View和World属性。摄像机使用投影和视图矩阵,世界矩阵设置对象的World属性。世界
矩阵乘以这里的变换(the transform),来确保ModelMesh放置在模型内合适的位置。
添加模型管理器:
public class ModelManager : DrawableGameComponent { //存放一系列BasicModel List<BasicModel> models = new List<BasicModel>(); public ModelManager(Game game) : base(game) { // TODO: Construct any child components here } public override void Initialize() { base.Initialize(); } protected override void LoadContent() { models.Add(new SpinningEnemy(Game.Content.Load<Model>(@"models\spaceship"))); base.LoadContent(); } public override void Update(GameTime gameTime) { foreach (BasicModel model in models) { model.Update(); } base.Update(gameTime); } public override void Draw(GameTime gameTime) { foreach (BasicModel bm in models) { bm.Draw(((Game1)Game).camera); } base.Draw(gameTime); } }
这里主要有两个重要部分:
一个是在LoadContent中加载刚才添加的模型,一个是在Update中遍历更新模型.
让模型转起来:
现在的模型似乎没有什么效果,和之前的二维图片没什么区别,接下来实现模型的转动.
添加一个新的类SpinningEnemy:
class SpinningEnemy : BasicModel { Matrix rotation = Matrix.Identity; public SpinningEnemy(Model m) : base(m) { } public override void Update() { rotation *= Matrix.CreateRotationY(MathHelper.Pi / 180); } public override Matrix GetWorld() { return world * rotation; } }
该类实现了BasicModel,这样就免去了重写Draw方法,该类的有了一个rotation旋转的变量;同时在Update中你将看到rotation变量在每一帧中以围绕Y轴多旋转1度的速度更新
(请记住角度是用弧度来表示的,π就是180°,所以π/ 180等于1度);类中还重写GetWorld方法,返回世界坐标和旋转的角度的乘积,也就是旋转后的位置.
最后在ModelManager中的LoadContent中修改创建BasicModel的代码为如下:
models.Add(new SpinningEnemy(Game.Content.Load<Model>(@"models\spaceship")));
这一部分主要讲解了在XNA中加载一个3D模型,同时实现了模型的旋转效果,在下一章节中将会讲解"3D中摄像机"的功能.