【问题标题】:Why is my FPS camera rolling, once and for all?为什么我的 FPS 相机一劳永逸地滚动?
【发布时间】:2019-03-11 16:21:46
【问题描述】:

如果我忽略四元数代数的肮脏细节,我想我理解旋转和平移变换背后的数学。但仍然无法理解我做错了什么。

为什么我的相机一劳永逸!? :)

更具体地说,我应该如何根据相机的方向(旋转矩阵)计算相机视图矩阵?

我正在用 Python 编写一个简约的 3d 引擎,其中包含一个场景节点类,用于处理 3d 对象的旋转和平移机制。它具有公开旋转和平移矩阵以及模型矩阵的方法。

还有一个 CameraNode 类,一个 Node 的子类,它也暴露了 View 和 Projection 矩阵(投影不是问题,所以我们可以忽略它)。

模型矩阵

为了正确应用转换,我将矩阵相乘如下:

PxVxM x v

即首先是模型,然后是视图,最后是投影。

其中 M 是通过首先应用旋转然后平移来计算的:

M = TxR

代码如下:

class Node():
    # ...

    def model_mat(self):
        return self.translation_mat() @ self.rotation_mat() @ self.scaling_mat()

    def translation_mat(self):
        translation = np.eye(4, dtype=np.float32)
        translation[:-1, -1] = self.position  # self.position is an ndarray
        return translation

    def rotation_mat(self):
        rotation = np.eye(4, dtype=np.float32)
        rotation[:-1, :-1] = qua.as_rotation_matrix(self.orientation)  # self.orientation is a quaternion object
        return rotation

查看矩阵

我正在根据相机位置和方向计算 View 矩阵,如下所示:

class CameraNode(Node):
    # ...

    def view_mat(self):
        trans = self.translation_mat()
        rot = self.rotation_mat()
        trans[:-1, 3] = -trans[:-1, 3]  # <-- efficient matrix inversion
        rot = rot.T                     # <-- efficient matrix inversion
        self.view = rot @ trans
        return self.view

如果我错了,请纠正我。由于我们只能移动和旋转世界几何体(而不是移动/旋转相机),我必须以相反的顺序乘以矩阵以及相反的变换(实际上是每个变换矩阵的逆)。换句话说,将相机从物体上移开也可以看作是把物体从相机上移开。

旋转输入

现在,这是我将键盘输入转换为相机旋转的方法。当我按下右/左/上/下箭头键时,我正在使用一些俯仰/偏航角度调用以下方法:

def rotate_in_xx(self, pitch):
    rot = qua.from_rotation_vector((pitch, 0.0, 0.0))
    self.orientation *= rot

def rotate_in_yy(self, yaw):
    rot = qua.from_rotation_vector((0.0, yaw, 0.0))
    self.orientation *= rot

行为错误但旋转矩阵正确

这就是我得到的:

现在,令人困惑的是,如果我将上述方法更改为:

class CameraNode(Node):

    def view_mat(self):
        view = np.eye(4)
        trans = self.translation_mat()
        rot = self.rotation_mat()
        trans[:-1, 3] = -trans[:-1, 3]
        # rot = rot.T                     # <-- COMMENTED OUT
        self.view = rot @ trans
        return self.view

    def rotate_in_xx(self, pitch):
        rot = qua.from_rotation_vector((pitch, 0.0, 0.0))
        self.orientation = rot * self.orientation  # <-- CHANGE

我可以让相机像 FPS 相机一样正常工作,但旋转矩阵似乎不正确。

请有人能解释一下吗? 提前致谢。

【问题讨论】:

  • 在你的最后一个例子中,你只处理音高,如果你在第一个例子中只保留音高,你有同样的结果吗?我不认为你想以你正在做的方式链接你的旋转(俯仰然后偏航)玩ctralie.com/Teaching/COMPSCI290/Materials/EulerAnglesViz。那你可能想看看弧线球
  • @Draykoon,感谢您的评论。如果您的建议是:“而不是将self.orientation = rot * self.orientation 设置在def rotate_in_xx(self, pitch) 内部self.orientation = rot * self.orientation 内部def rotate_in_yy(self, yaw)”那么我向您脱帽致敬。谢谢,这解决了我的问题:)))
  • 但是我不完全确定我理解为什么这能解决问题。我相信一旦我添加更多复杂性,这个问题或类似问题最终会再次困扰我,例如添加我现在还没有真正考虑过的子节点。
  • This answer 可能会帮助您理解问题。
  • 所以现在你只处理偏航,对吧?你可能想将你的俯仰和偏航存储在 2 个单独的变量上并设置 self.orientation = quat.fromEuler(m_pitch, m_yaw, 0.0f) * m_initialForyardVector 你可能不想做一些“* =”因为在下一帧你的新偏航会影响前滚等......跨度>

