Glm四元数lookat函数

3

我正在尝试编写一个使用glm::quat表示旋转的lookat函数,参考了这个答案。然而,我遇到了获取正确角度的问题。这是我的lookat函数:

void Camera::LookAt(float x, float y, float z) {
    glm::vec3 lookVector = glm::vec3(x, y, z);
    assert(lookVector != position);

    glm::vec3 direction = glm::normalize(lookVector-position);
    float dot = glm::dot(glm::vec3(0, 0, -1), direction);
    if (fabs(dot - (-1.0f)) < 0.000001f)
        rotation = glm::quat(RadiansToDegrees(M_PI), 0.0f, 1.0f, 0.0f);
    if (fabs(dot - (1.0f)) < 0.000001f)
        rotation = glm::quat();

    float angle = RadiansToDegrees(acosf(dot));

    glm::vec3 cross = (glm::cross(glm::vec3(0, 0, -1), direction));
    rotation = glm::normalize(glm::angleAxis(angle, cross));

    std::cout << glm::eulerAngles(rotation).x  << " " << glm::eulerAngles(rotation).y << " " << glm::eulerAngles(rotation).z << "\n";
}

当我的相机位于(0.0f, 0.0f, -10.0f)时,调用LookAt(0.0f, 0.0f, 0.0f)会输出正确的旋转角度0,0,0。但是,如果我将相机平移至(0.0f, -0.01f, -10.0f)或更多,则得到约为124,0,0的旋转角度。如果我继续将y平移-0.01f,这个值就会减小。如果我不对四元数进行归一化,就不会出现这个问题。旋转仍然是关于x轴的124,但外观看起来正常。但是,如果稍后对四元数进行归一化,它又会再次旋转到约124。我无法对cross进行归一化,因为这样会引发一个断言。是什么导致我从我的lookat函数中获得关于x轴旋转124的欧拉角,如何修复它?

正如GLM文档所述,glm::angleAxis的轴必须被标准化,而在您的情况下它没有被标准化。当您尝试标准化cross时会抛出什么样的断言?只要direction不与(0, 0, -1)平行,就不应该有问题。 - Nico Schertler
啊.. 在这种情况下,dot == -1 并且 rotation 已经被设置。然而,你尝试重新计算它,这是没有意义的。所以只需添加一个 else 块即可。 - Nico Schertler
现在旋转是关于(180, 0, 0)的。这仍然没有看向(0,0,0) - jbills
这就是你在第一个案例中定义的。你可能想再次使用 angleAxis() 而不是纯构造函数。 - Nico Schertler
我认为答案中提到的纯构造函数就是该人所指。在if语句中,他返回了一个纯构造函数四元数,而对于其他情况下则返回等同于angleAxis()的值。无论如何,如果我使用angleAxis,我得到的结果是(180, 0, 180) - jbills
显示剩余2条评论
2个回答

8

自版本0.9.9.0起,在<glm/gtc/quaternion.hpp>中有一个函数,它可以做大部分你想要的事情:

template<typename T, qualifier Q>
tquat<T, Q> quatLookAt(vec<3, T, Q> const& direction, vec<3, T, Q> const& up);

这是由这个拉取请求添加的功能,并于2017年7月24日合并到主分支中。

但是:

  1. 方向必须是一个标准化向量!
  2. 方向不能与上方平行!

因此,您可能希望编写一个更安全的函数封装:

glm::quat safeQuatLookAt(
    glm::vec3 const& lookFrom,
    glm::vec3 const& lookTo,
    glm::vec3 const& up,
    glm::vec3 const& alternativeUp)
{
    glm::vec3  direction       = lookTo - lookFrom;
    float      directionLength = glm::length(direction);

    // Check if the direction is valid; Also deals with NaN
    if(!(directionLength > 0.0001))
        return glm::quat(1, 0, 0, 0); // Just return identity

    // Normalize direction
    direction /= directionLength;

    // Is the normal up (nearly) parallel to direction?
    if(glm::abs(glm::dot(direction, up)) > .9999f) {
        // Use alternative up
        return glm::quatLookAt(direction, alternativeUp);
    }
    else {
        return glm::quatLookAt(direction, up);
    }
}

如果方向和上向量平行,那么备选的上向量应该是什么? - Tom
1
@TomH实际上可以使用任何不平行于上方向量。如果我认为“上”和“方向”很少会变成平行的话,通常只会使用沿着X轴或Z轴的向量。对于游戏角色来说,指向角色背面的向量是个不错的选择,因为“alternativeUp”基本上是当他们直视向上时头顶部指向的方向。 - Benno Straub

2
我已经用以下代码解决了问题:
void Camera::LookAt(float x, float y, float z) {
    glm::vec3 lookVector = glm::vec3(x, y, z);
    assert(lookVector != position);

    glm::vec3 direction = glm::normalize(lookVector-position);
    float dot = glm::dot(glm::vec3(0, 0, 1), direction);
    if (fabs(dot - (-1.0f)) < 0.000001f) {
        rotation = glm::angleAxis(RadiansToDegrees(M_PI), glm::vec3(0, 1, 0));
        return;
    }
    else if (fabs(dot - (1.0f)) < 0.000001f) {
        rotation = glm::quat();
        return;
    }

    float angle = -RadiansToDegrees(acosf(dot));

    glm::vec3 cross = glm::normalize(glm::cross(glm::vec3(0, 0, 1), direction));
    rotation = glm::normalize(glm::angleAxis(angle, cross));
}

然而,我不明白为什么要在angle上加负号。它解决了我最后的问题,但如果能解释一下这样做的数学原理会更有帮助。


这是因为你的轴指向错误的方向。你也可以这样做:cross = glm::normalize(glm::cross(direction, glm::vec3(0, 0, 1)))。顺便说一下,glm::angleAxis() 可能已经返回了一个标准化的四元数。 - Nico Schertler

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