65.9K
CodeProject 正在变化。 阅读更多。
Home

XNA:初学者入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (34投票s)

2007年7月17日

CPOL

9分钟阅读

viewsIcon

219162

downloadIcon

1950

XNA、Blender 和 3D 建模入门

Screenshot - img7.png

引言

这是一篇介绍 XNA 开发的文章,讨论了使用 XNA Game Studio 及其在 Blender 中创建的简单模型所需的工具和最少代码量。

工具

要使用 XNA Game Studio 编写应用程序(例如游戏),您必须下载并安装各种工具。由于 Microsoft 经常更改其网站,因此这些链接不保证有效。如果您无法访问下载页面,请使用 Google 搜索新的链接,并使用适当的关键词。

本文使用以下软件编写:

  • Visual Studio C# 2005 Express Edition SP1
  • XNA Game Studio Express 1.0 Refresh
  • XNA Framework 1.0 Refresh
  • Blender 2.44
  • Python 2.51

Visual Studio Express

截至本文撰写之时,XNA 开发只能使用 Visual Studio Express 完成。

  • 在此处下载 Visual Studio C# 2005 Express Edition
  • 在此处下载 VS Expression Edition 的 SP1(向下滚动一点)

XNA

XNA 开发需要 Game Studio Express (GSE) 和 XNA Framework

  • 在此处下载 XNA Game Studio Express
  • 在此处下载 XNA Framework

建模工具

如果您要用 XNA 做任何“严肃”的事情,您很快就会发现您需要一个建模工具来创建您的 3D 模型。我推荐 Blender(免费)或 AutoDesk Maya(2000 美元)。猜猜我将在本文和其他文章中提到哪个工具?

  • 在此处下载 Blender
  • 在此处体验 Maya 个人学习版,垂涎 Maya

Maya PLE 不能用于为 XNA 创建模型(据我所知),因为它不能导出 XNA 所需的格式。

插件

您可能希望利用 Blender 中的插件和脚本,它们使用 Python。

Blender 会自动检测 Python 安装,因此您无需进行其他操作。

教程

关于 XNA 和 Blender 的教程非常丰富,分别在 Microsoft 和 Blender 网站以及许多第三方网站上。只需 Google 搜索,您就会找到许多资源。我首先通过 Microsoft 教程来显示 3D 模型。请注意我在此处描述的宽高比错误。它不是致命的,但如果您将 Microsoft 教程作为处理 Blender 中创建的模型的基础,它将导致对象拉伸。

创建 Windows XNA 项目

启动 Visual Studio C# 2005 Express Edition。

创建 Windows 游戏项目

通过选择 Windows 游戏项目图标创建 Windows 游戏项目

输入项目名称并单击“确定”。

创建模型文件夹

模型(宇宙飞船、人物、对象等)通常位于 Content\Models 文件夹中。右键单击项目并添加 Content 文件夹和 Models 子文件夹,这样您的项目树看起来像这样:

如果您构建并运行此项目,您将获得一个带有蓝色字段的窗口。

在 Blender 中创建模型

启动 Blender。Blender 初始模型是一个立方体,尽管您从顶部看立方体,它看起来像一个正方形。

要查看它实际上是一个立方体,请单击数字键盘上的 0(打开数字锁定),您将看到:

导出模型

XNA 无法加载 Blender 文件,因此您必须将 Blender 模型导出为 FBX 格式。您可以在此处阅读有关 FBX 的一些信息。它基本上是 AutoDesk 创建的一种开放标准、平台无关的 3D 文件格式。您也可以导出为 DirectX (.x) 格式,但这会弹出一个复杂的选项对话框,而 FBX 导出则不会。

Blender UI 对 Windows 用户来说可能有点奇怪,所以这里是操作步骤:

  • 在顶部的菜单栏上,单击“文件”(不要尝试使用典型的 Alt-F 组合键!)
  • 将鼠标向下移动到“导出”
  • 将鼠标移到“Autodesk FBX”
  • 将出现一个对话框,其中包含两个文本框,第一个显示路径,第二个用于输入文件名。单击第二个编辑框并键入“cube”(不带引号)。
  • 单击“导出 FBX”按钮或按两次 Enter 键。

将模型导入 XNA

现在模型已准备好导入 XNA

  • 返回 XNA Game Studio,右键单击“模型”文件夹并选择“添加/现有项...”。
  • 在文件类型中,选择“内容管线文件”
  • 导航到 Blender 文件夹并选择 cube.fbx 文件。

