从零3D基础入门XNA

【题外话】

上一篇小说介绍了3D开采基础与XNA开辟顺序的全体结构,以及接纳Model类的Draw方法将模型绘制到荧屏上。本文接着上一篇小说继续,介绍XNA中模型的结构、BasicEffect的运用以及用户输入和分界面彰显的点子等,本文尽量把遇到的定义都分析清楚,但又避开复杂的数学方面包车型客车文化,希望对从未接触过3D开拓的同班有所帮衬。

 

【种类索引】

  1. 从零3D基础入门XN明锐.0(一)——3D开荒基础
  2. 从零3D基础入门XN威朗.0(二)——模型和BasicEffect

 

【文章索引】

  1. Model模型的构造
  2. BasicEffect效果的装置
  3. XNA的用户输入
  4. XNA分界面包车型客车显示格局

 

【一、Model模型的结构】

上壹篇文章使用Model自带的Draw方法落成了平素将载入的Model绘制到钦点的岗位上去,不过有时绘制出来的成效并不吻合大家的预期,比方下图(下图的模型是通过Maya创立的3个屋子):

图片 1

通过ILSpy查看Microsoft.Xna.Framework.Graphics.Model,能够阅览其Draw方法的代码如下:

图片 2图片 3

 1 public void Draw(Matrix world, Matrix view, Matrix projection)
 2 {
 3     int count = this.meshes.Count;
 4     int count2 = this.bones.Count;
 5     Matrix[] array = Model.sharedDrawBoneMatrices;
 6     if (array == null || array.Length < count2)
 7     {
 8         array = new Matrix[count2];
 9         Model.sharedDrawBoneMatrices = array;
10     }
11     this.CopyAbsoluteBoneTransformsTo(array);
12     for (int i = 0; i < count; i++)
13     {
14         ModelMesh modelMesh = this.meshes[i];
15         int index = modelMesh.ParentBone.Index;
16         int count3 = modelMesh.Effects.Count;
17         for (int j = 0; j < count3; j++)
18         {
19             Effect effect = modelMesh.Effects[j];
20             if (effect == null)
21             {
22                 throw new InvalidOperationException(FrameworkResources.ModelHasNoEffect);
23             }
24             IEffectMatrices effectMatrices = effect as IEffectMatrices;
25             if (effectMatrices == null)
26             {
27                 throw new InvalidOperationException(FrameworkResources.ModelHasNoIEffectMatrices);
28             }
29             effectMatrices.World = array[index] * world;
30             effectMatrices.View = view;
31             effectMatrices.Projection = projection;
32         }
33         modelMesh.Draw();
34     }
35 }

View Code

里头可见,Draw方法通过遍历模型的Mesh,然后再遍历每一种Mesh的Effect,并对各类Effect进行安装,最终动用Mesh的Draw方法将其绘制到显示器上。

为了领悟Model的渲染,我们首先供给精通Model的结构。实际上,在一个Model对象中,包蕴Bone会集(model.Bones)、Mesh集合(model.Meshes)以及根Bone(model.Root)四个个性,其布局和涉及如下

图片 4

能够看看对于每种ModelMesh,包罗1组ModelMeshPart与贰个ParentBone。在那之中,

  • ModelMesh代表单个能够单独运动的物理对象。举例,2个car的Model能够涵盖贰个车体(body)的ModelMesh、两个车轱辘(wheel)的ModelMesh与1对门(door)的ModelMesh。
  • ModelMeshPart表示单个同等质感的预制构件,其代表2个独立的绘图调用(draw
    call)。比方,上述车身可以分包着色的外表、使用意况映射(environment
    mapping)效果的挡风玻璃以及利用法线贴图(normalmap
    texture)效果的座椅等等。
  • ModelBone表示了对应的ModelMesh怎么着调换,其包蕴多个Transform的改动矩阵。ModelBone是以树形存款和储蓄的,每一个ModelBone都有二个父节点以及若干个子节点。上述的各样ModelMesh都有1个ParentBone,ModelMesh能够依赖ModelBone的转移来明确最终显示的职位等。举例,上述车门的ModelBone与车轮的ModelBone是车身的子节点等等。

