我曾经在Shader山下(十六)坐标空间与转换矩阵中介绍过,一个物体要显示在平面上,需要经过四步空间变换(实际上是五步):
物体空间->世界空间->观察空间->裁剪空间(->归一化设备空间)->屏幕空间

(差不多就是这个意思)
实际上,在可编程渲染管线中,我们往往只需要操作从物体空间到裁剪空间的转换,可以用一个矩阵链乘M(odel)V(iew)P(rojection)表示。本文就介绍一下这三个矩阵。
索性全介绍了吧。

先简单介绍一下变换矩阵。

常用的变换矩阵有三种:平移矩阵、旋转矩阵和缩放矩阵。

平移矩阵:

如果一个物体在世界坐标系中的位置为(px,py,pz)(p_x,p_y,p_z),那么它的平移矩阵为:

T(p)=⎡⎣⎢⎢⎢⎢100001000010pxpypz1⎤⎦⎥⎥⎥⎥

T(p) = \begin{bmatrix} 1 & 0 & 0 & p_x \\ 0 & 1 & 0 & p_y \\ 0 & 0 & 1 & p_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

旋转矩阵:

绕x轴旋转θx\theta_x,旋转矩阵为:

Rx(θx)=⎡⎣⎢⎢⎢10000cosθxsinθx00−sinθxcosθx00001⎤⎦⎥⎥⎥

R_x(\theta_x) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta_x & -\sin\theta_x & 0 \\ 0 & \sin\theta_x & \cos\theta_x & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
绕y轴旋转 θy\theta_y,旋转矩阵为:

Ry(θy)=⎡⎣⎢⎢⎢⎢cosθy0−sinθy00100sinθy0cosθy00001⎤⎦⎥⎥⎥⎥

R_y(\theta_y) = \begin{bmatrix} \cos\theta_y & 0 & \sin\theta_y & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta_y & 0 & \cos\theta_y & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
绕z轴旋转 θz\theta_z,旋转矩阵为:

Rz(θz)=⎡⎣⎢⎢⎢cosθzsinθz00−sinθzcosθz0000100001⎤⎦⎥⎥⎥

R_z(\theta_z) = \begin{bmatrix} \cos\theta_z & -\sin\theta_z & 0 & 0 \\ \sin\theta_z & \cos\theta_z & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

缩放矩阵:

一个向量沿x、y、z轴分别缩放(qx,qy,qz)(q_x, q_y, q_z),那么缩放矩阵为:

S(q)=⎡⎣⎢⎢⎢⎢qx0000qy0000qz00001⎤⎦⎥⎥⎥⎥

S(q) = \begin{bmatrix} q_x & 0 & 0 & 0 \\ 0 & q_y & 0 & 0 \\ 0 & 0 & q_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

组合成变换矩阵

只需要将这些矩阵链乘即可。但是需要注意顺序。这里按照T(ranslate)R(otate)S(cale)和H(eading)P(itch)B(ank)的顺序进行链乘。
Q=T(p)Ry(θy)Rx(θx)Rz(θz)S(q)Q=T(p)R_y(\theta_y) R_x(\theta_x)R_z(\theta_z)S(q)
等等!为什么是4*4的矩阵?参见百度百科齐次坐标
简单来讲,3*3的矩阵只能表示旋转和缩放矩阵,为了表示平移,便扩充为4*4的矩阵。
当然点或向量要与矩阵运算时也要扩充为4维(x,y,z,w)。其中w=1表示点,w=0表示向量。因为向量只有方向没有位置,所以w=0便不会与矩阵第4行进行运算。

模型变换矩阵

这个本身没什么,其实就是相对位置(模型空间)到绝对位置(世界坐标)的一个变换。
已知模型的位置、旋转角度和缩放值,带入之前的公式,便可以得到相应的模型变换矩阵。
当要计算模型内部某个点的世界坐标时,只需要将这个点与变换矩阵相乘即可。

观察矩阵

摄像机的方位可以由四个向量表示,pp表示位置,dd表示观察方向,uu表示上方向,rr表示右方向。
在空间变换时,我们希望将摄像机移动到世界坐标原点,并且观察方向与z轴重合,上方向与y轴重合,右方向与x轴重合。当然世界中的物体也要随之变换。
2D平面演示:

设V为观察矩阵,那么就有:
Vp=(0,0,0)Vp=(0,0,0)
Vr=(1,0,0)Vr=(1,0,0)
Vu=(0,1,0)Vu=(0,1,0)
Vd=(0,0,1)Vd=(0,0,1)
因为摄像机没有大小,只有位置和方位,那么就表示,我们通过平移和旋转来完成这个变换。

