目录
Moving the Camera
The Method
step 1: computetheforwardaxis
步骤2:计算机主机。
Step 4: compute the up vector
step 4: setthe 4x4matrixusingtheright,up and forward vector as from point。
look-at method limitation
keywords :矩阵,LookAt,camera,cross product,transformation matrix,transform,camera-to-world,look-at,gi
在这门短课程中,您将学习移动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矩阵编码笛卡尔坐标系的三个轴。 同样,如果这对你来说不明显,请读几何课。 处理矩阵和坐标系时,请注意两个约束。 对于矩阵,必须选择显示行优先和列优先。 在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) 时。 不幸的是,在这种特殊情况下,叉积无法为正确的向量生成结果。 这个问题实际上没有真正的解决方案。 您可以检测这种情况,并选择手动设置向量(因为您知道向量的配置应该是什么)。 可以使用四元数插值开发更优雅的解决方案。