你想在绘制模型之前独立地移动、旋转并/或缩放它们。
你可以设置对象的世界矩阵实现这个功能,而矩阵是可以存储任何变换(平移、旋转或缩放等操作)的对象。你可以简单地使用XNA框架提供的基本方法创建一个变换矩阵:
第一个方法创建一个平移矩阵,你可以定义将模型沿着X、Y和Z方向移动的距离。第二个方法让你可以缩放模型,第三个方法返回绕X、Y和Z轴旋转的矩阵。
你可以乘以这些矩阵组合多个变换,但必须按照正确的顺序,这会在本教程中讨论到。
当将模型绘制到屏幕上时,会将它的初始位置放置在3D空间的(0,0,0)点上,而最常见的操作就是让模型在3D场景中移动。
这很简单。在Draw方法中定义一个时间变量代表程序开始以来经过的秒数(精确起见使用毫秒,然后除以1000)。然后定义一个矩阵保存沿X轴方向的平移,而平移量由当前时间决定:
float time = (float)gameTime.TotalRealTime.TotalMilliseconds/1000.0f; Matrix worldMatrix = Matrix.CreateTranslation(time, 0, 0);
当将这个矩阵设置为世界矩阵时,可以让模型以每秒一个单位的速度沿x方向移动。下面的代码使用这个矩阵绘制模型:
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(); }
要让模型中的所有部分都在正确的位置,需要使Bone变换作为世界矩阵的一部分 (可见教程4-9学习bone变换的更多知识)。但这次,你通过将bone变换矩阵与平移矩阵相乘将两者组合起来,一定要将世界矩阵放置在乘式的右边,因为在矩阵乘法中顺序是很重要的。当你运行这个代码时,模型会缓缓地沿着x轴运动。
对于缩放和旋转方法类似。试着使用下面得世界矩阵:
Matrix worldMatrix = Matrix.CreateScale(time);
开始时time = 0,模型看不见,随后慢慢变大。一秒后达到原始大小,之后会继续变大。接下来使用这个矩阵:
Matrix worldMatrix = Matrix.CreateRotationY(time);
模型会持续地绕y轴旋转。
通常情况下,你会组合多个变换作为世界矩阵。例如,你想将一个角色移动到一个新位置,将它旋转到行走的方向并对它进行缩放。
因为这是一个小例子,所以使用下列世界矩阵:
Matrix worldMatrix = Matrix.CreateTranslation(time, 0, 0) * Matrix.CreateRotationY(time/10.0f);
这个组合矩阵将作为模型的世界矩阵。但是你可以用另一种方式组合这两个矩阵:
Matrix worldMatrix = Matrix.CreateRotationY(time / 10.0f) * Matrix.CreateTranslation(time, 0, 0);
当你运行程序后会发现结果是不同的。前面已经提到,在矩阵数学中,乘法的顺序是很重要的,接下来我将讨论这个问题。
在矩阵数学中,将矩阵M1乘以矩阵M2的结果通常与将M2乘以M1的结果是不同的,在下面的章节中,我会讨论所有可能的组合。
有一个规则(或者说是技巧)你必须记得:在矩阵乘法中,M1*M2就是“M1在M2之后”的意思。
旋转矩阵乘法的顺序是很重要的,这是因为当你首先绕A1轴旋转然后绕A2轴旋转,那么绕A1轴的旋转会在绕A2轴旋转前改变A2轴!
例如,M1表示绕向右轴旋转90度,M2表示绕向上轴旋转90度。
首先看一下M1*M2的情况,它的意思是“在绕向上轴之后绕向右轴旋转。”想一下结果会如何。在这种情况中,你的右手臂始终对着向右方向。你首先绕向上轴旋转90度,这样你会面向左方,如图4-2左图所示。当你看一下右手臂,你会发现它跟着你一起旋转!所以当你绕向右轴旋转90度后,你会面朝下躺倒在地,如图4-2的右图所示。
图4-2 先绕Up轴旋转后绕Right轴旋转
下面看一下第二种情况M2*M1,表示“在绕Right轴旋转后绕Up轴旋转。”你首先绕你的向右方向旋转,结果是面朝下,如图4-3的左图所示。你的向上方向变成了水平,即世界坐标系的向前方向。然后当你绕这个向前方向旋转时,结果是你面朝侧面,如图4-3的右图所示!
图4-3 先绕Right轴旋转后绕Up轴旋转
如你所见,M1*M2和M2*M1会导致不同的结果,这是因为当组合两个旋转时,第二个旋转轴会受到第一个旋转地影响。
在这种情况中,乘法的顺序仍是重要的。在这个例子中,M3表示绕Up轴旋转90度,M4表示沿x轴方向移动10个单位。 M3*M4表示“平移后旋转,”模型会首先移动到新位置。然后绕Up轴旋转,这两步如图4-4所示。
图4-4 先平移后旋转
M4*M3的情况不同。首先整个坐标系(包括模型和x轴)绕Up轴旋转90度,如图4-5左图所示。然后,模型沿着旋转过的x轴移动10个单位。但初始的x轴已经旋转了,本来它位于你的右方,但现在在你的前方!这意味着你实际上是沿世界坐标系向前的z轴在做平移。
图4-5 先旋转后平移
缩放和平移的乘法顺序还是重要的。本例中。M5表示缩放0.5倍,M6表示沿x轴平移10个单位。 M5*M6表示先平移后缩放。所以首先模型向右沿x轴平移10个单位,然后缩小,如图4-6所示。
图4-6 先平移后缩放
M6*M5的情况中你首先将整个坐标系(包括模型和x轴)缩小,然后缩小的模型沿着缩小的x轴移动10个单位。因为x轴也缩小了一半,所以只移动了相当于5个单位!结果是模型移动的不够远,如图4-7所示。
图4-7 先缩放后平移
幸运的是,有些变换无需考虑乘法的顺序。例如,组合两个平移矩阵是安全的,因为模型只是简单地移动了两次。两个缩放变换的组合也是安全的。
例如,先放大2倍再缩小10倍和先缩小10倍再放大2倍结果是一样的。最后,缩放不影响旋转,反之亦然。这是因为只会缩放坐标轴,但轴之间的角度仍保持90度不变。所以当缩放矩阵和旋转矩阵相乘时,你可以不用考虑乘法的顺序。
译者注:在《Microsoft XNA Game Studio Creator’s Guide》一书的第5章提到一个小技巧:使用I.S.R.O.T.作为矩阵组合的顺序通常就是你想要的结果,此处I.S.R.O.T. 分别代表Identity,Scale,Revolve,Orbit,Translate。
下面的代码组合了旋转和平移矩阵:
//draw model float time = (float)gameTime.TotalRealTime.TotalMilliseconds/1000.0f; Matrix worldMatrix = Matrix.CreateScale(0.005f)* Matrix.CreateRotationY(time / 10.0f) * Matrix.CreateTranslation(time, 0, 0); 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.ViewMatrix; effect.Projection = fpsCam.ProjectionMatrix; } mesh.Draw(); }