向量、矩阵、三维变换及四元数

向量、矩阵、三维变换及四元数

数学概念,特别是矩阵变换相关的数学知识一直是困扰图形学初学者的一大难关。即使看了大量的相关资料,看起代码或动手写代码的时候,还是感到力不从心。当遇到world/view/proj等矩阵的典型应用场景的时候,尚且还能简单调用几个API完成任务。但是一遇到稍微复杂一点的场景,就感觉对相关数学概念理解太过浅陋。本文尝试深入剖析一番矩阵变换和投影,偏重理解思路,讲求实用技巧。细细体会可加深对三维变换的理解。当然本文都是我自己学习相关数学知识过程中的一些心得,理解不对的,还请指出。

本文采用D3D左手坐标系,向量采用行向量,透视投影深度范围为\left[ 0, 1 \right]

1. 向量与点

向量无所谓位置,可以表示为\left[ px, py, pz, 0 \right] ;点用行向量表示为\left[ px, py, pz, 1 \right]
,称为齐次坐标

经常还用到非齐次坐标\left[ px, py, pz, w \right] \left( w\ne 0 \right) ,其表示的点是\left[ \frac{px}{w}, \frac{py}{w}, \frac{pz}{w}, 1  \right]

向量和点的区别在于,点可以平移,而向量无所谓平移。向量可以理解为原点向空间一点的矢量,即 \left[ px, py, pz, 0 \right] = \left[ px, py, pz, 1\right] - \left[0, 0, 0, 1 \right]

2. 线性插值

二维直线上取两点\left( px1, py1 \right) , \left( px2, py2 \right)
,该直线上任何一点\left( px, py \right)满足:

\frac{px - px1}{px2 - px1}  = \frac{py - py1}{py2 - py1}

3. 基本变换

常用的基本变换包括平移、旋转、伸缩等。理解基本变换应该从基变换出发。所谓基变换,就是变换矩阵对单位矩阵基向量的作用方式。

对于任意向量v=\left[x,y,z\right],都可以写成v=[x,y,z]\times\begin{bmatrix}
1 & 0 & 0\\
0 & 1 & 0 \\
0 & 0 & 1
\end{bmatrix},而向量与矩阵的乘积可表示为vM=\begin{bmatrix}
x & y & z
 \end{bmatrix} \times
\begin{bmatrix}
1 & 0 & 0\\
0 & 1 & 0\\
0 & 0 & 1
 \end{bmatrix} \times
\begin{bmatrix}
m_{11} & m_{12} & m_{13} \\
m_{21} & m_{22} & m_{23} \\
m_{31} & m_{32} & m_{33}
\end{bmatrix}
=
\begin{bmatrix}
x & y & z
\end{bmatrix} \times
\begin{bmatrix}
\vec {m_{1}} \\
\vec {m_{2}} \\
\vec {m_{3}}
\end{bmatrix}

即:转换矩阵的每一行都能解释成将原基向量经矩阵转换后的新基向量。

例如:

平移可以表示如下:

