如何正确表示游戏中的3D旋转

时间:2014-10-20 17:46:58

标签: opengl math 3d rotation quaternions

在大多数3D平台游戏中,只需围绕Y轴旋转,因为玩家总是直立。

但是,对于需要在所有轴上旋转玩家的3D太空游戏,表示旋转的最佳方式是什么?

我首先尝试使用欧拉角:

glRotatef(anglex, 1.0f, 0.0f, 0.0f);
glRotatef(angley, 0.0f, 1.0f, 0.0f);
glRotatef(anglez, 0.0f, 0.0f, 1.0f);

我采用这种方法的问题是每次旋转后,轴都会发生变化。例如,当anglex和angley为0时,anglez使船绕其机翼旋转,但是如果anglex或angley不为零,则不再如此。我希望anglez始终围绕翅膀旋转,与anglex和angley无关。

我读到四元数可以用来展示这种期望的行为,但是在实践中无法实现它。

我认为我的问题是由于我基本上仍在使用欧拉角,但在使用前将旋转转换为四元数表示。

struct quaternion q = eulerToQuaternion(anglex, angley, anglez);
struct matrix m = quaternionToMatrix(q);
glMultMatrix(&m);

但是,如果直接存储每个X,Y和Z角度不正确,当我的旋转存储为四元数时,如何说“将机翼(或任何一致轴)绕船旋转1度”? / p>

此外,我希望能够以旋转的角度转换模型。假设我只有q.x,q.y,q.z和q.w的四元数,我该如何移动它?

1 个答案:

答案 0 :(得分:2)

四元数是表示旋转的非常好的方法,因为它们是有效的,但我更喜欢用4x4矩阵表示完整状态“位置和方向”。

因此,假设您为场景中的每个对象都有一个4x4矩阵。最初,当对象未旋转且未经过处理时,此矩阵是单位矩阵,这就是我将其称为“原始状态”。例如,假设您的船头指向原始状态的-z,因此沿z轴旋转船舶的旋转矩阵为:

Matrix4 around_z(radian angle) {
    c = cos(angle);
    s = sin(angle);
    return Matrix4(c, -s, 0, 0,
            s, c, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1);
}

现在,如果你的船在太空中的任何地方并且向任何方向旋转,并且让我们调用此状态t,如果你想围绕z轴旋转angle量的船,就好像它一样处于“原始状态”,它将是:

t = t * around_z(angle);

使用OpenGL进行绘制时,t是您为该船的每个顶点相乘的内容。这假设你正在使用列向量(就像OpenGL那样),并且要注意OpenGL中的矩阵首先是存储列。

基本上,您的问题似乎与您应用旋转的顺序有关。参见,四元数和矩阵乘法是非交换的。所以,如果相反,你写:

t = around_z(angle) * t;

您将around_z旋转不应用于“原始状态”z,而是应用于全局坐标z,船舶已受初始转换影响(已转换已翻译) 。当您调用glRotateglTranslate函数时,这是相同的。他们被称为重要的顺序。

针对您的问题稍微具体一点:您拥有绝对翻译trans,以及围绕其中心rot的轮播。您可以使用以下内容更新场景中的每个对象:

void update(quaternion delta_rot, vector delta_trans) {
    rot = rot * delta_rot;
    trans = trans + rot.apply(delta_trans);
}

delta_rotdelta_trans都以相对于原始状态的坐标表示,所以,如果你想推进你的船只前进0.5个单位,你的delta_trans将是(0, 0,-0.5)。要绘制,它将类似于:

void draw() {
    // Apply the absolute translation first
    glLoadIdentity();
    glTranslatevf(&trans);

    // Apply the absolute rotation last
    struct matrix m = quaternionToMatrix(q);
    glMultMatrix(&m);

    // This sequence is equivalent to:
    // final_vertex_position = translation_matrix * rotation_matrix * vertex;

    // ... draw stuff
}

通过阅读glTranslateglMultMatrix的手册,我选择的通话顺序,以保证应用转换的顺序。

关于rot.apply()

正如维基百科文章Quaternions and spatial rotation所述,要在向量q上应用四元数p描述的轮播,它将是rp = q * p * q^(-1),其中rp是新旋转的矢量。如果你在游戏中实现了一个有效的四元数库,你应该已经实现了这个操作,或者现在应该实现它,因为这是使用四元数作为旋转的核心。

例如,如果你有一个描述90°(0,0,1)左右旋转的四元数,如果你将它应用于(1,0,0),你将得到向量(0,1, 0),即你有四元数旋转的原始矢量。这相当于将四元数转换为矩阵,并将矩阵转换为柱形矢量乘法(通过矩阵乘法规则,它产生另一个列矢量,旋转矢量)。