CG-02: 从2D变换到3D变换

37 阅读2分钟

如果要看懂本文,需要了解简单的矩阵运算,包括乘法、逆矩阵、转置、正交矩阵这些概念。

下面内容纯手打,无AI,请放心食用。

3D坐标系

2D坐标系是x轴、y轴相互垂直,并且向右 向上为正,为了方便我们讨论,也要先规定一下3D中的x y z轴的方向。

左右手坐标系如何理解?

简单来说,左右手坐标系就是为了规定旋转的正方向。对于2D我们说一个向量旋转的正方向,那么默认就是逆时针方向。但是在3D中绕轴旋转 θ\theta,我们必须确定 θ\theta 朝哪边旋转才是正的。判断方式也很简单,类似于我们初中学过的右手螺旋定则:

image.png

图片来源:juejin.cn/post/722038…

使用哪种规定并没有正确错误,只不过是不同的领域的习惯不同,大家都遵守某个习惯,后续讨论可以免于很多重复说明的麻烦而已,不用过分纠结。

3D变换

对于平移和缩放,直接类比2D的变换矩阵

平移:

T(x,y,z)=[100x010y001z0001] T(x,y,z)= \begin{bmatrix} 1 & 0 & 0 & x\\ 0 & 1 & 0 & y\\ 0 & 0 & 1 & z\\ 0 & 0 & 0 & 1 \end{bmatrix}

css transform写法:transform: translateX(x) translateY(y) translateZ(z)

缩放:

S(sx,sy,sz)=[sx0000sy0000sz00001]S(s_x,s_y,s_z)= \begin{bmatrix} s_x & 0 & 0 & 0\\ 0 & s_y & 0 & 0\\ 0 & 0 & s_z & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}

css中3d缩放用的是scale3d,但是由于大部分元素都是平面的,缩放z轴看不到有什么变化。如果一个面已经被3d旋转了,再进行scaleZ 是可以看到效果的

绕坐标轴旋转

如绕X轴,当一个向量绕X轴旋转的时候,它的x坐标不变,那么我们可以把它看作是在y-z平面旋转 θ\theta , 回想2D旋转的矩阵,旋转 θ\theta的矩阵为 ...

那么我们在3D的4x4齐次矩阵中,保持第一行和第一列不变(x不变):

[10000??00??00001] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & ? & ? & 0\\ 0 & ? & ? & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}

把2D旋转矩阵的内容入剩下的位置,得到:

Rx(θ)=[10000cosθsinθ00sinθcosθ00001]R_x(\theta)= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & \cos\theta & -\sin\theta & 0\\ 0 & \sin\theta & \cos\theta & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}

但是绕Y轴不太一样,因为在右手坐标系下,Z轴旋转到X轴才是正方向,但是我们说的旋转是X旋转到Z,所以旋转部分这里得取一个负号,也就是 θ\theta 变成 θ- \theta

使用html+CSS实现一个立方体

可以利用上面学到的一些变换,来实现一个立方体,思路很简单,创建6个面,分别把他们进行平移、旋转得到前后左右上下6个面,注释在codepen代码里,感兴趣的自己看。 可以参考这个文章来实现:dev.to/joeattardi/…

绕任意轴旋转

3D旋转必须指定一个旋转轴和一个角度,对于一个旋转,我们可以把它分解成绕x、绕y和绕z轴三个方向的旋转,也就是:

R=Rx(α)Ry(β)Rz(γ)R = R_x(\alpha)R_y(\beta)R_z(\gamma)

感兴趣的可以看下 Rodrigues旋转公式,定义了绕任意轴旋转的变换矩阵。

MVP变换

我们想象一下,拍一张照片需要做什么?

  1. 调整好物体的位置
  2. 摆放好相机
  3. 按下快门

上面这三个步骤分别对应三个变换,分别是模型变换(Model)、视图变换(View)以及投影(Projection)。

任何3D的物体要想渲染在平面中,都需要经过这三个步骤变换。

相机位置视角的约定

在图形学里,我们一般假定相机不动,世界/物体做逆向变换。这个也好理解,物体和相机一起移动,成像不变,那么我们可以把相机往上移动,看成是相机不动,物体向下移动。这样的好处是,固定一个因素,其他计算可以稍微简化一点。

相机的成像取决于三个因素:

  1. 相机的位置 ee
  2. 相机镜头的方向 gg
  3. 向上的方向 tt 。因为镜头是分上下的,你把镜头上下颠倒,拍出来的照片也是上下颠倒的。