\begin{bmatrix} px & py & pz & 1 \end{bmatrix} \times

 \begin {bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
off_{x} & off_{y} & off_{z} & 1
 \end{bmatrix}= \begin{bmatrix} px + off_{x} & py + off_{y} & pz + off_{z} & 1\end{bmatrix}

伸缩可以表示如下:

\begin{bmatrix} px & py & pz & 1 \end{bmatrix} \times

 \begin {bmatrix}
scale_{x} & 0 & 0 & 0 \\
0 & scale_{y} & 0 & 0 \\
0 & 0 & scale_{z} & 0 \\
0 & 0 & 0 & 1
 \end{bmatrix}= \begin{bmatrix} px * scale_{x} & py * scale_{y} & pz * scale_{z} & 1\end{bmatrix}

注意观察变换矩阵相应分量对平移和伸缩变换的作用。变换矩阵最后一行行向量row_{3}的作用是分别对原基向量x, y, z做平移;变换矩阵对角元素的作用是对原基向量做伸缩。

从模型坐标转换为世界坐标的世界矩阵WorldMatrix表示如下:\begin{bmatrix}
xaxis.x & xaxis.y & xaxis.z & 0\\
yaxis.x & yaxis.y & yaxis.z & 0 \\
zaxis.x & zaxis.y & zaxis.z & 0\\
pos.x & pos.y & pos.z & 1
\end{bmatrix}

观察该矩阵发现,行向量row_{0}, row_{1}, row_{2}分别是各模型坐标基向量变换到世界坐标后的单位向量,而pos是模型原点对世界坐标系原点的偏移。

对于绕轴v的旋转来说,可以把向量分解为(v_{\bot}, v_{||})两个分量,只有垂直分量对旋转结果有贡献。另一个理解方式是把旋转分解为分别对x, y, z轴的旋转yaw, pitch, roll,旋转矩阵就是这三个矩阵的积。

下面再看视图矩阵viewMatrix的构造。给定CameraEye, At, Up三个D3DXVECTOR3向量,调用D3DXMatrixLookAtLH()可得到ViewMatrix为[1]:

zaxis = normal(At - Eye) \\
xaxis = normal(cross(Up, zaxis))\\
yaxis = cross(zaxis, xaxis)\\
\\
\begin{bmatrix}
xaxis.x & yaxis.x & zaxis.x & 0 \\
xaxis.y & yaxis.y & zaxis.y & 0 \\
xaxis.z & yaxis.z & zaxis.z & 0 \\
-dot(xaxis, eye) & -dot(yaxis, eye) & -dot(zaxis, eye) & 1
\end{bmatrix}

xaxis: 世界坐标系中相机坐标系的基x
yaxis: 世界坐标系中相机坐标系的基y
zaxis
: 世界坐标系中相机坐标系的基z
\begin{bmatrix}
dot(xaxis, eye) & dot(yaxis, eye) & dot(zaxis, eye)

\end{bmatrix}: 世界坐标系中,原点指向eye的向量向基xaxis, yaxis, zaxis的投影,即旧原点向新原点的平移

对于镜头来说,局部坐标系(或模型坐标系)和视图坐标系是同一个坐标系。世界矩阵将局部坐标转换为世界坐标,同时也把视图坐标转换为世界坐标[9]。所以该视图矩阵和世界矩阵互为逆矩阵。容易验证ViewMatrix \times WorldMatrix = I(其中I为单位矩阵)

另外,我们发现上述计算的ViewMatrix各基向量xaxis, yaxis, zaxis分别处于列向量的位置上,这里不能把向量xaxis, yaxis, zaxis直接理解成对原基向量的变换。这是因为,从世界坐标向视图坐标转换时,转换后的点相对于转换前的点,在世界坐标中的位置并没有改变,只是观察基坐标发生了变化。此时应该将变换理解为世界坐标上一点向视图坐标上的投影,也就是该点对新坐标轴向量xaxis, yaxis, zaxis的投影。投影后的坐标为:dot(v, axis)-dot(axis, eye)=\begin{bmatrix}
dot(v, xaxis) \\
dot(v, yaxis)\\
dot(v, zaxis)
\end{bmatrix} ^{T} - \begin{bmatrix}
dot(xaxis, eye) \\
dot(yaxis, eye) \\
dot(zaxis, eye)
\end{bmatrix}^{T}


4. 投影变换

投影变换是将视图坐标变换到一个立方体空间的过程。有两种投影方式,透视投影和平行投影。平行投影的对象原先也在一个立方体中,所以投影过程就是一个线性的伸缩和平移变换。透视投影的对象则在一个棱台内[7],所以投影过程带有坐标轴的扭曲。

投影后的立方体可简单表示为六个面(左右,上下,远近):(l,r)(t,b)(zn,zf)

平行投影伸缩系数为scale = [\frac{2}{r-l} , \frac{2}{t-b}, \frac{1}{zf-zn}],平移表示为bias = [\frac{l+r}{l-r}, \frac{t+b}{b-t}, \frac{zn}{zn-zf}]。注意到,bias也经过了伸缩变换。若仅进行伸缩变换,实际是变换到原点为三个面\frac{l+r}{2}, \frac{t+b}{2}, {zn}的交点,立方体为(-1,1),(-1,1)(0,1)的范围内。最终正交变换矩阵为\begin{bmatrix}
\frac{2}{r-l} & 0 & 0 & 0\\
0 &\frac{2}{t-b} & 0 & 0\\
0 & 0 & \frac{1}{zf-zn} & 0\\
\frac{l+r}{l-r} & \frac{t+b}{b-t} & \frac{zn}{zn-zf} & 1
\end{bmatrix}

透视投影伸缩系数为scale=[\frac{2*zn}{r-l}, \frac{2*zn}{t-b}, \frac{zf}{zf-zn}]z坐标对x,y,z,w分量都有作用。转换后的坐标对\frac{1}{z}是线性关系。透视投影变换矩阵为\begin{bmatrix}
\frac{2*zn}{r-l} & 0 & 0 & 0\\
0 & \frac{2*zn}{t-b} & 0 & 0\\
\frac{l+r}{l-r} & \frac{t+b}{b-t} & \frac{zf}{zf-zn} & 1\\
0 & 0 & \frac{zn*zf}{zn-zf} & 0
\end{bmatrix}

注意上述都是offcenter情况下的一般情况,若变换到原点,可以令l+r=0, t+b = 0

下面介绍,给定透视投影矩阵M,如何得到viewfrustum各平面在view空间的表示方法[2]。

v'=vM\Rightarrow (x', y', z', w')=\begin{bmatrix}
v\bullet col_{1} &
v\bullet col_{2} &
v\bullet col_{3} &
v\bullet col_{4}
\end{bmatrix}

而投影后的立方体各面可表示为-w' < x' < w'\\
-w' < y' < w'\\
0 < z' < w',由此可得到viewfrustum各平面在view空间可表示为col_{4} \pm col_{1}, col_{4} \pm col_{2}, col_{4} \pm col_{3}

对于TBDR[3]中使用的平面方程也可由此得到,只是需使用offcenter情况下的一般矩阵。有篇文章[4]从平面法线的角度也得到了同样的结果。

另外需要提到的是,此节所讲的投影是投影到立方体,并没有丢失维度信息,所以和平移、旋转和缩放一样,都是可逆的。


5. 四元数与对偶四元数

单位四元数可用于旋转。该单位四元数可以表示为q=(q_{1},q_{2},q_{3},q_{4})=(
cos(\frac{\theta }{2}), 
sin(\frac{\theta}{2})*v_{x}, 
sin(\frac{\theta}{2})*v_{y},
sin(\frac{\theta}{2})*v_{z}
)

他对应一个以单位向量v=(v_{x}, v_{y}, v_{z})为轴旋转\theta角度的旋转操作。例如,对一个点P=(x,y,z),定义纯四元数w=(0, x, y, z),经四元数q旋转操作qwq^{-1}后也是一个纯四元数(0, x', y', z'),其中P'=(x',y',z')就是点P绕轴v旋转\theta角度后得到的。之所以使用纯四元数,是因为纯四元数代表了三维空间的一个点,而纯四元数经过上述旋转操作后得到的还是一个纯四元数,即还是一个三维空间中的点。

四元数和对偶四元数主要用于沿着旋转路径插值。

关于四元数的理解,可以看这篇文章[5]。对偶四元数,可参考[6][10]。

6. 参考文献:

[1] D3DXMatrixLookAtLH function (Windows)
[2] www8.cs.umu.se/kurser/5
[3] twvideo01.ubm-us.net/o1
[4] Tile frustum calculation for the point light culling
[5] 如何形象地理解四元数? - 计算机图形学
[6] 如何理解对偶四元数(dual quaternion)?为什么用在蒙皮动画中其效果比直接使用矩阵好? - 编程
[7] 深入探索透视投影变换
[8] 深入探讨透视投影坐标变换
[9] Understanding the View Matrix
[10] cs.gmu.edu/~jmlien/teac

注:转载请注明出处。

编辑于 2017-02-11

文章被以下专栏收录