计算 LookAt 矩阵

54

我正在编写一个3D引擎,发现DirectX文档中描述的LookAt算法:

zaxis = normal(At - Eye)
xaxis = normal(cross(Up, zaxis))
yaxis = cross(zaxis, xaxis)

 xaxis.x           yaxis.x           zaxis.x          0
 xaxis.y           yaxis.y           zaxis.y          0
 xaxis.z           yaxis.z           zaxis.z          0
-dot(xaxis, eye)  -dot(yaxis, eye)  -dot(zaxis, eye)  1

现在我明白它在旋转方面的工作原理了,但我不太明白为什么它要把矩阵的平移分量设为那些点积。仔细观察一下,似乎它是根据新基向量在眼睛/相机位置投影的一个小量来调整相机位置。

问题是为什么需要这样做?它能实现什么效果?


请阅读http://msdn.microsoft.com/en-au/library/bb206269(VS.85).aspx上的内容。 - Dominik Grabiec
10
请注意,这是一个关于矩阵的行优先、左手坐标系的描述 - bobobobo
4
底部右侧的字母 L(“l”)是否应为数字 1? - vidstige
1
@bobobobo 这是一个列主序矩阵,因为翻译在底部而不是右侧。 “列主序”在GLSL中是标准的。 - Crouching Kitten
在列主序变换矩阵中,平移组件位于右侧 - 请参见 https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/row-major-vs-column-major-vector 中的“摘要”。 - bobobobo
1
@bobobobo 那个页面假设列/行主要是数学概念,但实际上它们不是。相反,它们告诉我们如何将一维数组解释为矩阵。OpenGL总是以列主方式解释数组。OP的代码是从Microsoft页面复制的,它并没有显示一个矩阵。它显示了一个在多行中排列的数组。列主方式将其翻转(因此在OpenGL的解释中,翻译会到达右侧),而行主方式保持其按代码编写的方式,位于底部。 - Crouching Kitten
8个回答

69
请注意,给出的例子是左手系、行主序矩阵。
因此,操作如下:首先进行平移以移动到原点(减去“eye”),然后旋转,使从“eye”到“At”的向量与+z对齐:
基本上,如果你在旋转矩阵的前面乘以一个平移矩阵-“eye”,你会得到相同的结果。
[ 1 0 0 0 ] [ xaxis.x yaxis.x zaxis.x 0 ] [ 0 1 0 0 ] * [ xaxis.y yaxis.y zaxis.y 0 ] [ 0 0 1 0 ] [ xaxis.z yaxis.z zaxis.z 0 ] [ -eye.x -eye.y -eye.z 1 ] [ 0 0 0 1 ]
[ xaxis.x yaxis.x zaxis.x 0 ] = [ xaxis.y yaxis.y zaxis.y 0 ] [ xaxis.z yaxis.z zaxis.z 0 ] [ dot(xaxis,-eye) dot(yaxis,-eye) dot(zaxis,-eye) 1 ]
附加说明:
请注意,查看变换是(故意)反转的:你将每个顶点乘以这个矩阵,“移动世界”,使你想要看到的部分最终呈现在规范化视图体积中。
此外,请注意,LookAt矩阵的旋转矩阵(称为R)部分是一个反向基变换矩阵,其中R的行是新基向量,以旧基向量表示(因此变量名为xaxis.x,.. xaxis是变换发生后的新x轴)。但由于反转,所以行和列进行了转置。

5
矩阵乘法不满足交换律。 - 林奕忠
3
这是最佳答案,比目前被接受的答案更有说服力。 - prideout
1
这意味着LookAt矩阵是一个正交规范基,否则转置将不等于它的逆,对吗? - John Leidegren
1
@JohnLeidegren 是的,旋转部分是通过构造正交的,就是为了这个原因。 - eric

20

我通过创建一个3x3的旋转矩阵(如您所做的那样),并将其扩展为4x4矩阵(在右下角填充1,其他位置填充0)来构建一个观察矩阵。然后,我使用负眼点坐标(无点积)构建一个4x4平移矩阵,并将这两个矩阵相乘。我的猜测是,这种乘法产生了您示例中底部行的点积的等效值,但我需要在纸上计算一下以确保。

3D旋转会转换您的轴,因此您不能直接使用眼点,而是必须将其转换为新的坐标系。这就是矩阵乘法——或在本例中,3个点积值——的作用。


