接着“XNA学习笔记2”中的内容管道说。图片文件、字体文件、.x/.fbx和.fx文件经过内容管道的导入编译等工作,最后通过ContentManager对象的Load方法得到texture、spritefont、model和effect对象。
纹理(或shader)、模型和渲染的关系可以用一个形象的比喻。现在是一个立方体,立方体是模型;渲染就是对立方体的每个部分(这里就想做是每个面,稍后会介绍他的定义)如何添加纹理、颜色、阴影等效果;而纹理就是每个渲染用到的纹理。
第二章讲到了简单2D的纹理是通过SpriteBatch类来处理,这个类仅处理2D的简单纹理,并且没有涉及到模型的概念。
现在讨论一下3D的模型的一些知识。
每个成员的几何数据以ModelMesh对象的形式存储,而ModelMesh对象是在Model的 ModelMeshCollection中,在这个对象你可以找到它的Meshes属性。成员的位置数据以Bone对象的形式存储,而Bone对象在Model的ModelBoneCollection中,在这个对象中你可以找到模型的Bones集合。每个ModelMesh对象包含指向Bone对象的引用,而一个Bone对象包含指向父Bone的引用,它必须连接到这个父Bone。通过这种方式,你可以将所有Bone对象连接到一起。
[b]ModelMeshes和Bones[/b]。一个ModelMesh包含模型的一个成员的几何信息,这个成员无法再分成更小的成员。例如,一台笔记本电脑不是一个好的ModelMesh,因为你想翻起/关闭液晶屏,打开/关闭DVD托盘。好的办法是对电脑底座使用一个ModelMesh,液晶屏使用第二个ModelMeshcreen,而DVD托盘使用第三个ModelMesh。因为所有的ModelMesh都需要他连接到一个Bone上,下一步你的三个ModelMeshes还需要三个Bone。你需要将连接到电脑底座的ModelMesh的Bone作为root Bone,因为电脑底座可以看成电脑的初始位置。连接到液晶屏ModelMesh的表示与底座连接的位置。同理连接DVD托盘的ModelMesh的Bone也要指向root Bone,表示托盘相对于底座的位置。
[b]ModelMeshes和ModelMeshParts[/b]。你已经为液晶屏定义了一个ModelMesh和对应的Bone,这样很完美,因为液晶屏不包含可动部分。但是,你可能还想使用一个固定纹理的effect绘制液晶屏的塑料外壳,使用另一个effect绘制LCD,例如,这个效果会从另一张纹理采样颜色或你还想添加一点反光效果。这就需要用到ModelMeshParts。每个ModelMesh可以包含多个ModelMeshParts,每个ModelMeshParts可以使用不同的纹理,材质或effect进行绘制。这意味着每个ModelMeshPart 都包含各自的几何数据和对应几何数据的effect。
[color=red][b]实现model整体或局部的移动、旋转和缩放的功能[/b][/color]
将这部分之前先介绍两个函数:CopyAbsoluteBoneTransformsTo:将模型中的每个bone相对于root的变换信息存储在数组参数中;CopyBoneTransformsTo:将模型中的每个bone相对于其parent bone的变换信息存储在数组参数中。由于在对某个mesh或者part进行变换的时候,同时会对mesh对应的bone的child进行相同的变换。思考:以坦克的火炮为例,火炮的Bone矩阵包含一个诸如(0,0,-2)的偏移量:相对于它的父:炮塔向前移动2个单位。如果你只是简单地将火炮的世界矩阵设置为火炮ModelMesh的矩阵,这会导致火炮ModelMesh被绘制到相对于3D空间的初始位置(0,0,0)偏离(0,0,-2)的地方。但这不是你想要的结果!你实际是想将火炮放置到相对于它的parent:炮塔偏离(0,0,-2)的位置。这个时候会发现CopyAbsoluteBoneTransformsTo方法的重要作用。
你可以简单地使用XNA框架提供的基本方法创建一个变换矩阵:
[b]矩阵乘法的顺序[/b]。在矩阵数学中,将矩阵M1乘以矩阵M2的结果通常与将M2乘以M1的结果是不同的。有两个规则(或者说是技巧)你必须记得:在矩阵乘法中,M1*M2就是“M1在M2之后”的意思;使用I.S.R.O.T.作为矩阵组合的顺序通常就是你想要的结果,此处I.S.R.O.T. 分别代表Identity,Scale,Revolve,Orbit,Translate。
[color=red][b]碰撞检测[/b][/color]
你想检测两个模型是否发生碰撞。如果在场景中有许多模型,你将无法进行一个精确的逐三角形的碰撞检测。你想使用一个快速检测方法并在以后进行一个更好的碰撞检测。当进行碰撞检测时,你会发现常常需要在速度和精确度之间进行衡量。在大多数情况中,你会进行组合检测。在第一轮检测中,使用快速检测遍历所有对象,之后对快速检测中可能发生碰撞的物体进行精确检测。有两个方法处理两个模型间的碰撞检测。快速检测方法是找到模型的全局包围球,并通过调用一个包围球的Intersect 方法检测是否相交。你可以改进这个方法以增加精确度。由几个ModelMeshes组成的模型存储了模型不同部分的几何信息。每个ModelMeshes都能生成各自的包围球,所以你可以检测第一个模型和第二个模型的每个包围球。显然,这样做提高了精度但计算量也随之加大。
[color=blue][b]快速检测。[/b][/color]这个方法使用整个模型的包围球。如果这两个模型的包围球相交,则它们可能发生了碰撞。在某些情况下,这个方法会导致糟糕的结果,例如在滑板游戏中,当你想检测两个玩家的滑板的碰撞时,滑板的包围球相对于滑板本身大得多,滑板的体积不到包围球体积的百分之一,这个方法将会对全局包围球中的所有物体(可能包含其他滑板!)进行检测。但是,这个方法在进行第一次检测时用得还是很多的,因为它的速度足够快。[align=center][img]http://dl.iteye.com/upload/attachment/357774/0ae4d66f-3262-39ca-b99f-e3b8284c8138.png[/img][/align]
你可以访问模型中的每个ModelMesh的包围球。使用CreateMerged方法,你可以将这个包围球组合起来,获取包围整个模型的包围球。但是,因为每个ModelMesh的包围球是相对于Bone矩阵定义的,所以你需要进行转换。你将创建一个方法将一个素材加载到一个模型变量中,初始化它的Bone矩阵数组,并将全局包围球保存到模型的Tag属性中。
你首先将collision变量设置为false,除非检测到碰撞那么这个变量将保持为false。因为你需要将每个小包围球移动到正确的位置,所以需要绝对Bone矩阵。要对第一个模型的每个ModelMesh进行这个检测,你需要将包围球移动到绝对位置。要实现这一步,你需要考虑模型包围球的位置和模型在3D世界中的位置。然后你遍历第二个模型的所有部分并将这些包围球变换到绝对位置。对于第一个模型的每个包围球,你检查是否与第二个模型的任意一个包围球是否发生碰撞,如果是,则将collision变量设置为true。
[color=blue][b]对小而快的对象使用Ray-Traced进行碰撞检测[/b][/color]。大部分的碰撞检测方法只在两个物体发生物理碰撞时才检测。但是,如果有一个小物体快速地穿过另一个物体,你的程序的更新速度就有可能跟不上而无法检测到碰撞。举一个具体的例子,比如一枚子弹打穿一个瓶子。子弹以5000km/h 的速度射向一个瓶子,而瓶子的宽度只有15cm。XNA程序每秒更新60次,所以每次更新子弹会前进23米的距离。这样的话,在调用Update方法时几乎没有可能检测到子弹和瓶子的碰撞,即使上一帧子弹的确穿过了瓶子。你可以在子弹的上一个位置和当前位置之间创建一个Ray。然后通过调用Ray或包围球的Intersect方法检测Ray是否和包围球发生碰撞。如果发生碰撞,这个方法返回碰撞点与Ray的终点间的距离(你可以使用子弹的前一个位置)。
下一节,将讨论Effect的相关知识~~ :wink:
纹理(或shader)、模型和渲染的关系可以用一个形象的比喻。现在是一个立方体,立方体是模型;渲染就是对立方体的每个部分(这里就想做是每个面,稍后会介绍他的定义)如何添加纹理、颜色、阴影等效果;而纹理就是每个渲染用到的纹理。
第二章讲到了简单2D的纹理是通过SpriteBatch类来处理,这个类仅处理2D的简单纹理,并且没有涉及到模型的概念。
现在讨论一下3D的模型的一些知识。
myModel = Content.Load<Model>("tank");
在大多数情况下,你可以旋转和移动模型的一部分,例如,移动人的手臂。要能实现这个功能,模型应该要被分成几个成员。对每个成员,你需要知道两件事:[b]几何数据[/b]:你想知道顶点,顶点包含了组成模型成员的所有三角形顶点的信息,这些信息包括位置,颜色,法线等。[b]成员与它们的父成员如何连接[/b]:以人的手臂为例,你要指定手臂是连接在肩膀上的。
每个成员的几何数据以ModelMesh对象的形式存储,而ModelMesh对象是在Model的 ModelMeshCollection中,在这个对象你可以找到它的Meshes属性。成员的位置数据以Bone对象的形式存储,而Bone对象在Model的ModelBoneCollection中,在这个对象中你可以找到模型的Bones集合。每个ModelMesh对象包含指向Bone对象的引用,而一个Bone对象包含指向父Bone的引用,它必须连接到这个父Bone。通过这种方式,你可以将所有Bone对象连接到一起。
[b]ModelMeshes和Bones[/b]。一个ModelMesh包含模型的一个成员的几何信息,这个成员无法再分成更小的成员。例如,一台笔记本电脑不是一个好的ModelMesh,因为你想翻起/关闭液晶屏,打开/关闭DVD托盘。好的办法是对电脑底座使用一个ModelMesh,液晶屏使用第二个ModelMeshcreen,而DVD托盘使用第三个ModelMesh。因为所有的ModelMesh都需要他连接到一个Bone上,下一步你的三个ModelMeshes还需要三个Bone。你需要将连接到电脑底座的ModelMesh的Bone作为root Bone,因为电脑底座可以看成电脑的初始位置。连接到液晶屏ModelMesh的表示与底座连接的位置。同理连接DVD托盘的ModelMesh的Bone也要指向root Bone,表示托盘相对于底座的位置。
[b]ModelMeshes和ModelMeshParts[/b]。你已经为液晶屏定义了一个ModelMesh和对应的Bone,这样很完美,因为液晶屏不包含可动部分。但是,你可能还想使用一个固定纹理的effect绘制液晶屏的塑料外壳,使用另一个effect绘制LCD,例如,这个效果会从另一张纹理采样颜色或你还想添加一点反光效果。这就需要用到ModelMeshParts。每个ModelMesh可以包含多个ModelMeshParts,每个ModelMeshParts可以使用不同的纹理,材质或effect进行绘制。这意味着每个ModelMeshPart 都包含各自的几何数据和对应几何数据的effect。
[color=red][b]实现model整体或局部的移动、旋转和缩放的功能[/b][/color]
将这部分之前先介绍两个函数:CopyAbsoluteBoneTransformsTo:将模型中的每个bone相对于root的变换信息存储在数组参数中;CopyBoneTransformsTo:将模型中的每个bone相对于其parent bone的变换信息存储在数组参数中。由于在对某个mesh或者part进行变换的时候,同时会对mesh对应的bone的child进行相同的变换。思考:以坦克的火炮为例,火炮的Bone矩阵包含一个诸如(0,0,-2)的偏移量:相对于它的父:炮塔向前移动2个单位。如果你只是简单地将火炮的世界矩阵设置为火炮ModelMesh的矩阵,这会导致火炮ModelMesh被绘制到相对于3D空间的初始位置(0,0,0)偏离(0,0,-2)的地方。但这不是你想要的结果!你实际是想将火炮放置到相对于它的parent:炮塔偏离(0,0,-2)的位置。这个时候会发现CopyAbsoluteBoneTransformsTo方法的重要作用。
你可以简单地使用XNA框架提供的基本方法创建一个变换矩阵:
Matrix.CreateTranslation
Matrix.CreateScale
Matrix.CreateRotationX-Y-Z
第一个方法创建一个平移矩阵,你可以定义将模型沿着X、Y和Z方向移动的距离。第二个方法让你可以缩放模型,第三个方法返回绕X、Y和Z轴旋转的矩阵。
myModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = fpsCam.GetViewMatrix();
effect.Projection = fpsCam.GetProjectionMatrix();
}
mesh.Draw();
}
只需要对上面的方法进行组合(*)实现一些复杂的变换。
[b]矩阵乘法的顺序[/b]。在矩阵数学中,将矩阵M1乘以矩阵M2的结果通常与将M2乘以M1的结果是不同的。有两个规则(或者说是技巧)你必须记得:在矩阵乘法中,M1*M2就是“M1在M2之后”的意思;使用I.S.R.O.T.作为矩阵组合的顺序通常就是你想要的结果,此处I.S.R.O.T. 分别代表Identity,Scale,Revolve,Orbit,Translate。
[color=red][b]碰撞检测[/b][/color]
你想检测两个模型是否发生碰撞。如果在场景中有许多模型,你将无法进行一个精确的逐三角形的碰撞检测。你想使用一个快速检测方法并在以后进行一个更好的碰撞检测。当进行碰撞检测时,你会发现常常需要在速度和精确度之间进行衡量。在大多数情况中,你会进行组合检测。在第一轮检测中,使用快速检测遍历所有对象,之后对快速检测中可能发生碰撞的物体进行精确检测。有两个方法处理两个模型间的碰撞检测。快速检测方法是找到模型的全局包围球,并通过调用一个包围球的Intersect 方法检测是否相交。你可以改进这个方法以增加精确度。由几个ModelMeshes组成的模型存储了模型不同部分的几何信息。每个ModelMeshes都能生成各自的包围球,所以你可以检测第一个模型和第二个模型的每个包围球。显然,这样做提高了精度但计算量也随之加大。
[color=blue][b]快速检测。[/b][/color]这个方法使用整个模型的包围球。如果这两个模型的包围球相交,则它们可能发生了碰撞。在某些情况下,这个方法会导致糟糕的结果,例如在滑板游戏中,当你想检测两个玩家的滑板的碰撞时,滑板的包围球相对于滑板本身大得多,滑板的体积不到包围球体积的百分之一,这个方法将会对全局包围球中的所有物体(可能包含其他滑板!)进行检测。但是,这个方法在进行第一次检测时用得还是很多的,因为它的速度足够快。[align=center][img]http://dl.iteye.com/upload/attachment/357774/0ae4d66f-3262-39ca-b99f-e3b8284c8138.png[/img][/align]
你可以访问模型中的每个ModelMesh的包围球。使用CreateMerged方法,你可以将这个包围球组合起来,获取包围整个模型的包围球。但是,因为每个ModelMesh的包围球是相对于Bone矩阵定义的,所以你需要进行转换。你将创建一个方法将一个素材加载到一个模型变量中,初始化它的Bone矩阵数组,并将全局包围球保存到模型的Tag属性中。
private Model LoadModelWithBoundingSphere(ref Matrix[] modelTransforms,
string asset, ContentManager content)
{
Model newModel = content.Load<Model>(asset);
modelTransforms = new Matrix[newModel.Bones.Count];
newModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
BoundingSphere completeBoundingSphere = new BoundingSphere();
foreach (ModelMesh mesh in newModel.Meshes)
{
BoundingSphere origMeshSphere = mesh.BoundingSphere;
BoundingSphere transMeshSphere = XNAUtils.TransformBoundingSphere(
origMeshSphere, modelTransforms[mesh.ParentBone.Index]);
completeBoundingSphere = BoundingSphere.CreateMerged(
completeBoundingSphere, transMeshSphere);
}
newModel.Tag = completeBoundingSphere;
return newModel;
}
要检测两个模型间的碰撞,你需要通过世界矩阵变换每个包围球并检查变换过的包围球是否相交。
private bool CoarseCheck(Model model1, Matrix world1, Model model2, Matrix world2)
{
BoundingSphere origSphere1 = (BoundingSphere)model1.Tag;
BoundingSphere sphere1= XNAUtils.TransformBoundingSphere(origSphere1, world1);
BoundingSphere origSphere2= (BoundingSphere)model2.Tag;
BoundingSphere sphere2 = XNAUtils.TransformBoundingSphere(origSphere2,world2);
bool collision = sphere1.Intersects(sphere2);
return collision;
}
[color=blue][b]精确检测[/b][/color]。每个模型是由多个成员组成的,几何数据是存储在模型的Meshes集合中的。每个 ModelMesh都能生成自己的包围球。这些小的包围球的总体积比模型的全局包围球小得多,分别对每个小的包围球进行碰撞检测,通常会返回更好的结果。[align=center][img]http://dl.iteye.com/upload/attachment/357889/35c88312-519a-3958-a6e8-1a75d9a83da2.png[/img][/align]
你首先将collision变量设置为false,除非检测到碰撞那么这个变量将保持为false。因为你需要将每个小包围球移动到正确的位置,所以需要绝对Bone矩阵。要对第一个模型的每个ModelMesh进行这个检测,你需要将包围球移动到绝对位置。要实现这一步,你需要考虑模型包围球的位置和模型在3D世界中的位置。然后你遍历第二个模型的所有部分并将这些包围球变换到绝对位置。对于第一个模型的每个包围球,你检查是否与第二个模型的任意一个包围球是否发生碰撞,如果是,则将collision变量设置为true。
private bool FinerCheck(Model model1, Matrix world1, Model model2, Matrix world2)
{
//if (CoarseCheck(model1, world1, model2, world2) == false)
// return false;
Matrix[] model1Transforms = new Matrix[model1.Bones.Count];
model1.CopyAbsoluteBoneTransformsTo(model1Transforms);
BoundingSphere[] model1Spheres = new BoundingSphere[model1.Meshes.Count];
for (int i=0; i<model1.Meshes.Count; i++)
{
ModelMesh mesh = model1.Meshes[i];
BoundingSphere origSphere = mesh.BoundingSphere;
Matrix trans = model1Transforms[mesh.ParentBone.Index]* world1;
BoundingSphere transSphere = XNAUtils.TransformBoundingSphere(
origSphere,trans);
model1Spheres[i] = transSphere;
}
Matrix[] model2Transforms = new Matrix[model2.Bones.Count];
model2.CopyAbsoluteBoneTransformsTo(model2Transforms);
BoundingSphere[] model2Spheres= new BoundingSphere[model2.Meshes.Count];
for (int i = 0; i < model1.Meshes.Count;i++)
{
ModelMesh mesh = model2.Meshes[i];
BoundingSphere origSphere = mesh.BoundingSphere;
Matrix trans = model2Transforms[mesh.ParentBone.Index] * world2;
BoundingSphere transSphere = XNAUtils.TransformBoundingSphere(
origSphere, trans);
model2Spheres[i]= transSphere;
}
bool collision = false;
for (int i=0; i<model1Spheres.Length;i++)
for (int j = 0; j < model2Spheres.Length; j++)
if (model1Spheres[i].Intersects(model2Spheres[j]))
return true;
return collision;
}
一般情况下,你首先进行快速检测,如果这次检测返回false,那就没必要进行精确检测。如果快速检测显示可能发生碰撞,那么还要进行更加精确的检测。
[color=blue][b]对小而快的对象使用Ray-Traced进行碰撞检测[/b][/color]。大部分的碰撞检测方法只在两个物体发生物理碰撞时才检测。但是,如果有一个小物体快速地穿过另一个物体,你的程序的更新速度就有可能跟不上而无法检测到碰撞。举一个具体的例子,比如一枚子弹打穿一个瓶子。子弹以5000km/h 的速度射向一个瓶子,而瓶子的宽度只有15cm。XNA程序每秒更新60次,所以每次更新子弹会前进23米的距离。这样的话,在调用Update方法时几乎没有可能检测到子弹和瓶子的碰撞,即使上一帧子弹的确穿过了瓶子。你可以在子弹的上一个位置和当前位置之间创建一个Ray。然后通过调用Ray或包围球的Intersect方法检测Ray是否和包围球发生碰撞。如果发生碰撞,这个方法返回碰撞点与Ray的终点间的距离(你可以使用子弹的前一个位置)。
private bool RayCollision(Model model, Matrix world, Vector3 lastPosition,
Vector3 currentPosition)
{
BoundingSphere modelSpere = (BoundingSphere)model.Tag;
BoundingSphere transSphere = XNAUtils.TransformBoundingSphere(
modelSpere, world);
Vector3 direction = currentPosition - lastPosition;
float distanceCovered = direction.Length();
direction.Normalize();
Ray ray = new Ray(lastPosition, direction);
bool collision = false;
float? intersection = ray.Intersects(transSphere);
if (intersection != null)
if (intersection <= distanceCovered)
collision = true;
return collision;
}
你同样可以通过在模型的不同ModelMesh上的小包围球上进行ray和包围球的碰撞检测提高精度。
下一节,将讨论Effect的相关知识~~ :wink: