【问题标题】:How to calculate look at point to move the camera with the mouse in OpenGL/GLUT?如何计算在OpenGL/GLUT中用鼠标移动相机的点?
【发布时间】:2011-03-13 05:27:19
【问题描述】:

这会让我难以解释,所以请多多包涵。

我已经在我的相机类中实现了大多数类型的移动和旋转,一切都在使用键盘,现在我想实现鼠标。我像这样捕捉鼠标移动:

#define SENSITIVITY 25.0f

void main(void) {
    (...)
    glutPassiveMotionFunc(processPassiveMotion);    
    glutWarpPointer(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2);
    glutSetCursor(GLUT_CURSOR_NONE);
    (...)
}

void processPassiveMotion(int x, int y) {
    int centerX = WINDOW_WIDTH / 2;
    int centerY = WINDOW_HEIGHT / 2;

    int deltaX = -1 * (x - centerX);
    int deltaY = -1 * (y - centerY);

    if(deltaX != 0 || deltaY != 0) {
        mainCamera.Rotate(deltaX / SENSITIVITY, deltaY / SENSITIVITY);

        glutWarpPointer(centerX, centerY);
    }
}

在阅读完所有内容之后,我相信这对我的情况来说已经足够了。但是我必须声明,首先我尝试调用 Pitch()Yaw() 相机函数,但这是不行的,我必须创建一个额外的函数来“同时”旋转两个轴。

旋转功能是这样的:

#define DEG2RAD(a) (a * (M_PI / 180.0f))
#define SINDEG(a)  sin(DEG2RAD(a))
#define COSDEG(a)  cos(DEG2RAD(a))

void Camera::Rotate(GLfloat angleX, GLfloat angleY) {
    Reference = NormalizeVector(
        Reference * COSDEG(angleY) + UpVector * SINDEG(angleY)
    );

    Reference = NormalizeVector(
        Reference * COSDEG(angleX) - RightVector * SINDEG(angleX)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
    RightVector = CrossProduct(&Reference, &UpVector);
}

Reference 是查看方向,即相机正在查看的点。由于它是一个归一化向量,它从 -1.0 变为 1.0。该向量或点稍后会与另一个向量(Position,即相机位置)一起用于计算在gluLookAt 中使用的真实观察点,如下所示:

void Camera::LookAt(void) {
    Vector3D viewPoint = Position + Reference;

    gluLookAt(
        Position.x, Position.y, Position.z,
        viewPoint.x, viewPoint.y, viewPoint.z,
        UpVector.x, UpVector.y, UpVector.z
    );
}

上面的所有向量操作,如+-*,当然都是重载的。

现在我将尝试描述我的问题...

上面的旋转功能很好用,因为它使用鼠标正确地执行了俯仰和偏航。但是,这些轮换看起来不像第一人称射击游戏中的轮换。在那些游戏中,当一个人看着天空然后向左/向右看时,人们期望继续看天空。想象我们在一个球体内,这样的运动应该在球体的顶部“画”一个圆圈。

但这不是发生的事情,因为偏航不是这样做的。偏航运动将围绕任意轴旋转,我认为这是这种情况下的向上向量。所以,问题在于偏航运动,因为俯仰似乎工作正常。

换句话说,我上面的代码不能保持水平线,这就是必须发生的事情,因为在游戏中当一个人看着天空然后向左/向右看时,水平线总是水平的。我的代码不会发生同样的情况,我向上看,然后向左/向右看,地平线将全部扭曲。

我说得够清楚了吗?我不确定如何更好地解释这一点。 :( 希望这足以让任何人理解。

我不知道如何解决这个问题...如何在向上/向下看后正确地向左/向右看,同时保持水平线?

编辑:

我的旋转函数代码取自同样存在的 Yaw 和 Pitch 函数,因此我可以独立调用这些旋转。出于参考目的,我将在下面添加它们以及 Roll 功能(我可能永远不会使用,但如果我需要它,它就在那里):

void Camera::Pitch(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) + UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

void Camera::Yaw(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) - RightVector * SINDEG(angle)
    );

    RightVector = CrossProduct(&Reference, &UpVector);
}

