如何在现代opengl中正确实现四元数相机

How to correctly implement a quaternion camera in modern opengl?

本文关键字:实现 四元数 相机 opengl      更新时间:2023-10-16

我试图在opengl中创建一个基于uvn四元数的相机,使用了下面列出的各种教程,并阅读了四元数和轴角旋转。我留下了一个奇怪的错误,我似乎无法修复。

基本上相机似乎工作得很好,直到相机从+z旋转约45度,在这一点上倾斜相机向上或向下似乎倾斜相机围绕其目标轴,转动向上矢量。

当相机沿-z方向向上或向下倾斜时,会产生相反的错觉,向上向下倾斜,向下向上倾斜。

我已经看到其他的实现建议使用非uvn系统,其中四元数累积成一个描述当前方向的delta从一些任意的开始角度。这听起来很棒,但是我似乎不能确切地知道我将如何实现这一点,特别是从这个到视图矩阵的转换。

在SO的其他地方,我读到关于将旋转分成两个四元数分别代表偏航和俯仰,但我不相信这是问题的原因,因为在这种情况下,如果我错了,请纠正我,但我的理解是,你应用这两个旋转的顺序并不重要。

相关源代码片段:

Quarternion操作
Quaternion<TValue> conjugate() const{
            return Quaternion({ { -m_values[X], -m_values[Y], -m_values[Z], m_values[W] } });
};
Quaternion<TValue>& operator*=(const Quaternion<TValue>& rhs) {
        TValue x, y, z, w;
            w = rhs[W] * m_values[W] - rhs[X] * m_values[X] - rhs[Y] * m_values[Y] - rhs[Z] * m_values[Z];
            x = rhs[W] * m_values[X] + rhs[X] * m_values[W] - rhs[Y] * m_values[Z] + rhs[Z] * m_values[Y];
            y = rhs[W] * m_values[Y] + rhs[X] * m_values[Z] + rhs[Y] * m_values[W] - rhs[Z] * m_values[X];
            z = rhs[W] * m_values[Z] - rhs[X] * m_values[Y] + rhs[Y] * m_values[X] + rhs[Z] * m_values[W];          
            m_values[X] = x;
            m_values[Y] = y;
            m_values[Z] = z;
            m_values[W] = w;
            return *this;
};
static Quaternion<TValue> rotation(Vector<3, TValue> axis, TValue angle){
        float x, y, z, w;
        TValue halfTheta = angle / 2.0f;
        TValue sinHalfTheta = sin(halfTheta);
        return Quaternion<TValue>({ { axis[X] * sinHalfTheta, axis[Y] * sinHalfTheta, axis[Z] * sinHalfTheta, cos(halfTheta) } });
};

矢量旋转操作

Vector<dimensions, TValue> rotate(const Vector<3, TValue> axis, float angle){
        Quaternion<TValue> R = Quaternion<TValue>::rotation(axis, angle);
        Quaternion<TValue> V = (*this);
        Vector<dimensions, TValue> result = R * V * R.conjugate();
        return result;
}

照相机方法