首先是平移矩阵

没什么好说的:

T=⎡⎣⎢⎢⎢⎢100001000010−px−py−pz1⎤⎦⎥⎥⎥⎥

T = \begin{bmatrix} 1 & 0 & 0 & -p_x \\ 0 & 1 & 0 & -p_y \\ 0 & 0 & 1 & -p_z \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

旋转

我们只考虑矩阵的前三行和前三列即可,设为A:

Ar=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢rxryrz⎤⎦⎥=⎡⎣⎢100⎤⎦⎥

Ar = \begin{bmatrix} a_0 & a_1 & a_2 \\ a_3 & a_4 & a_5 \\ a_6 & a_7 & a_8 \\ \end{bmatrix} \begin{bmatrix} r_x \\ r_y \\ r_z \\ \end{bmatrix} =\begin{bmatrix} 1 \\ 0 \\ 0 \\ \end{bmatrix}

Au=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢uxuyuz⎤⎦⎥=⎡⎣⎢010⎤⎦⎥

Au = \begin{bmatrix} a_0 & a_1 & a_2 \\ a_3 & a_4 & a_5 \\ a_6 & a_7 & a_8 \\ \end{bmatrix} \begin{bmatrix} u_x \\ u_y \\ u_z \\ \end{bmatrix} =\begin{bmatrix} 0 \\ 1 \\ 0 \\ \end{bmatrix}

Ad=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢dxdydz⎤⎦⎥=⎡⎣⎢001⎤⎦⎥

Ad = \begin{bmatrix} a_0 & a_1 & a_2 \\ a_3 & a_4 & a_5 \\ a_6 & a_7 & a_8 \\ \end{bmatrix} \begin{bmatrix} d_x \\ d_y \\ d_z \\ \end{bmatrix} =\begin{bmatrix} 0 \\ 0 \\ 1 \\ \end{bmatrix}
联立可得:

AB=⎡⎣⎢a0a3a6a1a4a7a2a5a8⎤⎦⎥⎡⎣⎢rxryrzuxuyuzdxdydz⎤⎦⎥=⎡⎣⎢100010001⎤⎦⎥

AB = \begin{bmatrix} a_0 & a_1 & a_2 \\ a_3 & a_4 & a_5 \\ a_6 & a_7 & a_8 \\ \end{bmatrix} \begin{bmatrix} r_x & u_x & d_x \\ r_y & u_y & d_y \\ r_z & u_z & d_z \\ \end{bmatrix} =\begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}
那么可以看出 AA是BB的逆矩阵,由于 BB是标准正交矩阵(r⊥u⊥dr\bot{u}\bot{d},并为单位向量),所以其逆矩阵与其转置矩阵相等。即:

A=B−1=BT=⎡⎣⎢rxuxdxryuydyrzuzdz⎤⎦⎥

A = B^{-1} = B^T = \begin{bmatrix} r_x & r_y & r_z \\ u_x & u_y & u_z \\ d_x & d_y & d_z \\ \end{bmatrix}

最后整合一下:

V=TA=⎡⎣⎢⎢⎢⎢rxuxdx0ryuydy0rzuzdz0−p⋅r−p⋅u−p⋅d1⎤⎦⎥⎥⎥⎥

V = TA = \begin{bmatrix} r_x & r_y & r_z & -p\cdot r \\ u_x & u_y & u_z & -p\cdot u \\ d_x & d_y & d_z & -p\cdot d \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

投影矩阵

这个要比前两个要复杂一些。先说一下这个变换时做什么的。我们的屏幕实际上是一个2D平面,我们要显示3D物体,就要将3D坐标系投影到2D平面上。如果是正交摄像机,那么相对简单一点,使用降维打击便可以解决问题,但是如果是透视摄像机,那么就要考虑到透视问题,也就是近大远小。
著名的“鸽子为什么这么大”问题:

贴吧链接

透视投影

这里我们就要引入视锥体的概念,之前土圭垚㙓数学课(二)视锥体八个顶点的计算方法简单提到过这个视锥体。我们继续引用文章中的图片:

我们有下面这个等式:

⎡⎣⎢⎢⎢⎢xclipyclipzclipwclip⎤⎦⎥⎥⎥⎥=Mprojection⎡⎣⎢⎢⎢⎢xeyeyeyezeyeweye⎤⎦⎥⎥⎥⎥