之所以遍历二个Model中有所的ModelMesh,然后遍历当中具备的ModelMeshPart,并且依照ModelMesh的ParentBone来将每七个ModelMeshPart绘制到钦赐的职位上就足以绘制出1体化的Model。

唯独对此各个ModelMeshPart,其实际渲染的法力都留存Effect的属性中,对于暗中认可来讲,Effect均为BasicEffect。其它,对于ModelBone,其改造矩阵都以相对其自身的Parent来的,可是Model类也提供了一个办法,即CopyAbsoluteBoneTransformsTo(),就可以将各类Bone相对于RootBone的转变矩阵复制到二个矩阵数组中,然后将其选取到Effect中就可以。那种格局与上述提到的Model.Draw类似,但是自个儿写的话就足以自定义各类ModelMeshPart渲染的效率,当然也能够安装各种ModelMeshPart的渲染地点。

那么接下去就遵照那个思路去贯彻,同时在设置每2个Effect时,使用Effect提供的行使默许光照的方法EnableDefaultLighting(),启用后效果如下:

图片 5

那样的作用就直达了我们的意料,按上述的不2秘诀落成的代码如下:

图片 6图片 7

 1 Matrix world = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up);
 2 
 3 Matrix[] transforms = new Matrix[model.Bones.Count];
 4 this.model.CopyAbsoluteBoneTransformsTo(transforms);
 5 
 6 foreach (ModelMesh mesh in model.Meshes)
 7 {
 8     Int32 boneIndex = mesh.ParentBone.Index;
 9 
10     foreach (ModelMeshPart part in mesh.MeshParts)
11     {
12         BasicEffect effect = part.Effect as BasicEffect;
13         
14         effect.EnableDefaultLighting();
15         effect.World = transforms[boneIndex] * world;
16         effect.View = cameraView;
17         effect.Projection = cameraProjection;
18     }
19 
20     mesh.Draw();
21 }

View Code

可是那与刚刚看来的Model.Draw的代码并区别。实际上,XNA为了简化操作,已经将ModelMeshPart的各样Effect放到了ModelMesh的Effects集合中,只需求遍历那几个会集就可以,而无需再遍历ModelMeshPart,再赢得Effect了。所以上述代码能够简化为如下的代码:

 1 Matrix world = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up);
 2 
 3 Matrix[] transforms = new Matrix[model.Bones.Count];
 4 this.model.CopyAbsoluteBoneTransformsTo(transforms);
 5 
 6 foreach (ModelMesh mesh in model.Meshes)
 7 {
 8     Int32 boneIndex = mesh.ParentBone.Index;
 9     
10     foreach (BasicEffect effect in mesh.Effects)
11     {
12         effect.EnableDefaultLighting();
13         effect.World = transforms[boneIndex] * world;
14         effect.View = cameraView;
15         effect.Projection = cameraProjection;
16     }
17 
18     mesh.Draw();
19 }

 

【二、BasicEffect效果的安装】

首先用ILSpy查看下BasicEffect的EnableDefaultLighting()的代码:

public void EnableDefaultLighting()
{
    this.LightingEnabled = true;
    this.AmbientLightColor = EffectHelpers.EnableDefaultLighting(this.light0, this.light1, this.light2);
}

其间this.light0-二为BasicEffect的DirectionalLight0-二,即BasicEffect能够时候的四个光源。而EffectHelpers的EnableDefaultLighting是这么写的:

图片 8图片 9

 1 internal static Vector3 EnableDefaultLighting(DirectionalLight light0, DirectionalLight light1, DirectionalLight light2)
 2 {
 3     light0.Direction = new Vector3(-0.5265408f, -0.5735765f, -0.6275069f);
 4     light0.DiffuseColor = new Vector3(1f, 0.9607844f, 0.8078432f);
 5     light0.SpecularColor = new Vector3(1f, 0.9607844f, 0.8078432f);
 6     light0.Enabled = true;
 7     light1.Direction = new Vector3(0.7198464f, 0.3420201f, 0.6040227f);
 8     light1.DiffuseColor = new Vector3(0.9647059f, 0.7607844f, 0.4078432f);
 9     light1.SpecularColor = Vector3.Zero;
10     light1.Enabled = true;
11     light2.Direction = new Vector3(0.4545195f, -0.7660444f, 0.4545195f);
12     light2.DiffuseColor = new Vector3(0.3231373f, 0.3607844f, 0.3937255f);
13     light2.SpecularColor = new Vector3(0.3231373f, 0.3607844f, 0.3937255f);
14     light2.Enabled = true;
15     return new Vector3(0.05333332f, 0.09882354f, 0.1819608f);
16 }

