你如何计算相机的变换矩阵?

时间:2016-08-07 23:56:14

标签: c++ opengl matrix

我有一个摄像头,其位置,向前,向右和向下矢量被定义为班级成员_position_forward_right_down。对于问题的旋转部分,我只需使用_forward_right_down加载目标坐标系(视图空间)的行,然后应用旋转矩阵进入“OpenGL”视图空间。

在此之前,我用_position的否定词进行翻译。这是正确的,还是我需要做更多的数学运算来确定我的轮换前后乘以的实际平移?

下面是我的代码似乎不起作用。具体来说,我正在世界空间的原点渲染一个物体(一个面向z轴的小四边形),当我运行该程序时,该物体看起来是扭曲的。 (注意,mat4构造函数采用ROW主要顺序的元素,尽管它们在内部的列major中存储它们。)

mat4 Camera::matrix() const
{
    return
        mat4(
            0.0f, 0.0f, -1.0f, 0.0f,
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, -1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f) *
        mat4(
            _forward.x, _forward.y, _forward.z, 0.0f,
            _right.x, _right.y, _right.z, 0.0f,
            _down.x, _down.y, _down.z, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f) *
        translate(
            -_position.x,
            -_position.y,
            -_position.z
        );
}

以下是在上面使用之前使用的函数的代码。

void Camera::look_at(const vec3& position, const vec3& point)
{
    _position = position;
    _forward = normalize(point - position);
    _right = cross(_forward, vec3(0.0f, 1.0f, 0.0f));
    _down = cross(_forward, _right);
}

在我的初始化中,我使用此代码:

camera.look_at(vec3(1.0f, 1.0f, 1.0f), vec3(0.0f, 0.0f, 0.0f));
view = camera.matrix();
proj = perspective(60.0f, SCREEN_WIDTH / (float)SCREEN_HEIGHT, 0.001f, 100.0f);
view_proj = proj * view;

2 个答案:

答案 0 :(得分:3)

计算视图矩阵的问题(特定问题的答案"如何计算摄像机的变换矩阵?")是将矢量和点从世界空间转换为视图的问题空间。这与从任何空间转换为目标空间相同。 (对于此答案的其余部分,源空间指的是世界空间,而目标空间指的是视图空间。)我们给出了目标基础向量(旋转向量)的相对来源_right x,_up y和_backward z相机向量为vec3 s)和目标空间相对于源空间的原点( _position指向vec3)。虽然问题使用_forward_right_down,但我们首先会看_right_up_backward以保持一致OpenGL视图空间。最后,我们将使用前面的向量来查看解决方案。此外,术语 space 将引用坐标系基础(方向向量)以及帧原点(位置向量)。

从源空间到目标空间的变换矩阵可以表示为旋转,以便所有向量和点相对于目标空间正确定向,然后进行平移,以便所有点相对于目标正确定位空间。任何变换矩阵乘以点始终是旋转(矩阵和矢量的行的线性组合)和平移(通过第四列矢量抵消旋转的结果)。注意,方向矢量(由矢量的第四分量设置为零的齐次坐标表示)不受平移的影响。我们现在将看两种方法来推导出这样的矩阵,其中第二种方法在计算上更有效。

让偏移向量成为指向从目标空间到源空间的整个距离的向量。在问题提供的代码片段中使用的第一种方法是首先通过偏移向量相对于源空间进行平移。此操作重新定位所有点(但不是矢量)以将目标原点作为其参考点。其次,通过将相对于源空间的目标空间的基矢量加载到前三列中来应用旋转(即_right_up_backward)。为什么排?关于任何坐标系的矢量简单地表示坐标系的基础矢量的线性组合,并且4×4矩阵乘以4分量矢量的结果简单地是第一矩阵的行乘以列的线性组合。第二个矩阵(在列向量的情况下,这只是一个4x1矩阵)。这种线性组合正是我们追求的以及为什么齐次坐标起作用的结果。以下是实现此方法的示例代码。请注意,translate()函数返回转换矩阵。

mat4 Camera::matrix() const
{
    return
        mat4(
               _right.x,    _right.y,    _right.z, 0.0f,
                  _up.x,       _up.y,       _up.z, 0.0f,
            _backward.x, _backward.y, _backward.z, 0.0f,
                   0.0f,        0.0f,        0.0f, 1.0f) *
        translate(
            -_position.x,
            -_position.y,
            -_position.z
        );
}