\begin{bmatrix} x_{clip} \\ y_{clip} \\ z_{clip} \\ w_{clip} \\ \end{bmatrix}= M_{projection} \begin{bmatrix} x_{eye} \\ y_{eye} \\ z_{eye} \\ w_{eye} \\ \end{bmatrix}
投影变换的任务就是要把物体根据近大远小的规则转换到一个长方体。
然后转换到设备归一化(NDC)空间中:

⎡⎣⎢xndcyndczndc⎤⎦⎥=⎡⎣⎢⎢xclip/wclipyclip/wclipzclip/wclip⎤⎦⎥⎥

\begin{bmatrix} x_{ndc} \\ y_{ndc} \\ z_{ndc} \\ \end{bmatrix}= \begin{bmatrix} x_{clip}/w_{clip} \\ y_{clip}/w_{clip} \\ z_{clip}/w_{clip} \\ \end{bmatrix}
这是一个立方体空间:

从观察空间到NDC空间,会将x轴的范围从[l,r]压缩到[-1,1],y轴的范围从[b,t]压缩到[-1,1],z轴的范围从[n,f]压缩到[-1,1]。所以可以认为裁剪空间是从观察空间到NDC空间的一个中间状态(除此之外,会在这个空间内做裁剪操作)。
首先考虑如何将一个点映射到视锥体近平面上:

顶视图和右视图
从两张图,我们就可以得到计算 xpx_p和 ypy_p的公式:
xpxe=−nze\frac{x_p}{x_e}=\frac{-n}{z_e}
xp=−n⋅xeze=n⋅xe−zex_p=\frac{-n\cdot{x_e}}{z_e}=\frac{n\cdot{x_e}}{-z_e}
ypye=−nze\frac{y_p}{y_e}=\frac{-n}{z_e}
yp=−n⋅yeze=n⋅ye−zey_p=\frac{-n\cdot{y_e}}{z_e}=\frac{n\cdot{y_e}}{-z_e}
因为视图坐标系是右手坐标系,而裁剪空间是左手坐标系,所以才产生了这个负号。
因为在裁剪空间到NDC空间里会把向量的x,y,z分别处以w分量,所以投影矩阵的第四行为(0,0,-1,0)。

⎡⎣⎢⎢⎢xcyczcwc⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢...0...0...−1...0⎤⎦⎥⎥⎥⎡⎣⎢⎢⎢xeyezewe⎤⎦⎥⎥⎥

\begin{bmatrix} x_{c} \\ y_{c} \\ z_{c} \\ w_{c} \\ \end{bmatrix}= \begin{bmatrix} . & . & . & . \\ . & . & . & . \\ . & . & . & . \\ 0 & 0 & -1 & 0 \\ \end{bmatrix} \begin{bmatrix} x_{e} \\ y_{e} \\ z_{e} \\ w_{e} \\ \end{bmatrix}
即: wclip=−zclipw_{clip}=-z_{clip}
接着,我们利用线性关系将 xpx_p和 ypy_p映射给NDC空间的 xnx_n和 yny_n。
xn=2xpr−l−r+lr−lx_n=\frac{2x_p}{r-l}-\frac{r+l}{r-l}
yn=2ypt−b−t+bt−by_n=\frac{2y_p}{t-b}-\frac{t+b}{t-b}
带入 xpx_p和 ypy_p得到:
xn=(2nr−l⋅xe+r+lr−l⋅ze)/−zex_n=(\frac{2n}{r-l}\cdot{x_e}+\frac{r+l}{r-l}\cdot{z_e})/-z_e
yn=(2nt−b⋅ye+t+bt−b⋅ze)/−zey_n=(\frac{2n}{t-b}\cdot{y_e}+\frac{t+b}{t-b}\cdot{z_e})/-z_e
即:
xc=(2nr−l⋅xe+r+lr−l⋅ze)x_c=(\frac{2n}{r-l}\cdot{x_e}+\frac{r+l}{r-l}\cdot{z_e})
yc=(2nt−b⋅ye+t+bt−b⋅ze)y_c=(\frac{2n}{t-b}\cdot{y_e}+\frac{t+b}{t-b}\cdot{z_e})
于是我们得到矩阵:

⎡⎣⎢⎢⎢xcyczcwc⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢⎢2nr−l0.002nt−b.0r+lr−lt+bt−b.−100.0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢xeyezewe⎤⎦⎥⎥⎥

