# 一、相机
想要观察 3 维的世界,首先需要确定在哪里观察,这个哪里指的是相机的位置也即视点,此外还需要确定相机往那个方向拍照,为此需要确定观察点,将观察点与视点连线即可确定拍照的方向。最后还需要确定相机的朝向,是正着拍还是倒着拍,绕着 z 轴转了多少度,描述相机的朝向即上方向。当有了视线和上方向就可以对相机建系,另一个轴用视线叉乘上方向获得,由此就可以对相机建系。
当我们看一个物体时,这时我们向右移动相当于此时我们静止然后物体向左移动。如果有一个相机一开始在世界坐标系的原点,上方向为 y 轴,视线为 - z 轴,此时移动相机,假设这个移动包含平移和旋转,则可以使用一个四阶矩阵描述这次运动,假设这个矩阵是 A。如果有另一个相机没有动,只是 3d 世界上的物体发生了移动,而这个移动如果正好的 A 的逆矩阵的话,此时两个相机看到的画面将会是一样的。因此当我们求出 A 的逆矩阵,再对每一个物体的每一个顶点左乘 A 的逆矩阵,就相当于相机进行了矩阵 A 的运动。如果运动后的相机左乘这个 A 的逆矩阵,便会归回原点。所以我们需要求出一个相机归回原点的矩阵,然后再让空间的每一个物体每个顶点左乘这个矩阵。
想让相机归回原点有两个步骤
- 将相机平移到坐标原点。
假设这个相机的视点是 (x0,y0,z0), 左乘以下的矩阵就能将相机平移至原点。
- 将相机的上方向旋转至 (0,1,0), 将视线旋转至 (0,0,-1)。
假设上方向为 v2 (x2,y2,z2), 视线为 v3 (x3,y3,z3)。此时视线与上方向叉乘获得相机的 x 轴记作 v1,v1=v3xv2.
由于旋转矩阵是正交矩阵所以 A 的逆矩阵就是 A 的转置。最后将上面的平移矩阵左乘这个 A 矩阵就是视图矩阵。空间内的顶点需要左乘这个视图矩阵。另外如果物体了移动,有个移动矩阵记作 B,就让顶点坐标先左乘移动矩阵 B 再左乘视图矩阵 A。当然矩阵有结合律,可以先将 AB 结合成一个新的矩阵记作模型视图矩阵,坐标左乘这个模型视图矩阵即可。
# 二、投影
三维世界的物体最终需要在二维平面 (屏幕) 上显示,这时候需要经过投影将三维物体投影至二维平面上。投影的方式有两种,一种是正交投影,物体显示在平面上的大小不会因为相机的位置发生改变,适合观察各类模型。另一种是透视投影,物体显示在平面上的大小会因为相机的位置发生改变,呈现近大远小,符合人眼看世界。
# 1. 正交投影
正交投影有点像将三维物体拍扁,当三维物体每一个点将 z 分量设置为零后,就相当于将物体拍扁在 xoy 平面上。在正交投影下,用户所能观察的区域是一个长方体。长方体分为近面和远面。因为在此之前已经对相机进行了归位,就是将顶点左乘了视图矩阵相当于将相机归位。此时相机在原点,面向 - z 轴,朝向为 y 轴,所以此时观察的区域近面和远面是垂直于 z 轴的。之后需要将可视区域从长方体压缩成边长为 2,中心在原点的立方体。之后会对这个立方体进行投影,当然这个过程不是我们开发者做的,之后再根据用户屏幕的显示进行缩放。
假设近面距离原点为 n,远面距离原点的距离为 f,近面的上下左右的坐标分别为,t,b,l,r。注意这个长方体不一定是 yoz 和 xoz 平面对称的,也即 t 可能不等于 - b,l 可能不等于 - r,将这个长方体平移到原点然后压缩成立方体同样有两个步骤。首先平移到原点然后再压缩。
- 平移到原点
这个长方体中间的位置为 (n+f)/2。对这个长方体的 x 分量移动 (r+l)/2,y 分量移动 (t+b)/2,z 分量移动 (n+f)/2,
- 缩放成立方体
# 2. 透视投影
透视投影与正交投影不同的是,在透视投影下物体会呈现近大远小的特点,可视区域为四棱台。需要先将四棱台压缩成长方体然后再左乘一个正价变换矩阵。
- 压缩成长方体

分别在 zoy 和 zox 平面观察四棱台。可以发现 oan 和 obc 是相似三角形。也即 bc/an=oc/on。y/y1=z/n。y1=yn/z。其中 y1 为压缩后的 y。对于 x 分量也一样。x1=xn/z。也就是空间中有一点 (x,y,z) 在经过透视投影矩阵的变换后的坐标为 (nx/z,ny/z,z)。有一点比较讨厌的是,这个 z 是变量与顶点的坐标相关。之后这个投影矩阵还要右乘模型视图矩阵形成 MVP 矩阵。提前将矩阵相乘能够减少运算量,这样顶点就不必每次都乘三个矩阵,乘一个就够了,对于每个顶点来说就少了两次四维向量乘四阶矩阵的运算。对于同一个物体每个顶点来说 MVP 矩阵应该是相同的,MVP 矩阵应该要只与物体移动,相机,和投影相关与物体的顶点位置无关。所以这里要想办法将 z 去掉。这时就要用上齐次坐标。对于齐次坐标 (x,y,z,w) 而言,三维坐标为 (x/w,y/w,z/w)。可以发现如果我们将 w 变成 z,对于三维坐标不就相当于除于 z 了吗,所以需要找到一个矩阵使齐次坐标 (x,y,z,1) 变成 (nx,ny,z*z,z)。
我们很容易写出第一,二,四行。变换后的 z 明显与 x,y 无关,所以第三行第一二列为零。接下来就搞定第三行的三四列。这个地方使用待定系数法。这里取两个点一个在近平面,另一个在远平面。近平面 (x,y,n,1),远平面 (x,y,f,1)。射第三行第三列为 a,第四列为 b。
当然此时如果注意力够集中会发现一个问题就是,不对啊,(f+n)-fnz=z*z,(z-f)(z-n)=0。只有 z 是 n 或 f 才成立,或者说当点的 z 分量不是 n 或者 f 的话,投影后的 z 分量会发生变化。这时候就要讲 z 分量的意义了。z 最重要的是判别物体的远近。如果有两个物体 x 和 y 分量一致,z 分量小的会遮挡 z 分量大。换句话说我们在意 z 分量的具体大小,反正最后都会被投影到一块二维平面上。只要保证在经过投影矩阵变换后,z 分量在 n 到 f 之间,且两个点的 z 分量记作 z1 和 z2。投影前 z1<z2,投影后也要 z1<z2,就行。另外在透视投影下,z 分量还有一个作用就是确定缩放比例,z 的绝对值越大,缩放比例就越大。但是在完成投影矩阵变换后,此时 z 的大小就无所谓了。
- 左乘正交投影矩阵
将四棱锥压缩成长方体之后再进行正交投影即可。
# 三、绘制立方体
1 | 方法一、使用drawArrays方法绘制。但这样做要绘制24个顶点,而立方体一共8个顶点,每个顶点被三个面公用。 |