Placing a Camera: the LookAt Function(翻译)

目录

Moving the Camera

The Method

Step 1: compute the forward axis

 Step 2: compute the right vector. 

Step 4: compute the up vector

Step 4: set the 4x4 matrix using the right, up and forward vector as from point.

The Look-At Method Limitation

Keywords: Matrix, LookAt, camera, cross product, transformation matrix, transform, camera-to-world, look-at, Gimbal lock.

在这个简短的课程中,我们将学习一种简单但有用的方法来移动 3D 摄像机。 如果您不熟悉变换矩阵和向量之间的叉积的概念,您将不会轻易理解本课。 希望如果情况还不是这样,我们建议您阅读名为几何的课程(完整)。

Moving the Camera

能够在 3D 场景中移动相机非常重要。 然而,在 Scratchapixel 的大部分课程中,我们通常使用一个 4x4 矩阵,通常标记为 camToWorld 来设置相机位置和空间旋转(记住相机不应该缩放)请记住,假定处于默认位置的相机以原点为中心并沿负 z 轴对齐。 这在光线追踪:生成相机光线一课中有详细解释。但是,使用 4x4 矩阵来设置场景中的相机位置并不是很友好,除非我们可以访问 3D 动画系统(例如 Maya 或 Blender)来设置相机并导出其变换矩阵。

希望我们可以使用另一种比直接设置矩阵更好的方法,并且不需要editor (尽管这当然总是更好)。 这种技术并没有真正的名称,但程序员通常将其称为 Look-At 方法。 该方法的思想很简单。 为了设置相机位置和方向,您真正需要的是一个来设置空间中的相机位置,我们将其称为起点,以及一个定义相机正在观察的点。 我们将把这个点称为 to 点。

 有趣的是,从这对点,我们可以创建一个相机 4x4 矩阵,我们将在本课中演示。

相机沿负 z 轴对齐。 这是否意味着我需要将相机沿 y 轴旋转 180 度或沿 z 轴放大 -1? 一点也不。 变换相机与变换场景中的任何其他对象没有什么不同。 请记住,在光线追踪中,我们构建主光线就好像相机位于其默认位置一样。 这在光线追踪:生成相机光线课程中进行了解释。 这是我们实际反转光线方向的时候。 换句话说,此时光线方向的 z 坐标始终为负:处于默认位置的相机沿负 z 轴向下看。 这些主要光线然后由相机到世界矩阵进行转换。 因此,在构建 4x4 相机到世界矩阵时无需考虑相机的默认方向。

The Method

请记住,4x4 矩阵对笛卡尔坐标系的 3 轴进行编码。 同样,如果这对您来说不是很明显,请阅读几何课程。 请记住,在处理矩阵和坐标系时需要注意两个约定。 对于矩阵,您需要在行优先和列优先表示之间进行选择。 在 Scratchapixel,我们使用行优先符号。 至于坐标系,您需要在右手坐标系和左手坐标系之间进行选择。 我们使用右手坐标系。 4x4 矩阵的第四行(在行主矩阵中)对平移值进行编码。

 您如何命名笛卡尔坐标系的轴取决于您的偏好,您可以将它们命名为 x、y 和 z,但在本课中,为了清楚起见,我们将它们命名为 right(对于 x 轴)、up(对于 y 轴) ) 并向前 (z 轴)。 这在下图进行了说明。

根据 from-to 点对构建 4x4 矩阵的方法可以分为四个步骤:

Step 1: compute the forward axis

在图 1 和图 2 中,很容易看出相机局部坐标系的前轴沿着由 from 和 to 点定义的线段对齐。 一点点几何就足以计算这个向量。 你只需要归一化向量 To-From(注意这个向量的方向:它是 To-From 而不是 From-To)。 这可以通过以下代码片段来完成:

Vec3f forward = Normalize(from - to);

We found one vector. Two left!

 Step 2: compute the right vector. 

回忆一下几何课,笛卡尔坐标是由三个相互垂直的单位向量定义的。 我们也知道,如果我们取两个向量 A 和 B,它们可以被看作是在一个平面上,这两个向量的叉积创建了第三个向量 C 垂直于该平面,因此也明显垂直于 A 和 B . 我们可以使用这个属性来创建我们的右向量。 这里的想法是使用一些任意向量并计算前向向量和这个任意向量之间的交叉向量。 结果是一个向量,它必须垂直于前向向量,并且可以在我们的笛卡尔坐标系的构建中用作右向量。 计算这个向量的代码很简单,因为它只意味着前向向量和这个任意向量之间的叉积:

Vec3f right = crossProduct(randomVec, forward);

现在的问题是,我们如何选择这个任意向量? 嗯,这个向量不能完全随意,这就是我们用斜体写这个词的原因。 想一想:如果前向向量是 (0,0,1),那么正确的向量应该是 (1,0,0)。 只有当我们选择向量 (0,1,0) 作为我们的任意向量时,才能做到这一点。 事实上: (0,1,0) x (0,0,1) = (1,0,0) 这里的符号 x 说明了叉积。 请记住,计算叉积的方程式是:

