跳到主要内容

05 - 视图,投影与视口变换

MVP变换

MVP变换就是将三维物体变换到二维中,就像拍照片一样:

  1. 模型变换(Model Transformation):模型空间 -> 世界空间,即摆好模型,搭好场景。
  2. 视图变换(View Transformation):世界空间 -> 相机空间,即给场景找到一个好的摄影角度。
  3. 投影变换(Projection Transformation):相机空间 -> 屏幕空间,即进行拍照,得到照片。

有关模型变换的内容详见上一篇文章

视图变换

首先,先定义相机(观测矩阵):

  • 相机的位置:e\mathbf{e}
  • 相机“看”的方向:g^\mathbf{\hat{g}}
  • 相机向上的方向:t^\mathbf{\hat{t}}

如何进行视图变换?把相机和它所拍摄的场景"移动"到一个规定好的位置即可,这个位置(右手系 )通常为世界坐标的原点,向上为Y,看的方向为-Z

“移动”的过程如下:

  1. 将相机e\mathbf{e}移动到原点(一个简单的平移+引入齐次坐标):

    Tview=[100xe010ye001ze0001]\nonumber \mathbf{T}_{view}= \begin{bmatrix} 1 & 0 & 0 & -x_e\\ 0 & 1 & 0 & -y_e\\ 0 & 0 & 1 & -z_e\\ 0 & 0 & 0 & 1\\ \end{bmatrix}
  2. 通过旋转,将t^\mathbf{\hat{t}}旋转到+Y+Y方向、将g^\mathbf{\hat{g}}旋转到Z-Z方向、将g^×t^\mathbf{\hat{g}}\times \mathbf{\hat{t}}旋转到+X+X方向。

    但是仔细想想,把一个方向不规范的坐标空间旋转成方向规范的坐标空间,所需的旋转矩阵应该不好写。因此,可以考虑从规范坐标空间(世界空间)旋转到当前坐标空间(相机空间)的逆矩阵(引入齐次坐标):

    Rview1=[0g^×t^t^g^001]=[xg^×t^xt^xg^0yg^×t^yt^yg^0zg^×t^zt^zg^00001]\begin{align} \nonumber \mathbf{R}^{-1}_{view}&= \begin{bmatrix} | & | & | & 0\\ \mathbf{\hat{g}}\times \mathbf{\hat{t}}& \mathbf{\hat{t}}& -\mathbf{\hat{g}}& 0\\ | & | & | & 0\\ | & | & | & 1\\ \end{bmatrix}\\ &= \nonumber \begin{bmatrix} x_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & x_{\mathbf{\hat{t}}} & x_{-\mathbf{\hat{g}}} & 0\\ y_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & y_{\mathbf{\hat{t}}} & y_{-\mathbf{\hat{g}}} & 0\\ z_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & z_{\mathbf{\hat{t}}} & z_{-\mathbf{\hat{g}}} & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \end{align}

    由于旋转矩阵是 正交的,那么它的逆矩阵就是它的转置矩阵。因此,最终得到的旋转矩阵为:

    Rview=[xg^×t^yg^×t^zg^×t^0xt^yt^zt^0xg^yg^zg^00001]\nonumber \mathbf{R}_{view}= \begin{bmatrix} x_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & y_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & z_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}}& 0\\ x_{\mathbf{\hat{t}}} & y_{\mathbf{\hat{t}}} & z_{\mathbf{\hat{t}}} & 0\\ x_{-\mathbf{\hat{g}}} & y_{-\mathbf{\hat{g}}} & z_{-\mathbf{\hat{g}}} & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix}

将平移操作和旋转操作组合(列向量从右往左),得到的最终矩阵如下:

Mview=RviewTview=[xg^×t^yg^×t^zg^×t^0xt^yt^zt^0xg^yg^zg^00001][100xe010ye001ze0001]\begin{align} \nonumber \mathbf{M}_{view}&=\mathbf{R}_{view}\mathbf{T}_{view}\\ \nonumber &= \begin{bmatrix} x_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & y_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}} & z_{\mathbf{\hat{g}}\times \mathbf{\hat{t}}}& 0\\ x_{\mathbf{\hat{t}}} & y_{\mathbf{\hat{t}}} & z_{\mathbf{\hat{t}}} & 0\\ x_{-\mathbf{\hat{g}}} & y_{-\mathbf{\hat{g}}} & z_{-\mathbf{\hat{g}}} & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & -x_e\\ 0 & 1 & 0 & -y_e\\ 0 & 0 & 1 & -z_e\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \end{align}

投影变换

