首先,如果您想了解GLM lookAt算法的解释,请查看此问题的答案:https://stackoverflow.com/a/19740748/1525061
mat4x4 lookAt(vec3 const & eye, vec3 const & center, vec3 const & up)
{
vec3 f = normalize(center - eye);
vec3 u = normalize(up);
vec3 s = normalize(cross(f, u));
u = cross(s, f);
mat4x4 Result(1);
Result[0][0] = s.x;
Result[1][0] = s.y;
Result[2][0] = s.z;
Result[0][1] = u.x;
Result[1][1] = u.y;
Result[2][1] = u.z;
Result[0][2] =-f.x;
Result[1][2] =-f.y;
Result[2][2] =-f.z;
Result[3][0] =-dot(s, eye);
Result[3][1] =-dot(u, eye);
Result[3][2] = dot(f, eye);
return Result;
}
现在我要告诉你为什么我对这个算法有概念上的问题。这个视图矩阵有两个部分,平移和旋转。平移执行正确的逆变换,将摄影机位置带到原点,而不是将原点位置带到摄影机。类似地,您希望摄影机定义的旋转在放入此视图矩阵之前也反转。我看不出这里会发生这种事,这是我的问题。
考虑向前矢量,这是你的相机观察的地方。因此,这个向前矢量需要映射到-Z轴,这是openGL使用的向前方向。假设这个视图矩阵的工作方式是在视图矩阵的列中创建一个正交基,所以当你乘以这个矩阵右侧的一个顶点时,你本质上只是将它的坐标转换为不同轴的坐标。
当我在脑海中播放由于这种变换而发生的旋转时,我看到的旋转不是相机的反向旋转,就像假设发生的那样,而是非反向旋转。也就是说,我发现的不是将相机向前映射到-Z轴,而是将-Z轴映射到相机向前。
如果你不明白我的意思,可以考虑一个2D的例子,说明这里发生的同样类型的事情。假设前向矢量为(sqr(2)/2,sqr(1)/2),或sin/cos为45度,还假设这个2D相机的侧矢量为sin/cos,为-45度。我们想要将这个正向向量映射到(0,1),即正Y轴。正Y轴可以被认为是openGL空间中-Z轴的类比。让我们考虑一个与我们的前向向量方向相同的顶点,即(1,1)。通过使用GLM.lookAt的逻辑,我们应该能够通过使用由第一列中的前向矢量和第二列中的边矢量组成的2x2矩阵将(1,1)映射到Y轴。这是该计算的等效计算http://www.wolframalpha.com/input/?i=%28sqr%282%29%2F2+%2C+sqr%282%29%2F2+29++1+%2B+%28sqr%2282%29%2F2%2C+-sqr%28%29%2F2+%29+1。
请注意,您不会像想要的那样将(1,1)顶点映射到正Y轴,而是将其映射到正X轴。如果应用此变换,还可以考虑正Y轴上的顶点发生了什么。果不其然,它被转换为正向向量。
因此,GLM算法似乎出现了一些非常可疑的情况。然而,我怀疑这个算法是不正确的,因为它是如此流行。我错过了什么?
看看Mesa中的GLU源代码:http://cgit.freedesktop.org/mesa/glu/tree/src/libutil/project.c
首先,在gluPerspective的实现中,注意-1
使用索引[2][3]
,而-2 * zNear * zFar / (zFar - zNear)
使用[3][2]
。这意味着索引是[column][row]
。
现在,在gluLookAt
的实现中,第一行被设置为side
,下一行被设置成up
,最后一行被设成-forward
。这将为您提供旋转矩阵,该矩阵与将眼睛带到原点的平移相乘。
GLM似乎使用了相同的[column][row]
索引(来自代码)。您刚刚为lookAt
发布的文章与更标准的gluLookAt
(包括平移部分)一致。所以至少GLM和GLU同意。
然后让我们一步一步地推导出完整的构造。注意C
为中心位置,E
为眼睛位置。
-
移动整个场景以将眼睛的位置放在原点,即应用
-E
的平移。 -
旋转场景以使摄影机的轴与标准
(x, y, z)
轴对齐。2.1计算相机的正正交基:
f = normalize(C - E) (pointing towards the center) s = normalize(f x u) (pointing to the right side of the eye) u = s x f (pointing up)
因此,
(s, u, -f)
是相机的正正交基础。2.2找到将
(s, u, -f)
轴映射到标准轴(x, y, z)
的旋转矩阵R
。逆旋转矩阵R^-1
执行相反的操作,并将标准轴与相机轴对齐,根据定义,这意味着:(sx ux -fx) R^-1 = (sy uy -fy) (sz uz -fz)
自
R^-1 = R^T
以来,我们有:( sx sy sz) R = ( ux uy uz) (-fx -fy -fz)
-
将平移与旋转结合起来。点
M
通过"观察"变换映射到R (M - E) = R M - R E = R M + t
。因此,"查看"的最终4x4变换矩阵确实是:( sx sy sz tx ) ( sx sy sz -s.E ) L = ( ux uy uz ty ) = ( ux uy uz -u.E ) (-fx -fy -fz tz ) (-fx -fy -fz f.E ) ( 0 0 0 1 ) ( 0 0 0 1 )
所以当你写的时候:
也就是说,而不是发现相机正向映射到-Z轴,我发现-Z轴正被映射到相机的前方。
这非常令人惊讶,因为通过构造,"观察"变换将相机前向轴映射到-z轴。这种"观察"变换应该被认为是移动整个场景,使相机与标准原点/轴对齐,这就是它真正的作用。
使用2D示例:
通过使用GLM.lookAt的逻辑,我们应该能够将(1,1)映射到Y轴,使用2x2矩阵,该矩阵由第一列和第二列中的边向量。
相反,根据我所描述的构造,您需要一个2x2矩阵,其前向和行向量为行,而不是列,以将(1,1)和另一个向量映射到y和x轴。要使用矩阵系数的定义,需要通过变换获得标准基向量的图像。这直接给出了矩阵的列。但是,由于您所寻找的是相反的(将向量映射到标准基向量),因此必须反转变换(转置,因为它是旋转)。然后,您的引用向量变成行,而不是列。
这些家伙可能会对你的可疑问题有一些进一步的见解:glm::lookAt垂直摄影机在z<=时翻转0
你可能对答案感兴趣?