添加渲染模型的代码

渲染模型包括:

  • 定义模型的位置
  • 定义摄像机的位置
  • 定义摄像机方向
  • 将模型加载到内容管线
  • 绘制模型

所有这些工作都将在 game1.cs 文件和 Game1 类中完成。

定义模型的位置

为简单起见,模型放置在世界坐标 (0, 0, 0) 处。为模型位置创建字段:

protected Vector3 modelPosition = Vector3.Zero;

定义摄像机的位置

为摄像机位置创建一个字段:

protected Vector3 cameraPosition = new Vector3(0.0f, 0.0f, 10.0f);

我是如何得到这些值的?向量由模型的 x、y 和 z 轴位置组成。X 是左右,Y 是上下,Z 是进出。如果您查看上面立方体的屏幕截图,您会注意到一个网格。每条网格线代表一个单位,因此立方体宽 2 个单位,长 2 个单位,高 2 个单位。摄像机位于距离世界中心 (0, 0, 0) 十个单位的位置,这创建了一个合理大小的立方体渲染。

将模型加载到内容管线

为您的模型创建一个字段:

protected Model myModel;

LoadGraphicsContent 方法中,加载您的立方体模型:

protected override void LoadGraphicsContent(bool loadAllContent)
{
  if (loadAllContent)
  {
    myModel = content.Load<Model>("Content\\Models\\cube");
  }
}

请注意,您不需要指定文件扩展名。XNA Game Studio 将加载相应的文件,因为模型实际上被编译为与您添加到 Content\Models 文件夹中的 FBX 文件不同的文件扩展名。

与此同时,我们还将获取视口(我们的窗口)的宽高比,因为这对于渲染模型使其看起来像立方体是必要的。添加字段:

protected float aspectRatio;

并且,在上述方法中,计算宽高比:

protected override void LoadGraphicsContent(bool loadAllContent)
{
  if (loadAllContent)
  {
    myModel = content.Load<Model>("Content\\Models\\cube1");
  }

  aspectRatio = ((float)graphics.GraphicsDevice.Viewport.Width) / 
     ((float)graphics.GraphicsDevice.Viewport.Height);
}

绘制模型

修改 Draw 方法,使其遍历模型网格,适当地平移和旋转它们等。

protected override void Draw(GameTime gameTime)
{
  graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

  Matrix[] transforms = new Matrix[myModel.Bones.Count];
  myModel.CopyAbsoluteBoneTransformsTo(transforms);

  // Draw the model. A model can have multiple meshes, so loop.
  foreach (ModelMesh mesh in myModel.Meshes)
  {
    // This is where the mesh orientation is set, as well as our camera and
    // projection.
    foreach (BasicEffect effect in mesh.Effects)
    {
      effect.EnableDefaultLighting();
      effect.World = transforms[mesh.ParentBone.Index];
      effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, 
         Vector3.Up);
      effect.Projection = Matrix.CreatePerspectiveFieldOfView(
         MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
    }

    // Draw the mesh, using the effects set above.
    mesh.Draw();
  }

  base.Draw(gameTime);
}

当您运行程序时,您将在蓝色字段上看到一个渲染的立方体。

以下是对上述方法中发生的事情的简要描述。这些子主题中的每一个都值得我提供更多的描述,但我认为这超出了这篇初学者文章的范围。

骨骼

上面的代码是游戏中所有模型的基本渲染循环。XNA 使用骨骼动画技术,您可以在此处阅读

CopyAbsoluteBoneTransformsTo 将骨骼层次结构扁平化并将骨骼放入数组中,以便在此赋值中可以快速获取父骨骼矩阵:

"cs">effect.World = transforms[mesh.ParentBone.Index];

或者,您可以将其写为:

"cs">effect.World = mesh.ParentBone.Transform;

然而,由于子项中的每个网格都需要访问其父骨骼变换(以便网格在父骨骼的变换基础上构建),因此索引最终比每次都要求父骨骼重新计算其变换要快。

您认为此模型有多少根骨骼?如果您回答 1,因为您认为只有一个模型,那么您部分正确。然而,始终存在一个定义骨骼树最顶层父级的根:

模型网格

