[翻译]XNA 3.0 Game Programming Recipes之four
PS:自己翻译的,转载请著明出处
这里的相机旋转是定义个变量,这个变量定义了摄象机该如何旋转。文章中的旋转表示的定义的旋转矩阵,请细细品位。
2-4.创造一个自由风格的照相机:用四元数全三维旋转
问题
您想要建立一个摄像机,以一切可能的方式可以旋转,例如,要创建飞行游戏。做到这一点您将需要约3个轴旋转,但由于万向锁(每次旋转已经影响到其他旋转) ,这已成为非常困难的,不过不是不可能的。
解决方案
结合几个旋转围绕多个轴万向锁发生,这样导致了错误的结果。使用4元数组存储摄象机的旋转可以帮你避免这样的问题发生。
如何工作
当您结合围绕2个不同的轴的2次旋转,万向锁将触发。这个 是因为第一轮旋转也将在第二轴旋转,具体解释参看4-2节 。在这一节,旋转你的摄象机围绕着X-,Y-,Z-轴旋转。第二轴的旋转基于第一次的旋转和第三次旋转是由前2个轴的联合。所以,很难想象最后的结果。
旋转的一个属性,始终存在一个单一的旋转,有同样的结果作为多个旋转的组合。所以,在这里你需要的小技巧是定义唯一围绕旋转的坐标轴,摄象机围绕这个轴旋转。这可以是任何轴:它不必须之一的X , Y型,或Z轴。这个轴和数额的旋转将存放在一个变量中,这是所谓的四元。
四元数组是完美的储存一轮旋转,因为一个四元数可以存储一个轴和可容纳一定数量围绕坐标轴旋转的角度。
下一问题是,你如何计算这些坐标轴?你不能。进程会帮你把它实现。你只需要指定一个开始的坐标轴,用户移动鼠标时这些坐标轴将被更新,在下个例子中我们会看到。
当你开始围绕摄象机的向上的坐标轴旋转。对于一些用户的输入,想让摄象机围绕右边矢量方向旋转。右边矢量方向在当前摄象机定义的旋转而旋转,又因为定义的是0度,所以右边矢量保持不变。右边矢量方向成为当前旋转坐标轴。摄象机围绕这个轴被旋转,摄象机有一点向上(the camera is looking up a bit)。
下一步用户想让摄象机围绕向前的轴旋转.这轴头在当前的摄象机旋转下被旋转,而现在已不再是零,但包含一个围绕向右的坐标轴的旋转。双方旋转相结合,以及由此产生的单轴旋转和数额的旋转是储存在相机的四元。这种旋转现在成为目前摄像头的旋转。
用户的每次输入,同样的程序发生:向前/右/上/下...向量被当前摄象机的轴旋转,并结合这一新的轮旋转和目前相机的旋转作为新的摄象机四元被存储,在新的定义旋转后摄象机又将被旋转。
幸运的是,整个程序思想被转化为简单的代码。
2 KeyboardState keys=Keyboard.GetState();
3 if(keys.IsKeyDown(Keys.Up))
4 updownRotation=1.5f;
5 if(keys.IsKeyDown(Keys.Dwon))
6 updownRotation=-1.5f;
7 Quaternion additionalRotation=Quaternion.CreateFromAxisAngle(new Vector3(1,0,0),updownRotation);
8 cameraRotation=cameraRotation*additionalRotation;
首先,用户的输入决定摄象机是否围绕向上或者向下方向旋转。下一步,一个新的四元数组被创建它保存了围绕坐标轴围绕的旋转。最后,新的旋转矩阵把相机当前的旋转相整合,结果存在新的相机矩阵里。
注意 四元数这个词通常是让程序员颤抖。我已经遇到了许多文章和目前的四元数的网站,几乎就像是黑暗魔法,无法理解或形象化。 然而,这是完全不真实的。四元数是用来储存旋转的轴心,因此,它需要能够储存这个轴和一顶数量的旋转角度。轴的定义是由三个数和一定数量的度当然也可以一个数字定义。所以,如果你要存储旋转,最简便的方法来存储一个或四个数字。猜猜什么是真实的四元数?简单地说,这四个数字。没有什么神奇的。The math behind their operations closely resembles complex magic,though.
使用任何你知道的方法让你的相机旋转,您需要能够旋转沿第二轴旋转。这种旋转需要储存在在先前代码additionalRotation变量中,第一个轴的旋转乘以第二个轴的旋转。
2 float leftrightRotation=0.0f;
3 KeyboardState keys=Keyboard.GetState();
4 if(keys.IsKeyDown(Keys.Up))
5 updownRotation=0.05f;
6 if(keys.IsKeyDown(Keys.Dwon))
7 updownRotation=-0.05f
8 if(keys.IsKeyDown(Keys.Right))
9 updownRotation=-0.05f;
10 if(keys.IsKeyDown(Keys.Left))
11 updownRotation=0.05f
12 Quaternion additionalRotation=Quaternion.CreateFromAxisAngle(new Vector3(1,0,0),updownRotation)*Quaternion.CreateFromAxisAngle(new Vector3(0,1,0),leftrightRotation);
13 cameraRotation=cameraRotation*additionalRotation;
注意:由于矩阵乘法,顺序,而你乘四元数是非常重要的。为什么很重要,请阅读章节4-2的顺序矩阵乘法。当您在这里做,你必须记住这到四元数相乘时。但是,对于四元数有一个排除这一规则,这两个旋转轴垂直于对方的情况一样,在这里您计算additionalRotation变量,这意味着不管你第一次轮旋转在( 1,0,0 )右部向量,然后在( 0,1,0 )上部向量,或者您以其他方式旋转,其结果将是一样的。 当你计算cameraRotation变量情况并非如此,因为这两个轴不垂直于对方。所以,在这里,重要的是你写cameraRotation*additionalRotation.
注意:在矩阵乘法,*的方法见4-2节 。因为事情本来很容易了, *在四元数乘法的手段之前。因此,cameraRotation=cameraRotation*additionalRotation自于cameraRotation在additionalRotation之前,该轴储存在additionalRotation变量将首先围绕旋转的轴心储存在cameraRotation变量中。
一旦旋转矩阵成立,那么摄象机的View matrix就可以被构造,具体例子在2-2节
2 {
3 Vector3 cameraOriginalTarget=new Vector3(0,0,-1);
4 Vector3 cameraOriginalUpVector=new Vector3(0,1,0);
5 Vector3 cameraRotatedTarget=Vector3.Transform(cameraOriginalTarget,cameraRotation);
6 Vector3 cameraFinalTarget=cameraPosition+cameraRotatedTarget;
7 Vector3 cameraRotatedUpVector=Vector3.Transform(cameraOriginalUpVector,cameraRotation);
8 viewMatrix=Matrix.CreateLookAt(cameraPosition,cameraFinalTarget,cameraRotatedUpVector);
9 }
移动的相机,例如,当用户按下方向键,可以做到首先旋转运动矢量的摄像机旋转,然后通过添加此转变矢量目前摄像头的位置是:
2 Vector3 rotatedVector=Vector3.Transform(vectorToAdd,cameraRotation);
3 cameraPosition+=moveSpeed*rotatedVector;
4 UpdateViewMatrix();
代码
因为这种类型的相机通常是用在空间游戏中,代码在本节介绍,如果您要创建一个空射击和3D世界需要提供试验飞船内飞行员。用户必须能够靠键盘或鼠标控制旋转的摄像头,以及相机自动向前移动。键盘控制的例子将会放出;您可以找到如何使用鼠标这2-3节。
实施四元数的摄像系统,只有两个变量是必要的:当前的摄象机的位置和摄象机的旋转。
2 {
3 cCross=new CoordCross();
4 base.Initialize();
5 float viewAngle=MathHelper.PiOver4;
6 float nearPlane=0.5f;
7 float farPlane=100.0f;
8 projectionMatrix=Matrix.CreatePerspectiveFieldOfView(viewAngle,aspectRation,nearPlane,farPlane);
9 cameraPosition=new Vector3(-1,1,10);
10 cameraRotation=Quaternion.Indentity;
11 UpdateViewMatrix();
12 }
13 protected override void Update(GameTime gameTime)
14 {
15 if(GamePad.GetState(PlayerIndex.One).Buttons.Back==ButtonState.Pressed)
16 this.Exit();
17 float updownRotation=0.0f;
18 float leftrightRotation=0.0f;
19 KeyboardState keys=Keyboard.GetState();
20 if(Keys.IsKeyDown(Keys.Up))
21 updownRotation=0.05f;
22 if(Keys.IsKeyDown(Keys.Down))
23 updownRotation=-0.05f;
24 if(Keys.IsKeyDown(Keys.Right))
25 leftrightRotation=-0.05f;
26 if(Keys.IsKeyDown(Keys.Up))
27 leftrightRotation=0.05f;
28 Quaternion additionalRotation=Quaternion.CreateFromAxisAngle(new Vector3(1,0,0),updownRotation)*Quation.CreateFromAxisAngle(new Vector3(0,1,0),leftrightRotation);
29 cameraRotation=cameraRotation*additionalRotation;
30 AddToCameraPosition(new Vector3(0,0,-1));
31 base.Update(gameTime);
32 }
33 private void AddToCameraPosition(Vector3 vectorToAdd)
34 {
35 float moveSpeed=0.05f;
36 Vector3 rotatedVector=Vector3.Transform(vectorToAdd,cameraRotation);
37 cameraPosition +=moveSpeed*rotatedVector;
38 UpdateViewMatrix();
39 }
40 private void UpdateViewMatrix()
41 {
42 Vector3 cameraOriginalTarget=new Vector3(0,0,-1);
43 Vector3 cameraOriginalUpVector=new Vector3(0,1,0);
44 Vector3 cameraRotatedTarget=Vector3.Transform(cameraOriginalTarget,cameraRotation);
45 Vector3 cameraFinalTarget=cameraPosition+cameraRotatedTarget;
46 Vector3 cameraRotatedUpVector=Vector3.Transform(cameraOriginalUpVector,cameraRotation);
47 viewMatrix=Matrix.CreateLookAt(cameraPosition,cameraFinalTarget,cameraRotateUpVector);
48 }
课外阅读
相机之后对象
那么,如果你想把摄象机放在航天器背后面您可以使用早些时候代码创造一个空间的游戏而摄像头的位置在航天器驾驶舱内。您将需要其他的代码,你可以在这里找到。
这种情况下,航天器相应的位置和旋转都会被存储,在航天器位置的基础上计算出摄象机的位置。您可以使用早些时候的代码,这将使在航天器任意方向旋转和飞行。只是现在,你想要把摄象机定位到航天器的背后。
请记住,如果你想定义相机的视图矩阵,你需要三个矢量:相机的位置,目标和向上的向量,更多信息在2-1节。
如果你想把摄象机放在航天器的后面,把摄象机的镜头朝向航天器。因此,你可以知道视景矩阵的目标向量:航天器的位置。相机的向上的方向也是航天器的上部方向。这个向量就是(0,1,0)这是航天器的默认向量。
最后一个需要被说明的向量是相机的位置向量。将相机放在航天器的后面。自(0,0,-1)向量通常是作为前锋向量,(0,0,1)表示的背后向量这个向量会与航天器一起被旋转。然后,您需要购买此向量的位置,因此产生的摄象机的位置随着航天器位置的改变而发生改变。
下面是如何生成的:
2 {
3 if(GamePad.GetState(PlayerIndex.One).Buttons.Back==ButtonState.Pressed)
4 this.Exit();
5 float updownRotation=0.0f;
6 float leftrightRotation=0.0f;
7 KeyboardState keys=Keyboard.GetState();
8 if(Keys.IsKeyDown(Keys.Up))
9 updownRotation=0.05f;
10 if(Keys.IsKeyDown(Keys.Down))
11 updownRotation=-0.05f;
12 if(Keys.IsKeyDown(Keys.Right))
13 leftrightRotation=-0.05f;
14 if(Keys.IsKeyDown(Keys.Up))
15 leftrightRotation=0.05f;
16 Quaternion additionalRotation=Quaternion.CreateFromAxisAngle(new Vector3(1,0,0),updownRotation)*Quation.CreateFromAxisAngle(new Vector3(0,1,0),leftrightRotation);
17 cameraRotation=cameraRotation*additionalRotation;
18 AddToCameraPosition(new Vector3(0,0,-1));
19 base.Update(gameTime);
20 }
21 private void AddToCameraPosition(Vector3 vectorToAdd)
22 {
23 float moveSpeed=0.05f;
24 Vector3 rotatedVector=Vector3.Transform(vectorToAdd,cameraRotation);
25 cameraPosition +=moveSpeed*rotatedVector;
26 UpdateViewMatrix();
27 }//在UpdateViewMatrix方法中,摄象机的实际位置被计算出来在飞行器的位置后面
28 private void UpdateViewMatrix()
29 {
30 Vector3 cameraOriginalTarget=new Vector3(0,0,1);
31 Vector3 cameraRotatedTarget=Vector3.Transform(cameraOriginalTarget,spacecraftRotation);
32 Vector3 cameraFinalPosition=spacecraftRotation+cameraRotatedTarget;
33 Vector3 cameraOriginalUpVector=new Vector3(0,1,0);
34 Vector3 cameraRotatedUpVector=Vector3.Transform(cameraOriginalUpVector,spacecraftRotation);
35 viewMatrix=Matrix.CreateLookAt(cameraFinalPosition,spacecraftRotation,cameraRotateUpVector);
36 }//添加Draw循环,需要旋转和改变航天器来响应当前的旋转和位置。
37 Matrix worldMatrix=Matrix.CreateScale(0.001f,0.001f,0.001f)*Matrix.CreateFromQuaternion(spacecraftRotation)*Matrix.CreateTranslation(spacecraftPosition);
38 spacecraftModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
39 foreach(ModeMesh mesh in spacecraftModel.Meshes)
40 {
41 foreach(BasicEffect effect in mesh.Effects)
42 {
43 effect.EnableDefaultLighting();
44 effect.World=modelTransforms[mesh.ParentBone.Index]*worldMatrix;
45 effect.View=viewMatrix;
46 effect.Projection=projectionMatrix;
47 }
48 mesh.Draw();
49 }//Model的世界矩阵依靠航天器的旋转向量保存在四元数和它的位置。第4章会有更多关于3D对象的绘制与位置的设置。