View Code

能够见到在启用暗中认可光照里实际是给条件光AmbientLightColor以及叁束定向光(包括光线的自由化、漫反射颜色及镜面反射颜色)设置了先行定义好的颜料,并启用了那几个光源,那3束定向光的颜色(Light1的漫反射光的水彩如下,但其镜面反射光的颜料为茶绿)和趋势大致如下。

图片 10

下图第七个为启用了默许光照后的模型(上1篇文章中的dude),第叁、三、多个为只启用暗中认可光照的遭逢光及0、1、二三束定向光后的模子,第陆个为未有启用私下认可光照的模型(就像是上壹篇发生的功用等同):

图片 11

理所当然,在繁多动静下(举个例子室外的阳光等),大家仅供给三个光源,届时大家只要禁止使用(DirectionalLight*.Enabled
= false)别的七个定向光就可以,当然大家兴许还必要修改光源的颜料等等。

除此而外利用EnableDefaultLighting,BasicEffect还提供了比较丰裕的参数能够设置。首先来看下上述例子中Effect暗许的质量:

图片 12

中间与光线有关的:

  • LightingEnabled:是或不是开启光照(默感觉false)。
  • PreferPerPixelLighting:是还是不是展开逐像素的光照(默感觉false,为逐顶点光照),逐像素光照相对于逐点光照效果越来越好,但速度也更加慢,同时还亟需显卡帮忙Pixel
    Shader Model 2.0,假诺显卡不支持的话会自动使用逐顶点光照代替。
  • AmbientLightColor:碰到光颜色(默感觉Vector叁.Zero)。为了在局地光照模型(模型间的普照互不影响)中增进真实感,引进了碰到光的概念。景况光不借助于任何光源,但其影响全数物体。
  • DiffuseColor:漫反射颜色(默感到Vector3.One)。光线照到物体后,物体进行漫反射,其颜色与光线的可行性有关。
  • SpecularColor:镜面反射颜色。光线照到物体后,物体举行全反射,其颜色不仅与光线的倾向有关,还与侦察(相机)的动向有关。
  • EmissiveColor:放射颜色(默以为Vector叁.Zero)。放射光是指物体发出的光明,但在一些光照模型中,实际上不会对任何物体产生潜移默化。
  • DirectionalLight0、DirectionalLight一、DirectionalLight二:3束定向光(每束都席卷光线的自由化、漫反射颜色与镜面反射颜色)。

中间须求小心的是,在XNA中,颜色的积累并不是利用的Color(A汉兰达GB或ABGRubicon),而是使用的Vector三(或Vector四)。对于Vector三,其x、y、z多个轻重存款和储蓄的分别是GL450、G、B分别除以25五的浮点值(Vector四的w分量存款和储蓄的是Alpha通道除以25五的浮点值),所以Vector三.Zero即为浅湖蓝,而Vector三.One为深灰蓝。当然XNA也提供了多个Color类,并且Color也提供了提供了平昔转变为Vector3(或Vector四)的办法ToVector三()(或ToVector4())。

除了,BasicEffect还援助设置雾的效益:

  • FogEnabled:是或不是张开雾的法力(默感到false)。
  • FogColor:雾的颜色(默认为Vector三.Zero)。
  • FogStart:雾距离相机的开头(最近)值(默以为0.0F),这些距离之内的事物不受雾的影响。
  • FogEnd:雾距离相机的竣事(最远)值(默感觉1.0F),这一个距离之外的东西完全看不清。

