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

10

我正在使用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;
}

为什么我没有得到正确的四元数,该怎么修复我的代码?


这是一篇旧帖子,我曾经卡在同样的问题上。根据https://research.ncl.ac.uk/game/mastersdegree/graphicsforgames/theviewmatrix/Tutorial%202%20-%20The%20View%20Matrix.pdf第2页和第6页,您应该使用-position,并使用更新的glm编写glm :: mat4(1.0f)来表示身份。因此:glm :: translate(glm :: mat4(1.0f),-position)用于Camera :: GetView()。当执行A * B glm时,我还没有理解的是似乎计算的是B * A。 - Lecrapouille
关于为什么 A * B 实际上是在做 B * A,我想对自己说一句。OpenGL 的约定是 M * x,其中 M 是矩阵,x 是列向量。内部使用的是转置矩阵(列主序),因此 (M * x)' 是 x' * M',其中 ' 是转置运算符。据我所知,他们仍然希望遵循 M * x 约定,因此他们的 * 进行了转置。 - Lecrapouille
5个回答

8

解决方案:

您需要通过共轭来反转四元数的旋转:

using namespace glm;

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


说明:

lookAt函数是gluLookAt的替代品,用于构建视图矩阵

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

通过取反转的逆反转,您可以获得实际的变换。


点赞,刚帮我把一个令人害怕的方向向量转换为正确的四元数。 - Avi

2
我遇到过类似的问题,简短地说,你的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;
}

希望这能帮到你。

0

当乘以LookAt 视图矩阵时,世界空间向量会被旋转(带入)到相机的视图中,同时保持相机的方向不变

因此,通过应用一个45度向的矩阵来对所有世界空间顶点进行旋转,实际上可以将相机旋转45度向右

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

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

当使用四元数来存储物体的方向(无论是相机还是其他任何东西)时,通常使用这个rotation四元数来计算定义其本地空间(在下面的示例中为左手坐标系)的向量:

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或从中获得的四元数不能直接用于此目的的原因,因为它们描述的方向是相反的。

可以通过两种方式进行正确的旋转:

  • 计算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));
}

0

你已经得到了(或者更好的说法是:一个)正确的旋转。

当我在lookat调用之后调用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度将产生相同的效果。


0
我想使用glm预先编写的lookat函数来避免重复代码。
但这并不是重复代码。从glm :: lookat中得出的矩阵只是一个mat4。通过将四元数转换为3个向量,只是为了让glm :: lookat将其转换回方向,这只是浪费时间。您已经完成了lookat 85%的工作;只需完成剩下的部分即可。

1
这确实不是一个令人满意的答案,因为实际上可能有保留四元数的原因。当有人问如何生成lookat四元数时,说“只需使用矩阵”并不是一个回答。 - opatut
@opatut:我并没有说“只使用矩阵”。我的意思是,如果你可以直接使用四元数,那么将四元数转换为3个look-at向量仅仅为了生成矩阵是没有意义的。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接