void Camera::Roll(GLfloat angle) {
    RightVector = NormalizeVector(
        RightVector * COSDEG(angle) - UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

【问题讨论】:

    标签: opengl camera mouse move glut


    【解决方案1】:

    您的问题似乎在陈述中:

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
    

    在前面的语句中将 Reference 旋转到 RightVector 后,它们的叉积将不再产生为您提供水平地平线的 UpVector。用你的手臂试试。此外,Reference 和 RightVector 没有相隔 90 度,因此 UpVector 甚至都不是单位向量。 (最后,为了清楚起见,您实际上应该只切换叉积的顺序,而不是乘以 (-1)。)

    老实说,如果我这样做,我会采取不同的方法。我看不出两个旋转必须在一个函数中的任何合乎逻辑的原因。在使用向量时,我也会不惜一切代价避免显式正弦和余弦。我认为您真正需要的是Rotate About an Arbitrary Axis 的功能。如果不出意外,它非常很有用。幸运的是,所有细节都由默里先生照顾!如果你实现了这个功能,那么它就变得非常简单了。定义一个始终向上的常量SkyVector。然后在伪代码中,

    AxisRotation( Vector vec, Vector axis, float angle ) {
        Vector result;
    
        // The axis is assumed to be normalized:  
        //    (just make sure you're not modifying the original)
        axis = NormalizeVector( &axis );
    
        // expanded for clarity:
        float u = axis.x;
        float v = axis.y;
        float w = axis.z;
        float x = vec.x;
        float y = vec.y;
        float z = vec.z;
        float c = cos(angle);
        float s = sin(angle);
    
        // Apply the formula verbatim from the linked page:
        result.x = u*(u*x + v*y + w*z)*(1.-c) + x*c + (-w*y + v*z)*s;
        result.y = v*(u*x + v*y + w*z)*(1.-c) + y*c + ( w*x - u*z)*s;
        result.z = w*(u*x + v*y + w*z)*(1.-c) + z*c + (-v*x + u*y)*s;
    
        return result;
    }
    
    Yaw(angleX) {
        Reference = AxisRotation( &Reference, &SkyVector, angleX );
        RightVector = NormalizeVector( CrossProduct( &Reference, &SkyVector ) );
        UpVector = CrossProduct( &RightVector, &Reference );
    }
    
    Pitch(angleY) {
        Reference = AxisRotation( &Reference, &RightVector, angleY );
        //RightVector doesn't change!
        UpVector = CrossProduct( &RightVector, &Reference );
    }
    

    如果您逐个操作地执行该操作,那么它应该是有意义的。最后,我要补充一点,quaternions 确实是做这些事情的“正确”方法,避免使用gimbal lock,但我通常会做你所做的事情。您可能需要不时检查以确保您的向量保持良好和垂直。四元数更稳定。

    编辑:如果轴旋转功能过大,您仍然可以使用简单的向量和旋转矩阵来实现它。唯一的事情是你必须开始将东西投影到水平面上,这样你就可以独立地进行两次旋转而且它仍然需要一些正弦和余弦。您的时间可能最好花在实现轴旋转功能上!

    【讨论】:

    • 几点:1)老实告诉你,虽然我理解了这一切所涉及的一些概念,但我并没有自己实现上面的代码(codecolony.de/docs/camera2.htm ),这意味着我不确定我是否可以自己实现该功能。 2)两个旋转都在一个功能中,只是为了澄清,我有 3 个独立的功能(用于俯仰、偏航和滚动)。我已将它们添加到问题中,您认为它们是否也需要重做?
    • 3) 是的,我一直在阅读有关四元数的文章,但这对我来说太难实现了,我真的不想放弃我拥有的所有东西并走另一条路。至少现在不是,将来,如果这个问题对我来说真的很重要,我会深入研究它。 4)如果我确切地知道什么是 Gimbal Lock,我认为上面的代码可以防止这种情况发生。可能不是“正确”的方法,但它解决了问题。 4)您所说的“不时检查以确保您的向量保持良好和垂直”是什么意思?我应该检查什么、在哪里以及何时检查?
    • 当我说它们必须保持垂直时,我的意思是,如果你一遍又一遍地通过相同的变换独立旋转矢量,浮点错误可能会蔓延,并且它们可能会偏离垂直。 Dot(x,y)、dot(y,z) 和 dot(x,z) 应该非常接近于零。如果没有,您需要做一些交叉产品并重新垂直化它们。就像覆盖 z=cross(x,y),然后 y=cross(z,x) 一样简单。
    • 是的,是的,是的。它因为奇点而跳跃。您仍然应该小心,因为“偏航”不是真正的偏航,并且相机应该向上指向的约束与“滚动”相冲突。同样,我只能建议您考虑您想要的行为并尝试实现它。没有正确答案。 3D 建模师的要求不同于飞行模拟器的要求,也不同于第一人称射击游戏的要求。
    • 这就是我喜欢 stackoverflow 的原因……我想我自己不会找到这个。我试图做同样的事情,谢谢@Ricky!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多