也等于说,雾将会在离开相机(FogStart –
FogEnd)的地点发生,这一个距离要求依附物体所在的职分决定。设Distance为实体距离相机的相距,则Distance<FogStart<FogEnd时,物体不受雾的影响,与未有雾时同样;当FogStart<FogEnd<Distance时,物体完全看不清(即物体全部为雾的水彩);当FogStart<Distance<FogEnd时,物体受雾的影响,物体离FogEnd越近则越看不清。

举例当人的模子在(0, 0, 0),相机在(120, 120,
120)处,雾的颜料为格雷。下图第三个为未有加雾的成效,第二个为FogStart –
FogEnd为200 – 300,第多少个为1 – 300,第多个为壹 – 十0。

图片 13

 

【叁、XNA的用户输入】

在暗中认可生成XNA程序中的Update方法里,有三个收获GamePad的情状,当用户一的GamePad按下了“Back”键后将会脱离程序。微软对用户输入的支撑都在Microsoft.Xna.Framework.Input中,除了GamePad之外,微软还援救获取Keyboard、Mouse那三种的境况。别的在Microsoft.Xna.Framework.Input.Touch中,还有TouchPanel能够拿到触摸的状态。与GamePad同样,其余的这么些情况也都以因此微软提需求类中的GetState()方法举行获取。

譬如要拿走键盘和鼠标的图景,我们得以经过如下方式:

KeyboardState kbState = Keyboard.GetState();
MouseState mouseState = Mouse.GetState();

对于推断键盘的按钮,能够经过如下的措施获得是不是按下了点名按钮:

Boolean pressed = kbState.IsKeyDown(Keys.Enter);

而对于鼠标的开关,则要求看清开关的ButtonState才得以,举例决断鼠标左键是不是按下:

Boolean pressed = (mouseState.LeftButton == ButtonState.Pressed);

除外,即便要咬定鼠标是不是在先后区域内,能够透过如下的秘技推断

if (this.GraphicsDevice.Viewport.Bounds.Contains(mouseState.X, mouseState.Y))
{
    //TODO
}

就算如此在大部景况下,要是让用户操作鼠标的话会在程序内显示二个自定义的指针。但有时写个小程序,为了轻易希望直接利用系统的指针,大家得以在先后的自由地点(构造方法、Initialize乃至Update也可)写如下的代码,就足以显得鼠标指针了,反之则能够隐藏:

this.IsMouseVisible = true;

 

【肆、XNA分界面包车型客车突显情势】

私下认可情状下,运营XNA的先后会活动以800*480的分辨率展现,若要修改呈现的分辨率,其实万分简单,仅供给在Game的构造方法中增多如下代码即可:

graphics.PreferredBackBufferWidth = 1024;
graphics.PreferredBackBufferHeight = 768;

那样XNA的次第就能够根据我们设定的分辨率呈现了。除此而外,假使大家期望XNA的顺序能全屏突显,大家还足以增进如下的代码:

graphics.IsFullScreen = true;

当然大家还足以让用户来切换全屏与窗口化,可是那行代码写在Update()中是不起成效的,可是XNA提供其余三个格局,正是graphics.ToggleFullScreen()。举例我们须要按F键实行全屏与窗口化的切换,能够编写制定如下的代码:

KeyboardState kbState = Keyboard.GetState();
if (kbState.IsKeyDown(Keys.F))
{
    graphics.ToggleFullScreen();
}

 

【相关链接】

  1. Model
    Class:http://msdn.microsoft.com/en-us/library/Microsoft.Xna.Framework.Graphics.Model.aspx
  2. Models, meshes, parts, and
    bones:http://blogs.msdn.com/b/shawnhar/archive/2006/11/20/models-meshes-parts-and-bones.aspx
  3. What Is a Model
    Bone?:http://msdn.microsoft.com/en-us/library/dd904249.aspx
  4. BasicEffect
    Lighting:http://rbwhitaker.wikidot.com/basic-effect-lighting
  5. BasicEffect Fog:http://rbwhitaker.wikidot.com/basic-effect-fog
  6. 一齐学WP7 XNA游戏开拓(7.
    三d基本光源):http://www.cnblogs.com/randylee/archive/2011/03/09/1978312.html
  7. 【D3D1一娱乐编制程序】学习笔记拾二:光照模型:http://blog.csdn.net/bonchoix/article/details/8430561