一个模型由网格组成(在 Blender 文档中阅读有关网格的信息)。您认为立方体有多少个网格?预期的答案是 6——立方体的每个面一个网格。然而,立方体模型只有一个网格!令人困惑的是实际上名为 mesh 的局部变量,它应该被称为 modelMesh,因为只有一个网格代表整个模型。因此,必须清楚 XNA ModelMesh 实例和 Blender 网格之间的区别。

立方体的 XNA 网格

由 1 个 ModelMeshPart 对象组成,该对象有 8 个顶点,这是立方体中的顶点数(以上模型有 51 个顶点)。

作为第二个示例,像这样的对象:

(其中四边形——正方形——网格已转换为一个面的三角形,并且其中一个三角形已拉伸)在 XNA 模型中仍仅表示为一个模型网格

效果

这行代码:

foreach (BasicEffect effect in mesh.Effects)

是表达方式的简写(再次注意我们正在处理模型网格部分):

foreach (ModelMeshPart part in mesh.MeshParts)
{
  BasicEffect effect = (BasicEffect)part.Effect;
  ...

但是,如果多个 ModelMeshPart 共享相同的效果,前者经过优化以避免冗余设置工作(参考)。

效果属性

每个模型网格都基于以下内容进行渲染:

  • 它的方向
  • 相机的方向
  • 相机的视野

如这三个语句所表达的:

effect.World = transforms[mesh.ParentBone.Index];
effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
     MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
  • World 属性为该模型网格(从模型的世界的角度)建立世界矩阵,这通常是父骨骼变换和仅应用于该骨骼的任何局部变换的组合。
  • View 属性建立相机的方向——它的位置、它正在看的地方和它的方向。
  • Projection 属性建立相机如何将其视图投射到屏幕上——视野、视口的宽高比以及相机可以看到的边界(距离):近平面和远平面。

每个模型网格都需要设置 World 属性以反映父骨骼中的任何变化和任何局部变化。当相机位置和方向改变时,需要设置 View Projection 属性。在这个简单的例子中,我们可以预先计算这些值:

effect.World = transforms[mesh.ParentBone.Index];
effect.View = lookAt;
effect.Projection = projection;

其中 lookAtprojection 是在 LoadGraphicsContent 方法中初始化的 Matrix 对象。

lookAt = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
    aspectRatio, 1.0f, 10000.0f);

动画模型

上面立方体的截图看起来非常无聊,根本不像一个立方体。让物体自行旋转是最简单的动画形式之一。以下代码将使立方体围绕 X 和 Y 轴旋转,使其具有良好的旋转效果。首先,添加到:

protected float modelRotation = 0.0f;

Game1 类。此字段用于保存旋转角度。

Update 方法中,添加一行代码以根据更新之间经过的时间递增模型旋转:

protected override void Update(GameTime gameTime)
{
  // Allows the game to exit
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    this.Exit();

  modelRotation += (float)gameTime.ElapsedGameTime.TotalMilliseconds * 
     MathHelper.ToRadians(0.1f);
  base.Update(gameTime);
}

Draw 方法中,修改 World 属性赋值以旋转模型网格:

protected override void Draw(GameTime gameTime)
{
  graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

  Matrix[] transforms = new Matrix[myModel.Bones.Count];
  myModel.CopyAbsoluteBoneTransformsTo(transforms);

  // Draw the model. A model can have multiple meshes, so loop.
  foreach (ModelMesh mesh in myModel.Meshes)
  {
    // This is where the mesh orientation is set, as well as our camera and 
    // projection.
    foreach (BasicEffect effect in mesh.Effects)
    {
      effect.EnableDefaultLighting();
      effect.World = transforms[mesh.ParentBone.Index] * 
          Matrix.CreateRotationX(modelRotation) * 
          Matrix.CreateRotationY(modelRotation);
      effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, 
        Vector3.Up);
      effect.Projection = Matrix.CreatePerspectiveFieldOfView(
        MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
    }

    // Draw the mesh, using the effects set above.
    mesh.Draw();
  }

  base.Draw(gameTime);
}

这创建了一个漂亮的旋转立方体:

结论

本文总结了我为基本了解 XNA 以及如何使用 Blender 等工具开始开发 XNA 可以渲染的模型所做的研究和实验。我一直在这里记录我的尝试和磨难,随着我在正在开发的概念上取得进展,我将继续这样做。我主要对将 XNA 用作渲染空间而不是游戏开发感兴趣,但我也可能只是为了好玩而偏离主题,并编写一个我多年前在 Windows 3.1 中编写的简单游戏。

© . All rights reserved.