• 在3D世界中创建不同的相机模式——指定相机的目标


    2.2 指定相机的目标

    问题

    当定义View矩阵时,你需要指定Target向量作为参数,这个向量设置了相机的旋转。

    解决方案

    当选转相机时,相机的位置会保持不变。这意味着旋转取决于相机的Target点和Up向量的变化。你可以通过使用 (0,0,-1)Forward向量并将旋转施加到这个向量上获取Target向量,这会产生一个新的Target点。你可以使用相同的方法获取Up向量。

    工作原理

    如教程2-1所示,一个相机需要Position, Target和Up向量才能唯一确定。如果你想让相机绕着某一点旋转,Position向量保持不变,但Target和Up向量都要发生改变。

    给定绕着三根轴的旋转角度,一个方法是处理Target位置,但这会带来复杂的运算,还有一个更加清晰和快速的方法。

    最简单的例子:相机在初始位置,绕着Up向量

    旋转让我们先来看一下相机在(0,0,0)初始坐标的情况。它朝(0,0,-1) Forward方向观察,以默认的(0,1,0) Up向量作为向上方向。在这个情况中,你可以使用以下代码:

    Vector3 cameraPosition = new Vector3(0, 0, 0); 
    Vector3 cameraTarget = new Vector3(0, 0, -1); 
    Vector3 cameraUpVector = new Vector3(0, 1, 0);
    
    viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraTarget, cameraUpVector);

    例如你想创建一个绕Up向量旋转45度的View矩阵。如果你的头就是相机,这会让头部向右旋转45度。当计算新View矩阵时,Position向量和Up向量会保持不变,但你需要获取新的Target向量。你可以通过将使用45度旋转“转换”默认的(0,0,-1) Target向量获取新Target向量。这意味着你获取的这个向量是初始Target向量的旋转版本。下面是代码:

    Matrix cameraRotation = Matrix.CreateRotationY(MathHelper.PiOver4);
    Vector3 cameraPosition = new Vector3(0, 0, 0); 
    Vector3 cameraUpVector = new Vector3(0, 1, 0); 
    Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
    
    Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); 
    viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraRotatedTarget, cameraUpVector);

    注意:矩阵是表示某种变换的有力工具。一个矩阵可以表示旋转,平移,缩放或这些变换的组合。你可以在教程4-2中获取更多的例子。

    第一行代码创建了一个表示绕Up轴旋转45度的矩阵,对应PI/4弧度。下面的代码通过这个旋转矩阵转换初始(0,0,-1) Target向量,并将旋转过的Target向量保存在cameraRotatedTarget变量中,这个变量用来创建一个新的View矩阵。

    注意:这个变换并不神秘;它是向量和矩阵的乘积,只是简单地进行16次乘法和12次加法。

    第二个例子:相机在初始位置,任意旋转

    现在来看一个有点复杂的例子。你想使相机绕着任意轴旋转而不是绕着Up向量。例如,绕着(1,0,0) Right轴旋转45度,如果你的头是相机,这会导致斜向上45度观察。

    因为你仅旋转相机,Position向量保持不变。和前面一个例子一样,Target向量会变化,因为相机需要观察一个不同的位置。

    但是这种情况中Up向量也会发生变化。在前面的例子中你将头部转向右边,Up向量并不会变化。这个例子中,将头部向上旋转,Up向量和Target向量都要发生改变。

    变换Up向量和变换Forward向量的方法是一样的:你将旋转存储在一个矩阵中并定义初始Up向量。然后,通过这个旋转矩阵变换初始向量获取新的Up向量。下面是代码:

    Matrix cameraRotation = Matrix.CreateRotationX(MathHelper.PiOver4);
    Vector3 cameraPosition = new Vector3(0, 0, 0);
    Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
    Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
    
    Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); 
    Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); 
    viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraRotatedTarget, cameraRotatedUpVector);

    这里的“任意”旋转矩阵只是一个简单绕x轴的旋转。这个代码也可以处理任何旋转,例如下面的这个情况,它组合了三根轴上的旋转。下面的代码生成一个矩阵,这个矩阵是绕z轴–45度和y轴22.5度,x轴90度旋转的组合:

    Matrix cameraRotation = Matrix.CreateRotationX(MathHelper.PiOver2)* Matrix.CreateRotationY(MathHelper.Pi/8.0f)* Matrix.CreateRotationZ(-MathHelper.PiOver4);

    当获取新的Forward向量和Up向量时,相机的位置保持不变。

    第三个例子:相机在指定位置,任意旋转

    在大多数情况中,你想设置任意旋转并指定相机的位置。例如相机在位置(10,20,30),不旋转。非常简单,相机的Position向量为(10,20,30)。因为没有旋转,所以相机观察(0,0,-1) Forward方向。

    注意:记住需要指定Target而不是Target方向!将(0,0,-1)作为Target向量是错误的,因为这会让相机观察点(0,0,-1)。例如你将相机移动到点(-10,20,30),如果仍然指定(0,0,-1)作为Target向量,相机仍会观察点(0,0,-1),这个相机观察的方向就会发生变化!

    要让位于(10,20,30)相机朝向(0,0,-1)方向,你需要指定(10,20,29)作为Target向量。你可以通过求相机位置和目标方向的和获取这个向量:

    Vector3 cameraPosition = new Vector3(10, 20, 30); 
    Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); 
    cameraTargetPoint = cameraPosition + cameraOriginalTarget;

    现在,可以组合前面所学到的东西了。你将定义一个位于(10,20,30)的相机并可以任意旋转。Position向量保持(10,20,30)不变。对Target向量,首先定义为(0,0,-1)方向。要在旋转后获取Forward方向,你需要使用旋转矩阵变换它。最后,要获取Target向量让位于(10,20,30)的相机看向旋转方向,你需要在这个旋转方向上加上(10,20,30)。Up向量用同样的方法获取,下面是最后的代码:

    Matrix cameraRotation =Matrix.CreateRotationX(MathHelper.PiOver2)*  Matrix.CreateRotationY(MathHelper.Pi/8.0f)*Matrix.CreateRotationZ(-MathHelper.PiOver4); 
    Vector3 cameraPosition = new Vector3(10, 20, 30); 
    Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); 
    Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
    
    Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
    Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;
    
    Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);
    Vector3 cameraFinalUpVector = cameraPosition + cameraRotatedUpVector;
    
    viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraFinalUpVector);
    将旋转过的相机前后/左右移动

    现在已经实现了指定位置的相机并朝向正确的方向,新的挑战是前后移动相机。如果你想让相机向前移动,简单地将(0,0,-1) Forward向量添加到Position向量上不会成功,因为首先需要获取对应旋转相机的Forward向量。你已经在本教程中的第一个例子中使用过旋转矩阵变换过(0,0,-1) Forward向量了,有了这个变换过的Forward向量,才可以将它加到Position向量中:

    float moveSpeed = 0.5f;
    Vector3 cameraOriginalForward = new Vector3(0,0,-1);
    Vector3 cameraRotatedForward = Vector3.Transform(cameraOriginalForward, cameraRotation);
    cameraPosition += moveSpeed * cameraRotatedForward;

    改变moveSpeed的值可以增加/减少相机移动的速度,因为这个值会乘以旋转过的 Forward方向。

    同样的方法也可以让相机左右移动。处理的是(1,0,0) Right向量而不是(0,0,-1) Forward向量,仍然需要首先进行变换以获取对应当前相机旋转的Right向量。

    float moveSpeed = 0.5f;
    Vector3 cameraOriginalRight = new Vector3(1, 0, 0);
    Vector3 cameraRotatedRight = Vector3.Transform(cameraOriginalRight, cameraRotation); 
    cameraPosition += moveSpeed * cameraRotatedRight;
    代码

    这个方法中相机只需保存当前位置和旋转,位置和旋转任意一个发生变化就要更新View 矩阵。这些变化通常来自于用户输入,可以在教程2-3和2-4中看到具体实现。每个矩阵都需要进行初始化,你首先需将cameraRotation矩阵设置为单位矩阵。

    protected override void Initialize()
    {
        float viewAngle = MathHelper.PiOver4;
        float aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio; 
        float nearPlane = 0.5f;
        float farPlane = 100.0f;
        projectionMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearPlane, farPlane);
    
        cameraPosition = new Vector3(-5, 7, 14);
        cameraRotation = Matrix.CreateRotationX(-MathHelper.Pi/8.0f)* Matrix.CreateRotationY(-MathHelper.Pi/8.0f);
    
        UpdateViewMatrix();
        base.Initialize();
    }
    
    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();
    
        MoveCameraForward();
    
        base.Update(gameTime); 
    }
    
    private void MoveCameraForward()
    {
        float moveSpeed = 0.05f;
        Vector3 cameraOriginalForward = new Vector3(0, 0, -1); 
        Vector3 cameraRotatedForward = Vector3.Transform(cameraOriginalForward, cameraRotation); 
        cameraPosition += moveSpeed * cameraRotatedForward; 
        UpdateViewMatrix();
    }
     
    private void UpdateViewMatrix()
    {
        Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); 
        Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
    
        Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
        Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;
    
        Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);
        viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); 
    }
    
    protected override void Draw(GameTime gameTime) 
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
        //render coordcross using specified View and Projection matrices 
        cCross.Draw(viewMatrix, projectionMatrix);
    
        base.Draw(gameTime); 
    }
    扩展阅读
    矩阵乘法的顺序

    可参见教程4-2。

    改变坐标系统

    前面使用的Forward (0,0,-1)和Up (0,1,0)方法是“官方的” XNA向量,也可以使用Vector3 . Forward和Vector3 . Up快捷方式获取。但是这只是一个约定,你可以定义一个完全不同的坐标系统。例如也可以使用(0,0,1)作为Up方向,(0,1,0)作为Forward方向,(1,0,0)作为Right方向,这完全取决于你的需要。

    但是这三个向量应该符合一个规则。在XNA中x,y和z轴是右手坐标系。意思是一旦你知道了任意两个坐标轴,就可以知道第三个坐标轴的方向。展开你右手的大拇指和食指,然后弯曲中指使它垂直于食指和大拇指,将这三个手指看成坐标轴,如果是右手坐标系,那么x轴就对应大拇指,y轴对应食指,z轴对应中指。

    本教程第一段的坐标系统,对应大拇指指向右方,中指指向上方 。要代表“官方”XNA坐标系统,将你的大拇指指向右方(正x轴 = 右方),食指指向上方 (y轴=上方)。现在你可以看到中指指向后方(正z轴 =后方), 这就是为什么官方Forward (0,0,-1)向量中有个负号的原因!

  • 相关阅读:
    altium designer 工程中的原理图库和封装如何提取出来
    555是集成电路
    安装JDK+Tomcat,进行环境变量设置,和运行JSP
    2018年房价会涨吗?
    MySQLroot密码的恢复方法
    mysql中的数据类型长度
    感觉还不错,受益很高,有保障
    phpstorm问题
    Mac上的抓包工具Charles
    解决Firefox显示“已阻止载入混合活动内容”的方法
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120175.html
Copyright © 2020-2023  润新知