c++ - 实现复杂的基于旋转的相机

标签 c++ math matrix directx quaternions

我正在实现用于空间可视化的3D引擎,并且正在编写具有以下导航功能的摄像头:

  • 旋转相机(即类似于旋转头部)
  • 围绕任意3D点旋转(空间中的一个点,该点可能不在屏幕中心;相机需要围绕此点旋转,以保持相同的相对外观方向,即外观方向也会改变。这不会直接看起来在选定的旋转点)
  • 在相机平面中平移(因此,在与相机的外观 vector 正交的平面中向上/向下或向左/向右移动)

  • 相机不应该滚动-也就是说,“向上”保持向上。因此,我用一个位置和两个角度来表示摄像机,围绕X和Y轴旋转(Z会滚动)。然后使用摄像机的位置和这两个角度重新计算 View 矩阵。这对于平移和旋转眼睛很有用,但不适用于绕任意点旋转。相反,我得到以下行为:
  • 眼睛本身显然比
  • 还要向上或向下移动
  • m_dRotationX为0或pi时,眼睛根本不会向上或向下移动。 (万向节锁?如何避免这种情况?)
  • m_dRotationX在pi和2pi之间时,眼睛的旋转反转(改变旋转使其在应向下看时向上看,在应向上看时向下看)。

  • (a)是什么导致这种“漂移”旋转?

    这可能是gimbal lock。如果是这样,那么对此的标准答案是“使用四元数来表示旋转”,在SO上多次说过(例如123),但是不幸的是没有具体细节(example。这是the best answer我发现了到目前为止,这种情况很少见。)我一直努力使用结合了以上两种旋转类型的四元数来实现相机。实际上,我正在使用两次旋转来构建四元数,但是下面的评论者说没有理由-立即构建矩阵是可以的。

    在围绕点旋转时更改X和Y旋转(代表相机外观方向)时会发生这种情况,而在直接更改旋转(即围绕相机自身旋转)时,这不会简单发生。对我来说,这没有道理。相同的值。

    (b)对于此相机,采用其他方法(例如四元数)会更好吗?如果是这样,如何实现上述所有三个相机导航功能?

    如果采用其他方法会更好,请考虑提供该方法的具体实现示例。 (我使用的是DirectX9和C++,以及SDK提供的D3DX *库。)在第二种情况下,我将在几天内向问题中添加一个赏金,并予以奖励。这听起来像是我在开枪,但是我时间紧迫,需要快速实现或解决(这是一个期限很紧的商业项目。)详细的答案也将改善SO文件,因为大多数到目前为止,我读到的相机答案很少涉及代码。

    谢谢你的帮助 :)

    一些说明

    到目前为止,感谢您的评论和回答!我将尝试澄清有关该问题的一些信息:
  • 只要其中一项发生变化,就会根据相机位置和两个角度重新计算 View 矩阵。矩阵本身永远不会累积(即更新)-重新进行计算。但是,会累积相机位置和两个角度变量(例如,每当鼠标移动时,一个或两个角度都会根据鼠标上下移动的像素数和/或将少量的角度相加或减去。或左右屏幕。)
  • 评论员JCooper指出我患有万向节锁定,我需要:

  • add another rotation onto your transform that rotates the eyePos to be completely in the y-z plane before you apply the transformation, and then another rotation that moves it back afterward. Rotate around the y axis by the following angle immediately before and after applying the yaw-pitch-roll matrix (one of the angles will need to be negated; trying it out is the fastest way to decide which). double fixAngle = atan2(oEyeTranslated.z,oEyeTranslated.x);



    不幸的是,当按上述方式执行此操作时,由于旋转之一,我的眼睛以非常快的速度从场景上方射出。我确定我的代码只是该描述的错误实现,但我仍然需要更具体的东西。总的来说,我发现算法的非特定文本描述没有注释说明的实现有用。 我为一个具体的工作示例添加了赏金,该示例与下面的代码集成在一起(即,也与其他导航方法集成在一起)。这是因为我想了解解决方案,并且有一些可行的方法,并且因为我迫在眉睫的截止日期,所以我需要快速实现一些可行的方法。

    请,如果您用算法的文本描述来回答,请确保足够详细地实现(“绕Y旋转,然后变换,然后向后旋转”可能对您有意义,但缺少详细信息以了解您的意思。Good answers are clear, signposted, will allow others to understand even with a different basis, are 'solid weatherproof information boards.' )

    反过来,我尝试清楚地描述问题,如果可以使问题更清楚,请告诉我。

    我当前的代码

    要实现以上三个导航功能,在鼠标移动事件中,光标将根据移动的像素移动:
    // Adjust this to change rotation speed when dragging (units are radians per pixel mouse moves)
    // This is both rotating the eye, and rotating around a point
    static const double dRotatePixelScale = 0.001;
    // Adjust this to change pan speed (units are meters per pixel mouse moves)
    static const double dPanPixelScale = 0.15;
    
    switch (m_eCurrentNavigation) {
        case ENavigation::eRotatePoint: {
            // Rotating around m_oRotateAroundPos
            const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
            const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
    
            // To rotate around the point, translate so the point is at (0,0,0) (this makes the point
            // the origin so the eye rotates around the origin), rotate, translate back
            // However, the camera is represented as an eye plus two (X and Y) rotation angles
            // This needs to keep the same relative rotation.
    
            // Rotate the eye around the point
            const D3DXVECTOR3 oEyeTranslated = m_oEyePos - m_oRotateAroundPos;
            D3DXMATRIX oRotationMatrix;
            D3DXMatrixRotationYawPitchRoll(&oRotationMatrix, dX, dY, 0.0);
            D3DXVECTOR4 oEyeRotated;
            D3DXVec3Transform(&oEyeRotated, &oEyeTranslated, &oRotationMatrix);
            m_oEyePos = D3DXVECTOR3(oEyeRotated.x, oEyeRotated.y, oEyeRotated.z) + m_oRotateAroundPos;
    
            // Increment rotation to keep the same relative look angles
            RotateXAxis(dX);
            RotateYAxis(dY);
            break;
        }
        case ENavigation::ePanPlane: {
            const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dPanPixelScale;
            const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dPanPixelScale;
            m_oEyePos += GetXAxis() * dX; // GetX/YAxis reads from the view matrix, so increments correctly
            m_oEyePos += GetYAxis() * -dY; // Inverted compared to screen coords
            break;
        }
        case ENavigation::eRotateEye: {
            // Rotate in radians around local (camera not scene space) X and Y axes
            const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
            const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
            RotateXAxis(dX);
            RotateYAxis(dY);
            break;
        }
    
    RotateXAxisRotateYAxis方法非常简单:
    void Camera::RotateXAxis(const double dRadians) {
        m_dRotationX += dRadians;
        m_dRotationX = fmod(m_dRotationX, 2 * D3DX_PI); // Keep in valid circular range
    }
    
    void Camera::RotateYAxis(const double dRadians) {
        m_dRotationY += dRadians;
    
        // Limit it so you don't rotate around when looking up and down
        m_dRotationY = std::min(m_dRotationY, D3DX_PI * 0.49); // Almost fully up
        m_dRotationY = std::max(m_dRotationY, D3DX_PI * -0.49); // Almost fully down
    }
    

    并由此生成 View 矩阵:
    void Camera::UpdateView() const {
        const D3DXVECTOR3 oEyePos(GetEyePos());
        const D3DXVECTOR3 oUpVector(0.0f, 1.0f, 0.0f); // Keep up "up", always.
    
        // Generate a rotation matrix via a quaternion
        D3DXQUATERNION oRotationQuat;
        D3DXQuaternionRotationYawPitchRoll(&oRotationQuat, m_dRotationX, m_dRotationY, 0.0);
        D3DXMATRIX oRotationMatrix;
        D3DXMatrixRotationQuaternion(&oRotationMatrix, &oRotationQuat);
    
        // Generate view matrix by looking at a point 1 unit ahead of the eye (transformed by the above
        // rotation)
        D3DXVECTOR3 oForward(0.0, 0.0, 1.0);
        D3DXVECTOR4 oForward4;
        D3DXVec3Transform(&oForward4, &oForward, &oRotationMatrix);
        D3DXVECTOR3 oTarget = oEyePos + D3DXVECTOR3(oForward4.x, oForward4.y, oForward4.z); // eye pos + look vector = look target position
        D3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);
    }
    

    最佳答案

    在我看来,鉴于您形成 View 矩阵的方式,“滚动”应该是不可能的。不管其他所有代码(其中有些看上去确实有些有趣),当给定D3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);作为“向上” vector 时,调用[0,1,0]应该创建一个没有滚动的矩阵,除非oTarget-oEyePos碰巧与向上 vector 平行。由于您将m_dRotationY限制在(-.49pi,+。49pi)之内,因此似乎并非如此。

    也许您可以弄清如何知道“滚动”正在发生。您是否有一个接地平面,并且该接地平面的水平线偏离水平面?

    顺便说一句,在UpdateView中,D3DXQuaternionRotationYawPitchRoll似乎完全不必要,因为您可以立即转过来并将其更改为矩阵。只需像在鼠标事件中一样使用D3DXMatrixRotationYawPitchRoll即可。在相机中使用四元数是因为它们是积累眼坐标中发生的旋转的便捷方法。由于您仅严格使用两个旋转轴,因此累积角度的方法应该很好。 (0,0,1)的 vector 变换也实际上不是必需的。 oRotationMatrix条目中应该已经具有这些值。

    更新

    鉴于它不是滚动的,这是问题所在:创建一个旋转矩阵以在世界坐标系中移动眼睛,但您希望俯仰在相机坐标系中发生。由于不允许滚动并且最后执行偏航,因此在世界和相机参照系中偏航始终相同。请看下面的图片:



    您的代码对局部俯仰和偏航非常有效,因为它们是在相机坐标中完成的。

    但是,当您绕引用点旋转时,您将创建一个以世界坐标表示的旋转矩阵,并使用该矩阵旋转摄影机中心。如果相机的坐标系恰好与世界对齐,则可以正常工作。但是,如果在旋转相机位置之前不检查是否超出俯仰限制,则达到该限制时会出现疯狂的行为。相机将突然开始在世界范围内滑行-仍然围绕引用点“旋转”,但不再改变方向。

    如果相机的轴与世界的轴不对齐,将会发生奇怪的事情。在极端情况下,相机根本不会移动,因为您正试图使其旋转。

    以上是通常会发生的情况,但是由于您单独处理摄像头方向,因此摄像头实际上不会滚动。

    相反,它保持直立状态,但是您正在进行奇怪的翻译。

    一种解决方法是(1)始终将相机置于相对于引用点的规范位置和方向,(2)旋转,然后(3)完成后放回相机(例如,类似于将引用点转换为原点,应用偏航-俯仰旋转,然后向后平移的方式)。但是,考虑得更多,这可能不是最好的方法。

    更新2

    我认为通用人类的答案可能是最好的。问题仍然是如果旋转离轴应应用多少螺距,但是现在,我们将忽略它。也许它将给您可接受的结果。

    答案的实质是:在移动鼠标之前,您的摄像头位于 c1 = (_31,_32,_33),并通过 M1定位M1 = m_oEyePos。考虑引用点 = D3DXMatrixRotationYawPitchRoll(&M_1,m_dRotationX,m_dRotationY,0)。从摄像机的角度来看,这一点是 a'= M1(a-c1)

    您想将相机的方向更改为 M2 = m_oRotateAroundPos。 [重要提示:由于您不允许D3DXMatrixRotationYawPitchRoll(&M_2,m_dRotationX+dX,m_dRotationY+dY,0)落在特定范围之外,因此应确保dY不会违反该限制。]当摄像机改变方向时,您还希望其位置围绕旋转到一个新点 c2 。这意味着从相机的角度来看,不会改变。即 M1(a-c1)== M2(a-c2)

    因此,我们求解 c2 (请记住,旋转矩阵的转置与逆矩阵相同):

    M2TM1(a-c1)==((a-c2) =>

    -M2TM1(a-c1)+ a == c2

    现在,如果我们将其视为应用于 c1 的转换,则可以看到它首先被否定,然后由翻译为,然后由 M1 旋转,然后由 M2T 旋转,再次被否定然后再由翻译为。这些是图形库擅长的转换,可以将它们全部压缩为一个转换矩阵。

    @Generic Human的答案值得称赞,但是这里有相关代码。当然,您需要实现该功能以在应用音高变化之前对其进行验证,但这很简单。这段代码可能有一些错别字,因为我没有尝试编译:

    case ENavigation::eRotatePoint: {
        const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
        double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
        dY = validatePitch(dY); // dY needs to be kept within bounds so that m_dRotationY is within bounds
    
        D3DXMATRIX oRotationMatrix1; // The camera orientation before mouse-change
        D3DXMatrixRotationYawPitchRoll(&oRotationMatrix1, m_dRotationX, m_dRotationY, 0.0);
    
        D3DXMATRIX oRotationMatrix2; // The camera orientation after mouse-change
        D3DXMatrixRotationYawPitchRoll(&oRotationMatrix2, m_dRotationX + dX, m_dRotationY + dY, 0.0);
    
        D3DXMATRIX oRotationMatrix2Inv; // The inverse of the orientation
        D3DXMatrixTranspose(&oRotationMatrix2Inv,&oRotationMatrix2); // Transpose is the same in this case
    
        D3DXMATRIX oScaleMatrix; // Negative scaling matrix for negating the translation
        D3DXMatrixScaling(&oScaleMatrix,-1,-1,-1);
    
        D3DXMATRIX oTranslationMatrix; // Translation by the reference point
        D3DXMatrixTranslation(&oTranslationMatrix,
             m_oRotateAroundPos.x,m_oRotateAroundPos.y,m_oRotateAroundPos.z);
    
        D3DXMATRIX oTransformMatrix; // The full transform for the eyePos.
        // We assume the matrix multiply protects against variable aliasing
        D3DXMatrixMultiply(&oTransformMatrix,&oScaleMatrix,&oTranslationMatrix);
        D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix1);
        D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix2Inv);
        D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oScaleMatrix);
        D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oTranslationMatrix);
    
        D3DXVECTOR4 oEyeFinal;
        D3DXVec3Transform(&oEyeFinal, &m_oEyePos, &oTransformMatrix);
    
        m_oEyePos = D3DXVECTOR3(oEyeFinal.x, oEyeFinal.y, oEyeFinal.z) 
    
        // Increment rotation to keep the same relative look angles
        RotateXAxis(dX);
        RotateYAxis(dY);
        break;
    }
    

    关于c++ - 实现复杂的基于旋转的相机,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11470978/

    相关文章:

    C++ 将 double 转换为 char 并替换为 std::array

    c++ - 结合 Eigen 和 CppAD

    algorithm - 寻找 64 位以上数字的快速确定性素性测试

    java - 奇怪的数字

    javascript - 在 JavaScript 中拉伸(stretch)图形以填充 Canvas 元素

    python - 沿轴 0 重复一个 scipy csr 稀疏矩阵

    python - 矩阵的维度不等于相等

    c++ - 奇怪的 C++ 指针声明

    c++ - 需要 : C++ class for maintaining a 1-dimensional list of extents

    algorithm - 线性方程的快速近似解?