如果我忽略四元数代数的肮脏细节,我想我理解旋转和平移变换背后的数学。但仍然无法理解我做错了什么。
为什么我的相机一劳永逸地滚动!? :)
更具体一点,我应该如何从其方向(旋转矩阵)计算相机视图矩阵?
我正在用Python编写一个简约的3D引擎,它有一个场景Node类,用于处理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相机正常运行,但旋转矩阵似乎不正确。
请有人能说清楚吗?提前致谢。
在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
关于在上一次屏幕截图中相机旋转矩阵和物体旋转矩阵如何不相同的说明:物体旋转和平移矩阵(一起是模型矩阵)描述了从物体坐标到世界坐标的变换,而视图矩阵描述了变换从世界坐标到相机坐标。
因此,为了使三脚架相对于视口显示轴对齐,则视图旋转矩阵必须是模型旋转矩阵的反转。
您不应该在一个rotationMatrix,directionVector或Quaternion中累积所有Euler角度。
做的事情如:
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