\begin{bmatrix} x_{c} \\ y_{c} \\ z_{c} \\ w_{c} \\ \end{bmatrix}= \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 & -1 & 0 \\ \end{bmatrix} \begin{bmatrix} x_{e} \\ y_{e} \\ z_{e} \\ w_{e} \\ \end{bmatrix}
虽然投影后,z值貌似没有意义,但是为了裁剪和深度测试,我们需要归一化z值,另外这样做也方便我们逆向转换。因为z值不依赖于x和y,所以我们可以得出下面的方程式:
zn=zc/wc=Aze+Bwe−zez_n=z_c/w_c=\frac{Az_e+Bw_e}{-z_e}
因为在观察空间中, wew_e等于1,所以:
zn=Aze+B−zez_n=\frac{Az_e+B}{-z_e}
带入近平面和远平面,可得:

⎧⎩⎨−An+bn=−1−Af+bf=1→{−An+B=−n−Af+B=f→⎧⎩⎨A=−f+nf−nB=−2fnf−n

\begin{cases} \frac{-An+b}{n}=-1 \\ \frac{-Af+b}{f}=1 \\ \end{cases} \to \begin{cases} -An+B=-n \\ -Af+B=f \\ \end{cases} \to \begin{cases} A = -\frac{f+n}{f-n} \\ B = -\frac{2fn}{f-n} \\ \end{cases}
最终我们得到投影矩阵:

Mprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢2nr−l00002nt−b00r+lr−lt+bt−b−f+nf−n−100−2fnf−n0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥

M_projection= \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{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \\ \end{bmatrix}
因为视锥体是上下左右对称的,也就是说r=-l且t=-b,借此,我们可以整理一下投影矩阵:

Mprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢nr0000nt0000−f+nf−n−100−2fnf−n0⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥

M_{projection}= \begin{bmatrix} \frac{n}{r} & 0 & 0 & 0 \\ 0 & \frac{n}{t} & 0 & 0 \\ 0 & 0 & -\frac{f+n}{f-n} & -\frac{2fn}{f-n} \\ 0 & 0 & -1 & 0 \\ \end{bmatrix}

正交投影

因为没有近大远小的效果,所以xcx_c和ycy_c的值也就与近切面无关,而且正交投影的观察空间本身就是长方体,所以在裁剪空间里,我们可以直接将空间压缩到[-1,1]的立方体内,也就不用再对w分量进行计算。于是我们就可以比较容易的推导出投影矩阵:

Morthographicprojection=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢1r00001t0000−2f−n000−f+nf−n1⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥

M_{orthographicprojection}= \begin{bmatrix} \frac{1}{r} & 0 & 0 & 0 \\ 0 & \frac{1}{t} & 0 & 0 \\ 0 & 0 & -\frac{2}{f-n} & -\frac{f+n}{f-n} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

视口矩阵

也就是从NDC空间到屏幕空间的变换,有些时候我们可以给屏幕做一个偏移量(同屏显示)(xoffset,yoffset)(x_{offset},y_{offset}),并设置宽度WidthWidth和高度HeightHeight,z轴还是从近平面nn到远平面ff。那么,x轴[−1,1]→[xoffset,xoffset+Width][-1,1]\to[x_{offset},x_{offset}+Width],y轴[−1,1]→[yoffset,yoffset+Height][-1,1]\to[y_{offset},y_{offset}+Height],z轴[−1,1]→[n,f][-1,1]\to[n,f]。
易得:

Mviewport=⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢Width20000Height20000f−n20xoffset+Width2yoffset+Height2f+n21⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥

M_{viewport}= \begin{bmatrix} \frac{Width}{2} & 0 & 0 & x_{offset}+\frac{Width}{2} \\ 0 & \frac{Height}{2} & 0 & y_{offset}+\frac{Height}{2} \\ 0 & 0 & \frac{f-n}{2} & \frac{f+n}{2} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}
注: wndc=1w_{ndc}=1


参考文献:

  1. DirectX 9.0 3D游戏开发编程基础
  2. 3D数学基础:图形与游戏开发
  3. OpenGL Transformation

PS:写了这么多累死了,早知道拆成两三篇写了。第一次用markdown编辑器,真TM好用,早就该用这个了。

土圭垚㙓数学课(四)空间变换相关推荐

  1. 土圭垚㙓数学课(二)视锥体八个顶点的计算方法

    视锥体是摄像机可见的空间,看上去像截掉顶部的金字塔.视锥体由6个裁剪面围成,构成视锥体的4个侧面称为上左下右面,分别对应屏幕的四个边界.为了防止物体离摄像机过近,设置近切面,同时为了防止物体离摄像机太 ...

  2. 土圭垚㙓数学课(一)万向锁(Gimbal Lock)

    何为万向锁,我们先抛弃掉那些理论,直接举个栗子. 以你自身为对象. 首先,原地旋转为第一个维度,也就是heading. 其次,正翻跟头为第二个维度,也就是pitch. 然后,侧翻跟头为第三个维度,也就 ...

  3. 土圭垚㙓数学课(三)四元数

    何为四元数?讲解四元数的文章往往会把四元数跟复数联系在一起.诚然,四元数的起源跟复数有关系,但是理解复数系统并不是理解四元数的首要条件. 提到四元数,我们首先要提到一个人--莱昂哈德·欧拉(Leonh ...

  4. Unity Shader入门精要笔记(四):矩阵与空间变换

    本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢. http://blog.csdn.net/lzhq1982/article/details/73612170 上一篇我们学习 ...

  5. VTK修炼之道9:坐标系统及空间变换(窗口-视图分割)

    1.坐标系统 计算机图形学里常用的坐标系统主要有四种,分别是:Model坐标系统.World坐标系统.View坐标系统和Display坐标系统,以及两种表示坐标点的方式:以屏幕像素值为单位和归一化坐标 ...

  6. Opencv——几何空间变换(仿射变换和投影变换)

    几何空间变换 [1]几何变换(空间变换)简述 [2]变换矩阵知识简述 齐次坐标的概念 几何运算矩阵 [3]图像的仿射变换 1.平移变换 2.比例缩放 3.旋转 4.对称变换(不做展示) 1.关于X轴变 ...

  7. 图形学笔记(四) 数学变换

    数学变换 点和坐标 什么是点? 注意区分图形学中的点与图论中的点:图论中的点重视点与点的拓扑结构,而不关心点的坐标,而图形学中的点没有严格限制拓扑结构,且及其关注点的坐标. 在几何学中,点是最简单的形 ...

  8. 形象理解线性代数(三)——列空间、零空间(核)、值域、特征值(特征向量)、矩阵与空间变换、矩阵的秩

    这里,我们还是要以 形象理解线性代数(一)--什么是线性变换?为基础.矩阵对向量的作用,可以理解为线性变换,同时也可以理解为空间的变换,即(m*n)的矩阵会把一个向量从m维空间变换到n维空间. 一.矩 ...

  9. OpenGL学习——入门篇 第三章 四个变换及模拟地球公转

    一.四个变换 1.1 视图变换:不同位置观察它: 涉及函数: glMatrixMode(GL_MODELVIEW);//设置当前操作的矩阵为"模型视图矩阵" glLoadIdent ...

最新文章

  1. python常见错误集合
  2. vue-cli安装笔记
  3. 2021-01-07 matlab数值分析 数值积分与数值微分 复合梯形公式 复合Simpson公式
  4. angr学习笔记(7)(malloc地址单元符号化)
  5. Synchronize使用
  6. http接口测试工具——RESTClient
  7. C语言enum(枚举)、指针、函数指针
  8. 电脑主机,晚上就煎肉,把隔壁宿舍都馋哭了!
  9. 浅谈块级元素和行级元素的相对定位和绝对定位问题
  10. 将jOOQ与Spring结合使用:代码生成
  11. python安装scipy出现红字_windows下安装numpy,scipy遇到的问题总结
  12. CS224d lecture 7札记
  13. kubeadm源码分析(kubernetes离线安装包,三步安装)
  14. 【费用预测】基于matlab粒子群算法优化ELM神经网络预测费用【含Matlab源码 1378期】
  15. MyEclipse10安装properties文件插件
  16. 一套C#图书管理系统源码 书籍借还登记统计系统源码
  17. c语言合法的用户字符,在C语言中下列合法的字符常量是
  18. 销售型呼叫中心系统特点
  19. 任意输入一个年份 判断是否为闰年
  20. Java线程状态转化

热门文章

  1. 如何正确理解对微信营销的认知
  2. EBYTE ROLA通信模块初步学习
  3. uniapp 一键登录
  4. LED32*32点阵书写屏设计方案
  5. 操作系统实验(1)—— Linux命令解释程序设计与实现
  6. python ip反查询_python ip反查域名
  7. 4月10日服务器例行维护公告,4月14日服务器例行维护公告
  8. 彩虹表破解Hash算法
  9. 自信心、自制力。Java
  10. 全国3000多名医护人员感染新冠,医疗机器人与智能技术提供解决方案