计算机图形学:从相机坐标系到投影变换
一、视图矩阵将相机放置到原点在计算机图形学中相机不仅有位置还有自己的朝向。为了从相机的视角观察三维场景我们需要将世界坐标系中的点转换到相机坐标系中。这个过程叫作视图变换View Transformation。视图变换可以分为两个部分旋转变换将世界坐标系转换到相机坐标系的方向。平移变换将相机的位置移动到坐标原点。在之前的文章里面对于某一个点的旋转是不方便的所以我们一般会将该点移动到原点在进行旋转。一、相机的四个基本符号首先固定以下符号避免混淆。e相机在世界坐标系中的位置。g相机的视线方向从相机指向观察目标。t相机的上方向参考向量。r相机的右方向向量。其中e 决定相机的位置而 g、t、r 用来确定相机的朝向。假设相机位于e (0, 0, 5)相机观察世界原点target (0, 0, 0)那么视线方向为g target - e (0, 0, -5)对向量进行归一化只保留方向g normalize(target - e) (0, 0, -1)接下来给出一个参考上方向t (0, 1, 0)这个向量表示相机的上方大致朝向世界坐标系的 Y 轴正方向这个 t 也就相当于固定了照相机的角度。注意初始的 t 不一定与 g 垂直因此后面还需要修正。二、通过叉乘计算相机的右方向已知视线方向 g 和上方向参考向量 t可以通过叉乘计算相机的右方向r g × t叉乘顺序非常重要g × t -(t × g)如果将顺序颠倒结果方向就会相反。设g (gx, gy, gz) t (tx, ty, tz)那么叉乘公式为r ( gy * tz - gz * ty, gz * tx - gx * tz, gx * ty - gy * tx )代入前面的例子g (0, 0, -1) t (0, 1, 0)计算得到r g × t (1, 0, 0)因此相机的右方向就是世界坐标系的 X 轴正方向。接下来重新计算上方向t r × g这样可以让三个方向互相垂直。实际计算时应保证三个方向都是单位向量并且两两正交。如果初始上方向与视线方向平行叉乘会得到零向量需要更换参考上方向。最终得到三个相机方向r 相机右方向 t 相机上方向 -g 相机后方向三、为什么相机的 Z 轴是 -g使用的相机坐标系约定中X 轴正方向相机右方 Y 轴正方向相机上方 Z 轴正方向相机后方相机实际观察的方向是局部负 Z 轴方向。但是g 表示的是从相机指向目标的方向也就是相机前方。因此相机 Z 轴正方向 -g这里的负号来自相机坐标系的约定而不是叉乘运算本身。如果其他图形系统采用相机朝正 Z 轴观察的约定相关矩阵的符号也可能不同。因此推导矩阵之前必须先明确坐标系约定。四、根据相机的三个方向向量构造旋转矩阵前面已经求出了相机的三个方向向量r 相机右方向 t 相机上方向 g 相机视线方向由于使用相机朝局部负 Z 轴观察的约定因此相机坐标系的三个正方向分别为X 轴 r Y 轴 t Z 轴-g接下来我们要根据这三个方向向量构造旋转矩阵。1. 先构造世界坐标到相机坐标的矩阵我们的目标是将世界坐标中的向量转换为相机坐标中的向量。在列向量左乘的约定下世界坐标中的向量可以写成v_world (x, y, z)^T转换后的相机坐标为v_camera R * v_world矩阵的每一行决定了输入向量在相应相机坐标轴上的投影分量。例如相机坐标的 X 分量就是世界空间向量在相机右方向 r 上的投影x_camera dot(r, v_world)同理y_camera dot(t, v_world) z_camera dot(-g, v_world)因此世界坐标到相机坐标的旋转矩阵为R_world_to_camera [ rx ry rz ] [ tx ty tz ] [-gx -gy -gz ]这里r、t 和 -g 分别构成矩阵的三行。将矩阵乘法展开就能得到x_camera rx*x ry*y rz*z y_camera tx*x ty*y tz*z z_camera -gx*x - gy*y - gz*z这三个公式的含义非常直观分别计算世界空间中的点在相机右方向、上方向和后方向上的坐标分量。2. 再通过求逆得到相机到世界坐标的矩阵如果已经知道世界坐标到相机坐标的矩阵v_camera R_world_to_camera * v_world那么反过来将相机坐标转换回世界坐标就需要对矩阵求逆v_world inverse(R_world_to_camera) * v_camera因此R_camera_to_world inverse(R_world_to_camera)由于 r、t 和 -g 是互相正交的单位向量旋转矩阵属于正交矩阵所以满足inverse(R) transpose(R)于是R_camera_to_world transpose(R_world_to_camera)展开后得到R_camera_to_world [ rx tx -gx ] [ ry ty -gy ] [ rz tz -gz ]这就是相机坐标到世界坐标的旋转矩阵。需要注意这里不是随意交换矩阵元素而是利用正交矩阵的性质将矩阵求逆转换成矩阵转置。总结一下世界坐标 - 相机坐标 R_world_to_camera [ rx ry rz ] [ tx ty tz ] [-gx -gy -gz ] 相机坐标 - 世界坐标 R_camera_to_world [ rx tx -gx ] [ ry ty -gy ] [ rz tz -gz ]两者互为逆矩阵。由于旋转矩阵是正交矩阵因此它们也互为转置矩阵。二、投影在三维图形学中我们通常在三维世界中定义物体但最终需要将它们显示在二维屏幕上。因此我们需要将三维空间中的点映射到一个用于显示的投影平面上这个过程称为投影Projection。常见的投影方式有两种正交投影Orthographic Projection投影线彼此平行。透视投影Perspective Projection投影线汇聚于同一个投影中心具有近大远小的效果。一、正交投影正交投影的核心特点是物体的投影大小不会因为它距离相机的远近而发生变化。例如一个长度为 2 的立方体无论放在相机前方较近的位置还是较远的位置只要没有进行其他缩放它在投影平面上的大小就不会因为距离变化而改变。正交投影可以理解为从三维空间中沿着固定方向发出一组互相平行的投影线将物体投射到投影平面上。为了便于计算我们通常先确定一个长方体作为观察范围。这个长方体由以下六个边界确定左边界l右边界r下边界b上边界t近裁剪面距离n远裁剪面距离f这里采用常见的相机约定相机朝局部 -Z 方向观察因此近裁剪面和远裁剪面的相机空间坐标分别为 z -n 和 z -f其中 n、f 都是正数且 f n。我们希望将这个观察长方体变换到一个标准立方体中也就是规范设备坐标NDC所使用的范围x 方向[-1, 1]y 方向[-1, 1]z 方向[-1, 1]这个过程就是正交投影矩阵要完成的工作。一、正交投影的变换过程将观察长方体映射到标准立方体可以拆分为两个步骤平移将观察长方体的中心移动到原点。缩放将平移后的长方体缩放到 [-1, 1] 范围内。这样做的好处是可以将一个复杂的空间映射问题拆解成我们熟悉的平移和缩放变换。1. 平移变换观察长方体在 x、y 方向的中心坐标分别为center_x (l r) / 2 center_y (b t) / 2由于相机空间中的 z 范围是 [-f, -n]因此长方体在 z 方向的中心坐标为center_z (-f (-n)) / 2 -(f n) / 2为了将长方体中心移动到原点需要沿三个方向分别平移x: -(l r) / 2 y: -(b t) / 2 z: (f n) / 2采用列向量左乘的约定平移矩阵为T [ 1 0 0 -(lr)/2 ] [ 0 1 0 -(bt)/2 ] [ 0 0 1 (fn)/2 ] [ 0 0 0 1 ]经过这一步观察长方体的中心就移动到了原点。此时各轴方向的坐标范围变为x: [-(r-l)/2, (r-l)/2] y: [-(t-b)/2, (t-b)/2] z: [-(f-n)/2, (f-n)/2]2. 缩放变换平移完成后我们还需要将长方体的三个方向分别缩放使每个方向的范围都变成 [-1, 1]。对于 x 方向平移后的范围是[-(r-l)/2, (r-l)/2]要将这个范围映射到 [-1, 1]需要乘以scale_x 2 / (r-l)同理y 方向的缩放系数为scale_y 2 / (t-b)z 方向稍有不同。由于我们采用相机朝 -Z 方向观察的约定并希望近裁剪面 z -n 映射到 -1、远裁剪面 z -f 映射到 1因此 z 方向需要进行反向缩放scale_z -2 / (f-n)于是缩放矩阵为S [ 2/(r-l) 0 0 0 ] [ 0 2/(t-b) 0 0 ] [ 0 0 -2/(f-n) 0 ] [ 0 0 0 1 ]注意z 方向出现负号并不是所有正交投影都必然如此而是由这里采用的相机坐标约定和深度映射方向决定的。二、推导正交投影矩阵现在我们已经得到了两个变换矩阵T将观察长方体的中心平移到原点。S将平移后的长方体缩放到标准立方体。由于使用列向量左乘矩阵乘法的顺序与实际执行顺序相反先执行平移再执行缩放因此总变换矩阵为M_ortho S * T将两个矩阵相乘可以得到M_ortho [ 2/(r-l) 0 0 -(rl)/(r-l) ] [ 0 2/(t-b) 0 -(tb)/(t-b) ] [ 0 0 -2/(f-n) -(fn)/(f-n) ] [ 0 0 0 1 ]这就是在上述坐标约定下的正交投影矩阵。它将相机空间中指定的观察长方体映射到标准立方体使三个方向的坐标都落入 [-1, 1] 范围。需要注意正交投影矩阵的作用是将观察范围映射到标准立方体并不意味着所有位于长方体之外的物体都会自动被删除。实际渲染时超出裁剪范围的部分还需要由图形管线进行裁剪。二、透视投影在三维图形学中我们需要将三维世界中的物体映射到二维屏幕上。上一节介绍的正交投影可以将观察范围映射到标准立方体但它有一个明显的特点物体不会因为距离相机更远而自然变小。而在现实世界中我们通常会观察到近大远小的现象。例如近处的汽车看起来比较大远处的汽车看起来比较小。铁轨两侧的边缘会随着距离增加逐渐靠近。同样大小的物体距离相机越远在视野中通常占据越小的范围。为了模拟这种视觉效果我们需要使用透视投影Perspective Projection。透视投影的核心思想是将三维空间中的点沿着经过相机投影中心的射线映射到投影平面上。与正交投影不同透视投影的投影线并不是互相平行的而是汇聚于同一个投影中心因此能够产生近大远小的效果。一、透视投影的基本原理其实透视投影的原理并不复杂可以想象一下同样大小的东西离我们越远看起来就越小。比如站在铁轨上往远处看两条铁轨好像慢慢靠到了一起。实际上铁轨并没有变窄只是因为距离越来越远它们在我们眼中占据的范围越来越小。在计算机图形学中也是同样的道理。我们先看相机坐标系中的一个点P (x, y, z)在 GAMES101 中我们约定相机朝着 -Z 方向看所以物体在相机前方时z 通常是负数。假设我们在相机前方放了一块投影平面位置是z -n现在想象从相机原点出发穿过点 P继续延伸这条线直到它碰到投影平面。碰到的位置就是点 P 的投影位置。根据相似三角形可以得到x_projected n * x / (-z) y_projected n * y / (-z)这两个公式看起来有点绕其实只需要抓住一点投影后的坐标要除以点距离相机的深度。举个例子假设投影平面距离相机 1 个单位两个点的 x 坐标都是 2只是距离相机的远近不同。近处的点x 2 z -2 x_projected 1 * 2 / 2 1远处的点x 2 z -4 x_projected 1 * 2 / 4 0.5可以看到两个点原本的 x 坐标相同但远处的点投影后x 坐标只有近处点的一半。这就是因为距离越远除数越大最终得到的投影坐标就越小。对于物体来说它的各个点都会受到这种深度缩放的影响所以距离相机越远投影到屏幕上的大小通常也就越小。这就是透视投影产生近大远小效果的原因。我们可以将透视投影理解为两个步骤将透视投影的视锥体变换为一个长方体。再使用正交投影将这个长方体映射到标准立方体。一、构建矩阵转化为长方形n面为近裁剪面z面为远裁剪面n到z的对方也就是相机可以看到的范围我们首先就要将这个棱台压缩到长方体根据相似三角形性质所以每一个点的xy都可以使用x y来表示。x (n / z) * xy (n / z) * y当投影平面距离 n 固定时投影坐标与深度 z 成反比。当 z 较小时n / z 较大投影后的坐标偏移更大。当 z 较大时n / z 较小投影后的坐标偏移更小。这里要注意压缩后的x和y可以通过最近面的相似三角形性质来推导出来除了近裁剪面和远裁剪面的z是不变的中间的z坐标是会随着距离的增加而受到不同程度的压缩是无法通过相似三角形表示出来的。因此同样大小的物体距离相机越远投影到屏幕上的尺寸就越小这就是透视投影中的近大远小现象。也就是说某一个点xyz1要应用某一个矩阵后要变成nxnyunknown不知道z的形式所以我们就可以先推出来矩阵的大概为因为z坐标是不知道的所以第三行的值暂时无法推导出来。这个时候可以利用到近裁剪面和远裁剪面上面的点的z坐标是不会发生变化的。也就是近裁剪面上点xyn1的值经过经过矩阵变化后为nxnyn²n推导出A*nBn²。在利用远裁剪面的z坐标也不会发送改变。这里我们设远裁剪面的距离为f。、也就可以推导出另一个公式。A*nBf²。得出A值为nf得出B值为-nf所以压缩为长方体的矩阵就为M_persp | n 0 0 0 | | 0 n 0 0 | | 0 0 fn -f*n | | 0 0 1 0 |二、使用正交投影之后将这个矩阵再进行一次正交投影tblr分别为上边界下边界左边界右边界。① 挤压矩阵把棱台压成长方体M_persp_to_ortho | n 0 0 0 | | 0 n 0 0 | | 0 0 nf -nf | | 0 0 1 0 |② 正交投影矩阵代入对称条件 l -rb -t一般正交投影矩阵M_ortho | 2/(r-l) 0 0 0 | | 0 2/(t-b) 0 0 | | 0 0 2/(n-f) 0 | | 0 0 0 1 |代入对称条件 l -rb -t得到M_ortho | 1/r 0 0 0 | | 0 1/t 0 0 | | 0 0 2/(n-f) 0 | | 0 0 0 1 |如果需要将深度从 [-n, -f] 映射到 [-1, 1]还需要包含 Z 方向的平移项。对应的正交投影矩阵应为M_ortho | 1/r 0 0 0 | | 0 1/t 0 0 | | 0 0 2/(n-f) -(nf)/(n-f) | | 0 0 0 1 |③ 两者相乘得到最终透视投影矩阵M_persp M_ortho * M_persp_to_ortho最终结果M_persp | n/r 0 0 0 | | 0 n/t 0 0 | | 0 0 (nf)/(n-f) -2*n*f/(n-f)| | 0 0 1 0 |最后我们还可以使用含有fovY视场角aspect宽高比的版本最后的公式可以表示为代入得到用视场角FOV表示的透视投影矩阵M_persp | 1 / (aspect * tan(fovY / 2)) 0 0 0 | | 0 1 / tan(fovY / 2) 0 0 | | 0 0 (nf) / (n-f) -2*n*f/(n-f) | | 0 0 1 0 |