将glm :: lookat矩阵转换为四元数并返回

时间:2013-08-09 16:55:13

标签: c++ opengl quaternions glm-math

我正在使用glm创建一个相机类,我遇到了一些lookat函数的问题。我使用四元数来表示旋转,但我想使用glm的预先编写的lookat函数来避免重复代码。这是我的lookat功能:

void Camera::LookAt(float x, float y, float z) {
    glm::mat4 lookMat = glm::lookAt(position, glm::vec3(x, y, z), glm::vec3(0, 1, 0));
    rotation = glm::toQuat(lookMat);
}

但是当我拨打LookAt(0.0f,0.0f,0.0f)时,我的相机没有旋转到那一点。当我在lookat调用之后调用glm::eulerangles(rotation)时,我得到一个带有以下值的vec3:(180.0f,0.0f,180.0f)。 position是(0.0f,0.0f,-10.0f),所以我根本不应该有任何轮换来查看0,0,0。这是构建视图矩阵的函数:

glm::mat4 Camera::GetView() {
    view = glm::toMat4(rotation) * glm::translate(glm::mat4(), position);
    return view;
}

为什么我没有获得正确的四元数,以及如何修复代码?

5 个答案:

答案 0 :(得分:6)

<强>解决方案:

你必须通过结合它来反转四元数的旋转:

using namespace glm;

quat orientation = conjugate(toQuat(lookAt(vecA, vecB, up)));

<小时/> 的说明:

lookAt函数是gluLookAt的替代,用于构造view matrix

视图矩阵用于围绕观察者旋转世界,因此是相机变换的反转。

通过取倒数的倒数,可以得到实际的变换。

答案 1 :(得分:1)

我碰到了类似的东西,简短的回答是你的lookMat可能需要倒置/转置,因为它是一个相机旋转(至少在我的情况下),而不是世界旋转。旋转世界将是相机旋转的反转。

我有一个m_current_quat,它是一个存储当前相机旋转的四元数。我通过打印出glm :: lookAt生成的矩阵来调试问题,并通过应用m_current_quat和m_camera_position的转换来比较得到的矩阵。这是我测试的相关代码。

void PrintMatrix(const GLfloat m[16], const string &str)
{
    printf("%s:\n", str.c_str());

    for (int i=0; i<4; i++)
    {
        printf("[");
        //for (int j=i*4+0; j<i*4+4; j++)   // row major, 0, 1, 2, 3
        for (int j=i+0; j<16; j+=4) // OpenGL is column major by default, 0, 4, 8, 12
        {
            //printf("%d, ", j);            // print matrix index
            printf("%.2f, ", m[j]);

        }
        printf("]\n");
    }
    printf("\n");
}

void CameraQuaternion::SetLookAt(glm::vec3 look_at)
{
    m_camera_look_at = look_at;

    // update the initial camera direction and up
    //m_initial_camera_direction = glm::normalize(m_camera_look_at - m_camera_position);
    //glm::vec3 initial_right_vector = glm::cross(m_initial_camera_direction, glm::vec3(0, 1, 0));
    //m_initial_camera_up = glm::cross(initial_right_vector, m_initial_camera_direction);

    m_camera_direction = glm::normalize(m_camera_look_at - m_camera_position);
    glm::vec3 right_vector = glm::cross(m_camera_direction, glm::vec3(0, 1, 0));
    m_camera_up = glm::cross(right_vector, m_camera_direction);


    glm::mat4 lookat_matrix = glm::lookAt(m_camera_position, m_camera_look_at, m_camera_up);

    // Note: m_current_quat quat stores the camera rotation with respect to the camera space
    // The lookat_matrix produces a transformation for world space, where we rotate the world
    // with the camera at the origin
    // Our m_current_quat need to be an inverse, which is accompolished by transposing the lookat_matrix
    // since the rotation matrix is orthonormal.
    m_current_quat = glm::toQuat(glm::transpose(lookat_matrix));    

    // Testing: Make sure our model view matrix after gluLookAt, glmLookAt, and m_current_quat agrees
    GLfloat current_model_view_matrix[16];              

    //Test 1: gluLookAt
    gluLookAt(m_camera_position.x, m_camera_position.y, m_camera_position.z,
                m_camera_look_at.x, m_camera_look_at.y, m_camera_look_at.z,
                m_camera_up.x, m_camera_up.y, m_camera_up.z);       
    glGetFloatv(GL_MODELVIEW_MATRIX, current_model_view_matrix);                        
    PrintMatrix(current_model_view_matrix, "Model view after gluLookAt");   

    //Test 2: glm::lookAt
    lookat_matrix = glm::lookAt(m_camera_position, m_camera_look_at, m_camera_up);
    PrintMatrix(glm::value_ptr(lookat_matrix), "Model view after glm::lookAt");

    //Test 3: m_current_quat
    glLoadIdentity();
    glMultMatrixf( glm::value_ptr( glm::transpose(glm::mat4_cast(m_current_quat))) );
    glTranslatef(-m_camera_position.x, -m_camera_position.y, -m_camera_position.z);
    glGetFloatv(GL_MODELVIEW_MATRIX, current_model_view_matrix);                        
    PrintMatrix(current_model_view_matrix, "Model view after quaternion transform");    

    return;
}