我们也约定,相机固定在原点,向上方向为Y,看向-Z,那么就可以得到相机的坐标系三个方向:

r=g^×t^u=t^f=g^r = \hat g \times \hat t \\ u = \hat t\\ f = -\hat g

那么按照约定,需要将相机坐标系与世界坐标系对齐一下,也就是把让 gg 指向 z-z, tt 指向 yy, 与gt平面垂直的 g×tg \times t 指向 xx

这个变换并不好求,但是如果反过来,让 zz指向 g-g, yy指向ttxx指向 g×tg \times t 比较简单:

看下面这个单位阵,代表初始x y z轴的方向,第一列表示x的单位向量

I=[100010001]I = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

现在x的单位向量变成了 g×tg \times t 方向,那么第一列就可以写成

[(g×t)x(g×t)y(g×t)z] \begin{bmatrix} (g\times t)_x\\ (g\times t)_y\\ (g\times t)_z \end{bmatrix}

类似的,我们可以写出剩下两列

[(g×t)xtxgx(g×t)ytygy(g×t)ztzgz]\begin{bmatrix} (g\times t)_x & t_x & -g_x\\ (g\times t)_y & t_y & -g_y\\ (g\times t)_z & t_z & -g_z \end{bmatrix}

这个变换矩阵并不是我们想要的,我们想要的是他的逆变换,那么怎么求逆矩阵呢?其实这个矩阵是正交矩阵(三个列向量互相垂直,并且都是单位向量),他有一个很好的性质,就是他的逆矩阵就是他的转置:

R1=RTR^{-1}=R^T

投影(Projection)

为什么会有“一叶障目”这种现象?这是因为我们人眼看到物体符合近大远小的透视投影的效果。

正交投影可以理解为摄像机距离物体无限远,在这种情况下,不存在近大远小的效果,在工程领域常用在工程制图上(想一下小学做过的三视图问题)

这里有个很好的区分了两种投影方式:

image.png

可以看到对于透视投影,远近平面形成了一个锥形,我们叫做视锥(frustum),连接远近平面对应的点,相较于一点点就是相机的位置。对于正交投影,远近平面构成是一个长方体。

在图中可以看到,透视投影的成像是存在近大远小的,正交投影不管远近大小都一样,只存在遮挡关系。

正交投影

约定:相机在原点,看相 -Z 方向,相机向上方向为Y

正交投影的过程:

  1. 物体映射到NDC:先确定物体空间的前后左右上线6个平面,这6个平面构成一个长方体,我们想要把它首先移动到原点,然后缩放成一个 [1,1]3[-1, 1]^3的立方体,我们称之为正则(canonical)立方体。

image.png

这个变换比较简单,就是先平移,后缩放,两个变换矩阵相乘得到:

image.png

  1. 从NDC到屏幕

现在所有的点都在标准空间内了,对于一个点(x, y, z)如何显示到屏幕上呢?这里的z之后会用来计算遮挡关系,现在不管,那么只需要关注(x, y)。

标准空间中 x[1,1]x \in [-1, 1] ,我们想把他映射到屏幕上 xscreen[0,width]x_{screen} \in [0, width]?怎么做到呢?就是先向右平移1,再把x的范围从2变成1,然后再乘width进行缩放。也就是下面的公式,y坐标也是类似的

xscreen=(xndc+1)2×widthx_{screen} = \frac{(x_{ndc} + 1)}{2} \times width

透视投影

由于存在近大远小,一个人站在铁轨上向远处看,平行的铁轨看起来相交于一点。

透视投影的过程:

  1. 把视锥的远平面压缩,得到一个长方体

image.png

第一步压缩远平面,坐标变成什么了呢?看下面这个图,他是上面视锥的侧面,(x,y,z)(x, y, z) 代表原来视锥中任意一点,n代表近平面到相机距离。 image.png

首先我们要知道的是,远平面被压缩,z的坐标肯定不变,那么就看x y,看上图存在一个相似三角形,所以得到这个比例:

yy=nz\frac{y'}{y} = \frac{n}{z}

所以就得到,压缩后y坐标变成了:

y=nzyy' = \frac{n}{z}y
  1. 在对这个长方体做上面正交投影, 这里不再赘述。

可以理解 透视投影 = 压缩远平面 + 正交投影

参考

  • 本文内容主要参考了 GAMES 101的Lecture 03, 04。如果有不懂的,推荐去看闫令琪老师的视频。
  • 对于其中的 推导过程,个人认为首次理解一下,后续记住结论就行。