2
你不应该通过计算相机方向的逆世界矩阵来创建视图矩阵吗? - xcrypt
@xcrypt 你是指相机的逆变换矩阵吗? - user6241971
2
请纠正我,但是您的描述似乎是视图变换(即视图矩阵),而OP似乎展示了观察矩阵。 有一次我认为视图矩阵观察矩阵是相同的东西,但是遭受了(昂贵的)惨败,现在我认为它们是两个不同的矩阵。这个理解错误吗?观察矩阵视图矩阵完全相同,只是构建方式不同吗? - code_dredd
@code_dredd 的 LookAt() 函数有时会返回“世界矩阵”,它是“视图矩阵”的倒数。但由于求逆非常昂贵,最好让 LookAt 直接返回视图矩阵。 - Crouching Kitten

5
该翻译组件通过在原点处创建一个正交基,并用该原点(即您的“眼睛”)和三个轴的术语来表达其他所有内容,从而帮助您。
这个概念不是矩阵调整相机位置。相反,它试图简化数学:当您想要渲染您从“眼睛”位置可以看到的所有内容的图片时,最容易的方法是假装您的眼睛是宇宙的中心。
因此,简短的答案是这使得数学计算更加容易。
回答评论中的问题:你不只是从每个位置减去“眼睛”位置的原因与操作顺序有关。这样想:一旦您进入新的参考系(即由xaxis、yaxis和zaxis表示的头部位置),现在您希望以这个新(旋转的)参考系为基础来表达距离。这就是为什么您使用新轴与眼睛位置的点积:它代表了物体需要移动的相同距离,但它使用了新的坐标系。

据我理解,矩阵已经正确设置了翻译,但是为什么在计算中要使用点积?它不能只是 -eye.x,-eye.y,-eye.z 吗? - Dominik Grabiec

4

一些常规信息:

lookat矩阵是一个矩阵,将某物体定位/旋转到指向(看向)空间中的某个点,从另一个空间中的点。

该方法需要摄像机视图的期望“中心”、“上”向量,它代表摄像机的“上”方向(几乎总是(0,1,0),但不必如此),以及“眼睛”向量,即摄像机的位置。

这主要用于相机,但也可用于其他技术,如阴影、聚光灯等。

坦率地说,我不完全确定为什么在这种方法中设置翻译组件的方式。在OpenGL的gluLookAt中,由于相机始终被视为在0,0,0处,因此将翻译组件设置为0,0,0。


2

点积是将一个点投影到轴上,以获取眼睛的x、y或z分量。您正在向后移动相机,因此从(10,0,0)和从(100000,0,0)看(0,0,0)会产生不同的效果。


2

lookat矩阵执行以下两个步骤:

  1. 将您的模型平移到原点位置,
  2. 根据up向量和观察方向设置旋转。

点积的意思是先进行平移,然后再进行旋转。点积不是将两个矩阵相乘,而是将一行与一列相乘。


1
一个4x4的转换矩阵包含两到三个组件: 1. 旋转矩阵 2. 平移矩阵。 3. 缩放矩阵(许多引擎不直接在矩阵中使用)。
它们的组合将把一个点从空间A变换到空间B,因此这是一个变换矩阵M_ab。
现在,相机的位置在空间A中,因此它不是空间B的有效变换,所以需要将此位置乘以旋转变换。
唯一仍然存在的问题是为什么要用那些圆点? 嗯,如果你在纸上写出3个带有X、Y和Z的点,你会发现,这正好像是乘以一个旋转矩阵。
第四行/列的一个例子是取世界空间中的零点-(0,0,0)。这不是相机空间中的零点,因此需要知道相机空间中的表示,因为旋转和缩放会使其保持为零!
干杯

0

需要将眼点放在您的轴空间中,而不是世界空间中。当您用坐标单位基向量之一(x、y、z)点乘一个向量时,它会给出在该空间中眼睛的坐标。通过在最后一个位置应用三个平移(在本例中为最后一行),可以通过变换位置来向后移动眼睛,负数相当于将其余空间全部向前移动。就像在电梯里上升会让你感觉到整个世界从你脚下滑落。

使用左手矩阵,将平移作为最后一行而不是最后一列,这是一种宗教信仰的差异,与答案完全无关。然而,这是一种应该严格避免的教条。在绘制树形草图时,最好按自然阅读顺序从左到右链接全局到局部(正向运动学)变换。使用左手矩阵会强制您从右到左编写这些内容。


“轴空间”是什么?我以前从未听说过这个术语。你是不是指的是物体空间?竖直空间?相机空间? - Bjorn

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