Camera::Camera(Vector<2, int> windowSize, float fov, float near, float far):
m_uvn(Matrix<4, float>::identity()),
m_translation(Matrix<4, float>::identity()),
m_ar(windowSize[Dimensions::X] / (float)windowSize[Dimensions::Y]),
m_fov(fov),
m_near(near),
m_far(far),
m_position(),
m_forward({ { 0, 0, 1 } }),
m_up({ { 0, 1, 0 } })
{
    setViewMatrix(Matrix<4, float>::identity());
    setProjectionMatrix(Matrix<4, float>::perspective(m_ar, m_near, m_far, m_fov));
};
Matrix<4, float> Camera::getVPMatrix() const{
    return m_vp;
};
const Vector<3, float> Camera::globalY = Vector<3, float>({ { 0, 1, 0 } });
void Camera::setProjectionMatrix(const Matrix<4, float> p){
    m_projection = p;
    m_vp = m_projection * m_view;
};
void Camera::setViewMatrix(const Matrix<4, float> v){
    m_view = v;
    m_vp = m_projection * m_view;
};
void Camera::setTranslationMatrix(const Matrix<4, float> t){
    m_translation = t;
    setViewMatrix(m_uvn * m_translation);
}
void Camera::setPosition(Vector<3, float> position){
    if (position != m_position){
        m_position = position;
        setTranslationMatrix(Matrix<4, float>::translation(-position));
    }
};
void Camera::moveForward(float ammount){
    setPosition(m_position + (m_forward * ammount));
}
void Camera::moveRight(float ammount){
    setPosition(m_position + (getRight() * ammount));
}
void Camera::moveUp(float ammount){
    setPosition(m_position + (m_up * ammount));
}
void Camera::setLookAt(Vector<3, float> target, Vector<3, float> up){
    Vector<3, float> newUp = up.normalize();
    Vector<3, float> newForward = target.normalize();
    if (newUp != m_up || newForward != m_forward){
        m_up = newUp;
        m_forward = newForward;
        Vector<3, float> newLeft = getLeft();
        m_up = newLeft * m_forward;
        m_uvn = generateUVN();
        setViewMatrix(m_uvn * m_translation);
    }
};
void Camera::rotateX(float angle){
    Vector<3, float> hAxis = (globalY * m_forward).normalize();
    m_forward = m_forward.rotate(hAxis, angle).normalize();
    m_up = (m_forward * hAxis).normalize();
    m_uvn = generateUVN();
    setViewMatrix(m_translation * m_uvn);
}
void Camera::rotateY(float angle){
    Vector<3, float> hAxis = (globalY * m_forward).normalize();
    m_forward = m_forward.rotate(globalY, angle).normalize();
    m_up = (m_forward * hAxis).normalize();
    m_uvn = generateUVN();
    setViewMatrix(m_translation * m_uvn);
}
Vector<3, float> Camera::getRight(){
    return (m_forward * m_up).normalize();
}
Vector <3, float> Camera::getLeft(){
    return (m_up * m_forward).normalize();
}

};

我猜问题在于我的四元数的实现或我使用它的方式,但由于系统的复杂性,我似乎无法进一步确定问题。由于遇到了奇怪的bug,我不确定是否只是我试图实现相机的方式有问题?

教程
  • https://www.youtube.com/watch?v=1Aw1PDu33PI
  • http://www.gamedev.net/page/resources/_/technical/math-and-physics/a-simple-quaternion-based-camera-r1997

Quarternion/矢量数学

  • http://mathworld.wolfram.com/Quaternion.html
  • https://en.wikipedia.org/wiki/Cross_product
  • http://ogldev.atspace.co.uk/www/tutorial13/tutorial13.html
  • http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm

老问题,但仍然是一个相关的话题,所以我给一些提示。要记住的是,四元数是一个三维地图。单位四元数,在较小程度上是纯四元数,提供了一种一致的方式来表示这个旋转是这个值。与欧拉角相比,它的好处是它们试图通过围绕某个轴的旋转来重建方向,其中四元数直接与方向相关并且可以避免万向节锁定

具体来说,对于一个四元数相机,设Q{1,0,0,0},其中w=1,则这个四元数对应的矩阵是单位矩阵。对于任何有效的单位四元数,当分解成3x3矩阵时,你(通常)会得到相机的世界空间旋转。然而,你甚至不需要这些,因为你可以定义你的相机空间,比如X{1,0,0} Y{0,1,0} Z{0,0,-1}然后将这些单位轴乘以你的相机方向四元数,结果向量就是转换后的单位向量。这些然后创建你的右向上和前矢量,可以用来建立3 × 3旋转的视图变换。

移动相机应该相对笔直地向前。线性运动向量可以很容易地重建并应用于摄像机位置,角运动可以通过将摄像机四元数与旋转发生的相应平面的法线方向相乘来实现。例如,左右旋转是发生在XZ平面上的旋转因为Y向量{作为单位四元数而不是纯四元数}将与相机四元数相乘产生所需的旋转效果