接下来进行投影变换,将相机框内的东西拍成一张照片。投影主要分为 正交投影透视投影两种:

透视投影会带来“近大远小”的现象,而正交投影不会。

正交投影(Orthographic)

正交投影的正式做法如下:

  1. 定义一个立方体[左l, 右r], [下b, 上t], [远f, 近n],用它框住想要正交投影的所有物体。

  2. 通过平移和缩放,将该立方体标准化为[1,1]3\mathrm{[-1,1]^3}:

然后,上边变换用矩阵表示就是:

Mortho=[2rl00002tb00002nf00001][100r+l2010t+b2001n+f20001]=[2rl00r+lrl02tb0t+btb002nfn+fnf0001]\begin{align} \nonumber \mathbf{M}_{ortho}&= \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & 0\\ 0 & \frac{2}{t-b} & 0 & 0\\ 0 & 0 & \frac{2}{n-f} & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & -\frac{r+l}{2}\\ 0 & 1 & 0 & -\frac{t+b}{2}\\ 0 & 0 & 1 & -\frac{n+f}{2}\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \\ \nonumber &= \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l}\\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b}\\ 0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f}\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \end{align}

Ps:由于左右手系有些地方不同,如果结果不正确,尝试给Z轴相关的运算取个相反数。

透视投影(Perspective)

运用得最广泛,满足“近大远小”性质。透视投影可以看出是特殊的正交投影,用“远平面”和“近平面”框住物体,先把“远平面”向“近平面”挤压,挤压成类似于正交投影的小盒子,然后做一次正交投影。

透视投影的做法如下:

  1. 把“远平面(z)”向“近平面(n)”挤压。规定“近平面”永远不变;挤压时,“远平面”的z值不变,但他俩之间的要发生变化;“远平面”的中心点始终不变。

    在挤压过程中,x,y的比例关系(相似三角形)如下:

    y=nzyx=nzx\nonumber \begin{aligned} y'=\frac{n}{z}y \\ x'=\frac{n}{z}x \end{aligned}

    引入齐次坐标的概念。假设挤压前,远平面某点为(x,y,z,1)T(x,y,z,1)^\mathsf{T},经过挤压,它的坐标变为(nx/z,ny/z,?,1)T(nx/z,ny/z,?,1)^\mathsf{T}。根据齐次坐标的性质,该点还能被表示为(同乘z,z不为0)(nx,ny,?,z)T(nx,ny,?,z)^\mathsf{T}

    现在,挤压前后的点坐标都知道了,剩下求转换矩阵:

    M透视正交(4×4)[xyz1]=[nxny?z]\nonumber \mathbf{M}^{(4\times4)}_{\text{透视}\to\text{正交}} \begin{bmatrix} x\\ y\\ z\\ 1\\ \end{bmatrix}= \begin{bmatrix} nx\\ ny\\ ?\\ z\\ \end{bmatrix}

    求得:

    M透视正交(4×4)=[n0000n00????0010]\nonumber \mathbf{M}^{(4\times4)}_{\text{透视}\to\text{正交}}= \begin{bmatrix} n & 0 & 0 & 0\\ 0 & n & 0 & 0\\ ? & ? & ? & ?\\ 0 & 0 & 1 & 0\\ \end{bmatrix}

    由于近平面不变,近平面上某点(x,y,n,1)T(x,y,n,1)^\mathsf{T}也不变(n是近平面位置),于是有:

M透视正交(4×4)[xyn1]=[nxnyn2z]\nonumber \mathbf{M}^{(4\times4)}_{\text{透视}\to\text{正交}} \begin{bmatrix} x\\ y\\ n\\ 1\\ \end{bmatrix}= \begin{bmatrix} nx\\ ny\\ n^2\\ z\\ \end{bmatrix}

所以缺失的一行应该是这样子的:

[00AB][xyn1]=n2 \nonumber \begin{bmatrix} 0 & 0 & A & B \end{bmatrix} \begin{bmatrix} x\\ y\\ n\\ 1\\ \end{bmatrix}= n^2

解得

An+B=n2 An+B=n^2

由于远平面的中心点(0,0,f,1)T(0,0,f,1)^\mathsf{T}是始终不变的(f是远平面位置),同理有:

Af+B=f2 Af+B=f^2

两式联立,解得

A=n+fB=nf \nonumber \begin{aligned} A=n+f\\ B=-nf \end{aligned}

得到挤压时的转换矩阵:

M透视正交(4×4)=[n0000n0000n+fnf0010]\nonumber \mathbf{M}^{(4\times4)}_{\text{透视}\to\text{正交}}= \begin{bmatrix} n & 0 & 0 & 0\\ 0 & n & 0 & 0\\ 0 & 0 & n+f & -nf\\ 0 & 0 & 1 & 0\\ \end{bmatrix}
  1. 进行正交投影。