其中 a 和 b 是两个向量,c 是 a 和 b 的叉积的结果。 当您查看图 3 时,您还可以注意到,无论前向矢量的方向如何,与前向矢量和矢量 (0,1,0) 定义的平面垂直的矢量始终是相机笛卡尔坐标的右矢量 系统。 那是因为该坐标系的向上向量位于图 4 所示的同一平面内。这很好,因为向量 (0,1,0) 可以明显地代替我们之前所说的任意向量。

 另请注意,从该观察中,正确的向量始终位于 xz 平面内。 你怎么会问? 如果相机有一个滚动,正确的向量不会在不同的平面上吗? 这实际上是正确的,但是将滚动应用于相机并不是您可以直接使用观察方法完成的事情。 要添加相机胶卷,您首先需要创建一个矩阵来滚动相机(围绕 z 轴旋转相机),然后将该矩阵乘以使用观察方法构建的相机到世界矩阵。

Finally, here is the code to compute the right vector:

Vec3f tmp(0, 1, 0); 
Vec3f right = crossProduct(Normalize(tmp), forward); 

请注意,我们对任意向量进行归一化,以防您实际使用不同于 (0,1,0) 的向量。 因此,为了安全起见,我们将使其正常化。 还要注意叉积中向量的顺序。 请记住,叉积不是可交换的(它实际上是反交换的,有关详细信息,请查看几何课程)。 记住正确顺序的最佳助记方法是考虑正向向量 (0,0,1) 与向上向量 (0,1,0) 的叉积,我们知道它应该给出 (1,0,0 ) 而不是 (-1,0,0)。 如果你知道叉积的方程,你应该很容易发现顺序是向 up×forward 而不是相反。 太好了,我们有前向和右向向量。 现在如何找到向上向量?

Step 4: compute the up vector

嗯,这很简单,我们有两个正交向量,前向向量和右向向量,因此计算这两个向量之间的叉积只会给我们缺少的第三个向量,向上向量。 请注意,如果前向和右向向量被归一化,那么从叉积计算出的向上向量也将被归一化:

Vec3f up = crossProduct(forward, right); 

同样,您需要注意叉积中涉及的向量的顺序。 太好了,我们现在有了定义相机坐标系的三个向量。 现在让我们构建最终的 4x4 相机到世界矩阵。

Step 4: set the 4x4 matrix using the right, up and forward vector as from point.

完成这个过程所需要做的就是构建相机到世界矩阵本身。 为此,我们只需用正确的数据替换矩阵的每一行:

  • Row 1: replace the first three coefficients of the row with the coordinates of the right vector,
  • Row 2: replace the first three coefficients of the row with the coordinates of the up vector,
  • Row 3: replace the first three coefficients of the row with the coordinates of the forward vector,
  • Row 4: replace the first three coefficients of the row with the coordinates of the from point.

同样,如果您不确定我们为什么要这样做,请查看几何课程。 最后这里是完整功能的源代码。 它从两个参数(from 和 to 点)计算并返回相机到世界的矩阵。 请注意,函数第三个参数(在以下代码中称为 tmp)是用于计算右向量的任意向量。 它使用默认值 (0,1,0) 设置,但可以根据需要进行更改(因此需要在使用时对其进行标准化)。

Matrix44f lookAt(const Vec3f& from, const Vec3f& to, const Vec3f& tmp = Vec3f(0, 1, 0)) 
{ 
    Vec3f forward = normalize(from - to); 
    Vec3f right = crossProduct(normalize(tmp), forward); 
    Vec3f up = crossProduct(forward, right); 
 
    Matrix44f camToWorld; 
 
    camToWorld[0][0] = right.x; 
    camToWorld[0][1] = right.y; 
    camToWorld[0][2] = right.z; 
    camToWorld[1][0] = up.x; 
    camToWorld[1][1] = up.y; 
    camToWorld[1][2] = up.z; 
    camToWorld[2][0] = forward.x; 
    camToWorld[2][1] = forward.y; 
    camToWorld[2][2] = forward.z; 
 
    camToWorld[3][0] = from.x; 
    camToWorld[3][1] = from.y; 
    camToWorld[3][2] = from.z; 
 
    return camToWorld; 
} 

The Look-At Method Limitation

该方法非常简单并且通常效果很好。 虽然它有一个Achilles heels (弱点)。 当相机垂直向下或向上看时,前轴非常接近用于计算右轴的任意轴。 极端情况当然是当向前轴和这个任意轴完全平行时,例如 当前向向量是 (0,1,0) 或 (0,-1,0) 时。 不幸的是,在这种特殊情况下,叉积无法为正确的向量生成结果。 这个问题实际上没有真正的解决方案。 您可以检测这种情况,并选择手动设置向量(因为您知道向量的配置应该是什么)。 可以使用四元数插值开发更优雅的解决方案。

猜你喜欢

转载自blog.csdn.net/Vpn_zc/article/details/121388091