希望这有帮助。

答案 2 :(得分:0)

  

我想使用glm的预先编写的lookat函数来避免重复代码。

但它重复代码。来自glm::lookat的矩阵只是mat4。通过从四元数到3个向量的转换,只有glm::lookat可以将其转换回方向只是浪费时间。你已经完成了lookat工作的85%;做其余的事。

答案 3 :(得分:0)

正在获得(或更好: a )正确的轮播。

  

当我在外观电话后拨打glm::eulerangles(rotation)时,我得到了一个   vec3具有以下值:(180.0f,0.0f,180.0f)。 position是   (0.0f,0.0f,-10.0f),所以我根本不应该有任何轮换   在0,0,0。

glm遵循旧固定功能GL的惯例。在那里,眼睛空间被定义为放置在原点的相机,向右x指向,y向上并向 -z 方向看。由于您希望以正z方向查看,因此相机必须转动。现在,作为人类,我会将其描述为围绕y旋转180度,但围绕x旋转180度并结合z周围的另一个180度旋转将也有同样的效果。

答案 4 :(得分:0)

乘以LookAt 视图矩阵后,世界空间矢量被旋转(引出)到摄像机的视图中在相机方向保持不变的情况下。

因此,通过矩阵将摄像机的实际旋转角度向右移向右旋转45度,该矩阵向世界所有空间的向左旋转45度顶点。

对于Camera对象,您需要获取其 local forwardup方向矢量,以便计算lookAt视图矩阵

viewMatrix = glm::lookAtLH (position, position + camera_forward, camera_up);

在使用四元数来存储对象(无论是照相机还是其他物体)的方向时,通常将此rotation quat用于计算定义其局部空间的向量(在以下示例中为左手):

glm::vec3 camera_forward = rotation * glm::vec3(0,0,1); // +Z is forward direction
glm::vec3 camera_right = rotation * glm::vec3(1,0,0); // +X is right direction
glm::vec3 camera_up = rotation * glm::vec3(0,1,0); // +Y is up direction

因此,应将世界空间方向旋转45度到,以反映相机的正确方向。

这就是为什么lookMat或从其获得的quat不能直接用于此目的的原因,因为它们描述的方向是相反的。

正确的旋转方式有两种:

  • 计算lookAt矩阵的逆并将该旋转矩阵乘以世界空间方向向量
  • (效率更高)将LookAt矩阵转换为四元数,并对其进行共轭而不是应用glm::inverse,因为结果是单位四元,并且对于此类四元,其逆等于共轭的。

您的LookAt应该如下所示:

void Camera::LookAt(float x, float y, float z) {
    glm::mat4 lookMat = glm::lookAt(position, glm::vec3(x, y, z), glm::vec3(0, 1, 0));
    rotation = glm::conjugate( glm::quat_cast(lookMat));
}