最终得到的透视投影矩阵为:

Mper=MorthoM透视正交=[2rl00r+lrl02tb0t+btb002nfn+fnf0001][n0000n0000n+fnf0010]=[2nrl0r+lrl002ntbt+btb000n+fnf2nfnf0010]=[nr0000nt0000n+fnf2nfnf0010](化简)\begin{align} \nonumber \mathbf{M}_{per}&=\mathbf{M}_{ortho}\mathbf{M}_{\text{透视}\to\text{正交}} \\ \nonumber &= \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l}\\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b}\\ 0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f}\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} n & 0 & 0 & 0\\ 0 & n & 0 & 0\\ 0 & 0 & n+f & -nf\\ 0 & 0 & 1 & 0\\ \end{bmatrix} \\ \nonumber &= \begin{bmatrix} \frac{2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0\\ 0 & \frac{2n}{t-b} & -\frac{t+b}{t-b} & 0\\ 0 & 0 & \frac{n+f}{n-f} & -\frac{2nf}{n-f}\\ 0 & 0 & 1 & 0\\ \end{bmatrix} \\ \nonumber &= \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0\\ 0 & \frac{n}{t} & 0 & 0\\ 0 & 0 & \frac{n+f}{n-f} & -\frac{2nf}{n-f}\\ 0 & 0 & 1 & 0\\ \end{bmatrix}(\text{化简}) \end{align}

如果观察的是Z轴的负方向(如OpenGL),那么投影矩阵是:

Mper=[nr0000nt0000n+fnf2nfnf0010]\nonumber \mathbf{M}_{per}= \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0\\ 0 & \frac{n}{t} & 0 & 0\\ 0 & 0 & \frac{n+f}{n-f} & \frac{2nf}{n-f}\\ 0 & 0 & -1 & 0\\ \end{bmatrix}

注意:

这里有问题,做完西交大图形学实验才知道这里的n,r,f啥的我搞混了,解释一下:左上角两个n对应上图的n\|\mathbf{n}\|,是摄像机到参考点的距离;右下角的n和f是近平面和远平面距离坐标轴的距离。

一些概念

视锥体(View Frustum)

视锥体是相机可能看到的空间体积,它的形状像金字塔,只是尖端被剪掉了。

衍生的定义还有:

  • 视野(Field Of View,FOV):视锥体截取的角度,分水平和垂直两种。
  • 宽高比(Aspect):视锥体矩形截面的宽度与高度的比值。

一般来说,知道宽高比和垂直的fovY,就能定义一个视锥体了。

还能利用它俩推出视锥某矩形截面的l,r,b,t:

透视矩阵也能写成这样:

Mpers=[1aspecttan(fovY/2)00001tan(fovY/2)0000Znear+ZfarZnearZfar2ZnearZfarZnearZfar0010]\nonumber \mathrm{M_{pers}}= \begin{bmatrix} \frac{1}{aspect·\tan(fovY/2)} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(fovY/2)} & 0 & 0 \\ 0 & 0 & \frac{Z_{near}+Z_{far}}{Z_{near}-Z_{far}} & -\frac{2Z_{near}Z_{far}}{Z_{near}-Z_{far}} \\ 0 & 0 & 1 & 0 \end{bmatrix}

屏幕(Screen)

  • 二维数组,元素为像素
  • 数组的大小就是屏幕的分辨率了
  • 一种典型的光栅(Raster)成像设备,光栅化(Rasterize)就是把成像内容画到屏幕上。

屏幕空间(Screen Space)

认为屏幕左下角是原点,向右是x,向上是y。

通常有以下规定:

  • 像素的坐标是(x, y)形式,x,y都是整数。
  • 像素坐标的范围是(0, 0)到(width-1, height-1)。
  • 像素的中心位置是(x+0.5, y+0.5)。
  • 整个屏幕覆盖(0,0)到(width, height)。

视口变换(Viewport)

将MVP变换后得到的“照片”映射到屏幕空间中。

先不管Z坐标,那么只需将[1,1]2[-1,1]^2内的内容变换到[0,width]×[0,height][0,width]\times[0, height]上就行:

Mviewport=[width200width20height20height200100001]\nonumber \mathbf{M}_{viewport}= \begin{bmatrix} \frac{width}{2} & 0 & 0 & \frac{width}{2}\\ 0 & \frac{height}{2} & 0 & \frac{height}{2}\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix}

参考资料