第二种更有效的方法是,不是将两个矩阵相乘,而是直接生成矩阵,避免需要乘以旋转矩阵和平移矩阵。首先,和以前一样,前三列加载目标基矢量作为行。最后,使用每个基矢量的点积计算平移矢量(第四列中的前三个元素)。

mat4 Camera::matrix() const
{
    return mat4(
           _right.x,    _right.y,    _right.z, -dot(   _right, _position),
              _up.x,       _up.y,       _up.z, -dot(      _up, _position),
        _backward.x, _backward.y, _backward.z, -dot(_backward, _position),
               0.0f,        0.0f,        0.0f,                      1.0f);
}

请注意,点积应为dot(_right, -_position)等,但我们可以通过将点积之外的否定移至-dot(_right, _position)来保存乘法。

这些点积与之前将旋转矩阵乘以平移矩阵完全相同。例如,以下矩阵乘法演示了第一种方法中的情况。将它与上面的代码与点积进行比较。

| x0 x1 x2 0 |   | 1 0 0 -t0 |   | x0 x1 x2 -(x0*t0 + x1*t1 + x2*t2) |
| y0 y1 y2 0 |   | 0 1 0 -t1 |   | y0 y1 y2 -(y0*t0 + y1*t1 + y2*t2) |
| z0 z1 z2 0 | * | 0 0 1 -t2 | = | z0 z1 z2 -(z0*t0 + z1*t1 + z2*t2) |
|  0  0  0 1 |   | 0 0 0   1 |   |  0  0  0                       1  |

请注意,_right_up_backward向量(或_forward_right_down向量始终为_forward_right_down个向量如问题中那样)必须标准化,否则会产生一些无意的缩放。

要考虑使用| 0 1 0 0 | | 0 0 -1 0 | | -1 0 0 0 | | 0 0 0 1 | | 0 1 0 0 | | x0 x1 x2 t0 | | y0 y1 y2 y3 | | 0 0 -1 0 | | y0 y1 y2 t1 | | -z0 -z1 -z2 -t2 | | -1 0 0 0 | * | z0 z1 z2 t2 | = | -x0 -x1 -x2 -t0 | | 0 0 0 1 | | 0 0 0 1 | | 0 0 0 1 | mat4 Camera::matrix() const { float tx = dot(_forward, _position); float ty = -dot(_right, _position); float tz = dot(_down, _position); return mat4( _right.x, _right.y, _right.z, ty, -_down.x, -_down.y, -_down.z, tz, -_forward.x, -_forward.y, -_forward.z, tx, 0.0f, 0.0f, 0.0f, 1.0f); } 向量,需要应用额外的轮播才能进入OpenGL视图空间。该矩阵列在下面:

look_at()

左上3x3子矩阵中的行是相对于所需相机空间(前x,右y,下z)的OpenGL视图空间(右x,上y,后z)的基矢量。然后,我们将此矩阵与之前使用点积的矩阵相乘。这种矩阵乘法类似于:

{{1}}

最终的矩阵函数可以是:

{{1}}

因此除了一些性能问题之外,问题中的所有代码都是正确的,除了在{{1}}函数中未被规范化的事实。

答案 1 :(得分:2)

为方便起见,我建议使用glm::lookAt()

有关详细信息,请参阅https://glm.g-truc.net/0.9.2/api/a00245.html#ga2d6b6c381f047ea4d9ca4145fed9edd5

但是这里是如何从位置,目标和向上矢量构建它。

请注意,这与lookAt()函数相同。

//creates a lookat matrix
mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up)
{
    Vec3 left, up2, forward;

    // make rotation matrix

    // forward vector
    forward = center - eye;
    forward.Normalize();

    // up2 vector (assuming up is normalized)
    up2 = up;

    // left vector = up2 cross forward
    left = up2.Cross(forward);

    // Recompute up2 = forward cross left (in case up and forward were not orthogonal)
    up2 = forward.Cross(left);

    // cross product gives area of parallelogram, which is < 1.0 for
    // non-perpendicular unit-length vectors; so normalize left, up2 here
    left.Normalize();
    up2.Normalize();

     mat4(
        left.x, left.y, left.z, left.Dot(-eye),
        up2.x, up2.y, up2.z, up2.Dot(-eye),
        -forward.x, -forward.y, -forward.z, -forward.Dot(-eye),
        0.0f, 0.0f, 0.0f, 1.0f)

}

请注意up2 = up,以防up up和forward为正交,否则up2是正向向上矢量。