我对GLM的lookAt函数理解上存在的不一致性

4

首先,如果您想要了解GLM lookAt算法的解释,请查看此问题提供的答案: https://dev59.com/znjZa4cB1Zd3GeqPg609#19740748

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(2)/ 2),或者是45度的sin / cos,并且还假设这个2D摄像机的侧向矢量是-45度的sin / cos。我们想将这个前向矢量映射到(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%282%29%2F2%2C+-sqr%282%29%2F2+%29+1
请注意,您无法将(1,1)顶点映射到所需的正Y轴,而是将其映射到正X轴。如果您应用此转换,可能还要考虑在正Y轴上的顶点会发生什么情况。毫不奇怪,它被转换为向前的矢量。
因此,似乎GLM算法存在一些非常可疑的问题。但是,我怀疑该算法不正确,因为它非常流行。我错过了什么?

首先,"OpenGL空间"并不存在,GL处理的是半打坐标空间(对象->世界->视图=右手)(裁剪->NDC->屏幕=左手)。投影矩阵是改变坐标空间的左右手的东西(通过反转Z),这并没有包含在你对LookAt(...)的探索中。 - Andon M. Coleman
@AndonM.Coleman ,我完全理解。 我使用术语openGL空间来指代在投影矩阵之前所需的空间。 尽管我没有明确说明投影矩阵可能会被使用,但这对问题并不重要,因为我提到了-Z应该映射摄像机前向量。 - Thomas
2个回答

5

看一下 Mesa 中 GLU 源代码:http://cgit.freedesktop.org/mesa/glu/tree/src/libutil/project.c

首先,在 gluPerspective 的实现中,注意到 -1 使用的是索引 [2][3],而 -2 * zNear * zFar / (zFar - zNear) 使用的是 [3][2]。这意味着索引是 [列][行]

现在,在 gluLookAt 的实现中,第一行设置为 side,下一行设置为 up,最后一行设置为 -forward。这给出了旋转矩阵,该矩阵与将眼睛带到原点的平移矩阵进行后乘积。

GLM 似乎使用相同的 [列][行] 索引(从代码中可以看出)。你刚刚发布的 lookAt 片段与更标准的 gluLookAt(包括平移部分)一致。因此,至少 GLM 和 GLU 是一致的。

让我们逐步推导完整的构造过程。记 C 为中心位置,E 为眼睛位置。

  1. 将整个场景移动以使眼睛位置位于原点,即应用平移 -E

  2. 旋转场景以使相机轴与标准的 (x, y, z) 轴对齐。

    2.1 计算相机的正交基:

    f = normalize(C - E)(指向中心)
    s = normalize(f x u)(指向眼睛右侧)
    u = s x f(指向上方)
    

    因此,(s, u, -f) 是相机的正交基。

    2.2 找到旋转矩阵 R,将 (s, u, -f) 轴映射到标准轴 (x, y, z)。逆旋转矩阵 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)
    
  3. 将平移与旋转组合。点 M 通过 "look at" 变换映射为 R (M - E) = R M - R E = R M + t。因此,"look at" 的最终 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轴被映射到相机前方,这非常令人惊讶。

这很令人惊讶,因为按照构造方式,“look at”变换将相机前轴映射到-Z轴。应该将这个“look at”变换视为将整个场景移动以使相机与标准原点/轴对齐,这才是它的真正作用。

使用你的2D示例:

通过使用GLM.lookAt的逻辑,我们应该能够通过使用一个2x2矩阵来将(1,1)映射到Y轴,其中第一列是前向量,第二列是侧向量。

这恰恰相反,按照我描述的构造方式,您需要一个2x2矩阵,其中前向量和行向量作为而不是列来映射(1, 1)和另一个向量到y轴和x轴。要使用矩阵系数的定义,您需要通过变换获得标准基向量的图像。这直接给出了矩阵的列。但由于您要寻找的是相反的(将您的向量映射到标准基向量),因此您必须反转变换(转置,因为它是旋转)。然后您的参考向量变成而不是列。


0

这个相关问题对我来说并不是特别有帮助。 - Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接