标签: python opengl graphics rotation quaternions


【解决方案1】:

my last answer 你的问题中,我告诉你为什么重复使用你的视图矩阵不是一个好主意,因为俯仰和偏航不会通勤。您现在正在使用四元数,但同样,俯仰和偏航四元数不会通勤。只需存储俯仰值和偏航值,然后重新计算 随时根据需要进行俯仰和偏航的方向。

def rotate_in_xx(self, pitch):
    self.pitch += pitch

def rotate_in_yy(self, yaw):
    self.yaw += yaw

def get_orientation():
    pitch_rotation = qua.from_rotation_vector((self.pitch, 0.0, 0.0))
    yaw_rotation = qua.from_rotation_vector((0.0, self.yaw, 0.0))
    return yaw_rotation * pitch_rotation

关于上一个屏幕截图中相机旋转矩阵和对象旋转矩阵的不同之处的说明:对象旋转和平移矩阵(连同模型矩阵)描述了从对象坐标世界坐标,而视图矩阵描述了从世界坐标相机坐标的转换。

因此,为了让三脚架相对于您的视口轴对齐显示,那么视图旋转矩阵必须是模型旋转矩阵的

【讨论】:

  • 你为什么说我在重用视图矩阵?我正在使用四元数来保持我的节点方向。在需要时从四元数计算视图矩阵。我不认为错误累积是这里的问题。这个问题与我之前的问题不同,因为现在我使用的是四元数,而不是保持欧拉角。
  • 在我上一张截图中,相机旋转矩阵和物体旋转矩阵并不完全相同,我在图像本身中以绿色消息的形式说明了这一点。这就是发布这个问题的全部原因。换句话说,第二个示例表明我能够实现 FPS 相机行为,但方向矩阵不正确,我表明将相机旋转矩阵与受到相同旋转的参考对象进行比较。
  • 视图矩阵在def view_mat() 内部计算,我分别用trans[:-1, 3] = -trans[:-1, 3]rot = rot.T 反转平移和旋转矩阵。
  • 对,你的四元数不是一个视图矩阵,但是对应于俯仰和偏航的四元数也不通勤(i*j != j*i),所以你会遇到完全相同的问题和以前一样。
  • 我没有使用每个俯仰、偏航和角度的四元数,我使用单个四元数来表示节点方向。然后我根据用户输入生成旋转四元数,然后应用(乘)相应的四元数。四元数乘法也不是可交换的,我知道这一点。这就是为什么在我的第二个示例中,相机的行为符合预期。问题是旋转矩阵不行。但是@Draykoon 相关的建议似乎已经解决了这个问题。
【解决方案2】:

您不应该将所有欧拉角累积在一个旋转矩阵、方向向量或四元数中。

做这样的事情:

vec3 euler = vec3(yaw, pitch, roll);
rot *= quaternion(euler)
or
self.orientation = quaternion(euler) * self.orientation

您为存储在结构中的现有旋转添加一个旋转的每一帧。

float deltaYaw = getInput();
float deltaPitch = getInput();
m_rotation = m_rotation * euler(deltaYaw, deltaRoll, 0.0f) 
is not equal to
m_rotation = rotationMatrix(m_yaw + deltaYaw, m_pitch + deltaRoll, 0.0f);

在一种情况下,您使用 deltaYaw 使用新的 3D 框架旋转已旋转的对象。您现在应用的偏航将考虑您之前所做的滚动。

oldYaw * oldRoll * deltaYaw != (oldYaw * deltaYaw) * oldRoll

另一方面,您构建从网格位置到请求的欧拉角的旋转。

是的,你是对的,这不是一种方便的相机处理方式,保持偏航、俯仰和滚动变量会导致以后出现问题(gimBall 锁定,相机动画会变得棘手......)。我建议看看 arcBall 相机https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Arcball

【讨论】:

  • 澄清一下,这是在回答“为什么我的相机在滚动?”这个问题。或者只是给出一些建议“请注意,如果您长时间运行模拟,您可能会遇到少量滚动,这不是您现在遇到滚动的罪魁祸首”?我之所以问,是因为在我做出更改之后(请参阅我对您的第一条评论的评论),事情开始按我的预期工作,即相机的行为就像 FPS 相机,旋转矩阵似乎是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-25
  • 2016-07-20
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-13
相关资源
最近更新 更多