之前学习中对这部分的理解不深,简单补了一遍线代,就又温习了一遍 MVP 变换
MVP 变换简单来说就是将我们已经构建好的各种3维模型映射到屏幕这个2维坐标中, 参与 MVP 变换的信息包括点、矢量、法线、切线等
模型变换(Model):将模型空间转换到世界空间
观察变换(View):将世界空间转换到观察空间
投影变换(Projection):将观察空间转换到裁剪空间
最后要获取屏幕坐标还需要一步:屏幕映射,又叫视口变换
屏幕映射:获取对应屏幕的 2D 坐标
本质就是旋转,平移,缩放(见本文最后)
模型自身会携带模型坐标的原点信息等,再结合世界坐标还是很好变换的
在我的理解下,M
和 V
变换本质上是同一种类型的变换
比较快速的方法是把整个摄像机坐标空间移动到世界坐标使二者重合再反转 z 轴(不是真的移动摄像机),即在物体与摄像机相对位置不变的情况下让摄像机位于(0,0,0),将变换摄像机的矩阵作用于物体上就可得到观察空间中的坐标
ps:模型变换也能这么想
投影要为裁剪做准备
视野是有限的,所以在视野外(视锥外)的东西是不需要显示的,而用六个裁剪平面(视锥的六个面)直接判断相对复杂,所以需要投影操作将视锥变成裁剪空间,再通过齐次除法,将裁剪空间变成CVV(Canonical View Volume),OpenGL中CVV就是一个 [ − 1 , 1 ] 3 [-1,1]^3 [−1,1]3的正方体(下面讨论这种),DirectX中的 z 分量稍微不同是 [ 0 , 1 ] [0,1] [0,1],此时的坐标就是 NDC(Normalized Device Coordinates,归一化设备坐标)
不考虑 z 轴的情况下,投影是将所有点投影到近裁剪平面上(个人认为可以理解为摄像机拍到的画面,一张特殊的二维相片),然后放缩到 [ − 1 , 1 ] 2 [-1,1]^2 [−1,1]2的正方形平面
而 z 坐标,同样的放缩,但是还需要平移,因为旋向性改变了,z轴变正了(距离越远,z值越大)
所以投影阶段就已经将x,y
坐标和z
坐标(深度信息)割裂开来了,所谓的 CVV 可能是之前导致我困惑的一点,先分开分析再合并对我来说更好理解一些
如上图,正交投影的“视锥”是一个长方体,可以通过六个参数定义 right,left,top,bottom,near,far
分别对应了x,y,z轴上的范围(n和f是距离),从而构成一个长方体
正交投影的裁剪空间和齐次除法后的CVV是同一个东西就混着讲了
正交投影的XoY坐标不需要变化直接投射到近平面,然后缩放至 [ − 1 , 1 ] 2 [-1,1]^2 [−1,1]2
M o r t h o 结 构 [ a 0 0 0 0 b 0 0 0 0 c d 0 0 0 1 ] M_{ortho}结构\left[\begin{matrix}a&0&0&0\\0&b&0&0\\0&0&c&d\\0&0&0&1\\ \end{matrix}\right] Mortho结构⎣⎢⎢⎡a0000b0000c000d1⎦⎥⎥⎤
x,y是简单的放缩
a ⋅ x = x r − l 2 a·x=\frac{x}{\frac{r-l}{2}} a⋅x=2r−lx
a = 2 r − l a=\frac{2}{r-l} a=r−l2
y同理
z 考 虑 z 0 = − n , z n o r m a l i z e d = − 1 z考虑z_0=-n,z_{normalized}=-1 z考虑z0=−n,znormalized=−1
z 0 = − f , z n o r m a l i z e d = 1 z_0=-f,z_{normalized}=1 z0=−f,znormalized=1
{ ( − n ) c + d = − 1 ( − f ) c + d = 1 \left\{ \begin{aligned} (-n)c+d=-1\\ (-f)c+d=1 \end{aligned} \right. {(−n)c+d=−1(−f)c+d=1
解 得 c = − 2 f − n 解得c=-\frac{2}{f-n} 解得c=−f−n2
d = − f + n f − n d=-\frac{f+n}{f-n} d=−f−nf+n
M o r t h o = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] M_{ortho}=\left[\begin{matrix}\frac{2}{r-l}&0&0&0\\0&\frac{2}{t-b}&0&0\\0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\0&0&0&1\\ \end{matrix}\right] Mortho=⎣⎢⎢⎡r−l20000t−b20000−f−n2000−f−nf+n1⎦⎥⎥⎤
相对于正交投影,透视投影只是多了一条规则:近大远小,即 z 坐标越靠近近裁剪平面投影过来的 x,y 坐标受到的压缩更小
透视投影的视锥由4个参数定义,near,far,FOV,Aspect
(FOV 有 FOVx 和 FOVy 之分,Aspect是宽高比 w:h)
对于 XOZ 和 YOZ 平面来说,如下图
从上图可知,对于 xy 坐标只需要放缩,同时 z 坐标全变为 -n,与xy也没有关系
所以矩阵结构大致如下
M p e r s p = [ a 0 0 0 0 b 0 0 0 0 c d e h g i ] M_{persp}=\left[\begin{matrix}a&0&0&0\\0&b&0&0\\0&0&c&d\\e&h&g&i\\ \end{matrix}\right] Mpersp=⎣⎢⎢⎡a00e0b0h00cg00di⎦⎥⎥⎤
投影完我们需要将把xy坐标压缩到 [ − 1 , 1 ] 2 [-1,1]^2 [−1,1]2范围内
M p e r s p ( x e , y e , z e , 1 ) = ( x ′ , y ′ , z e ′ , 1 ) M_{persp}(x_e,y_e,z_e,1)=(x',y',z_e',1) Mpersp(xe,ye,ze,1)=(x′,y′,ze′,1)
近 平 面 w i d t h = 2 ⋅ n e a r ⋅ t a n F O V x 2 近平面width=2·near·tan\frac{FOV_x}{2} 近平面width=2⋅near⋅tan2FOVx
近 平 面 h e i g h t = w e i g h t A s p e c t = 2 ⋅ n e a r ⋅ t a n F O V x 2 ⋅ 1 A s p e c t 近平面height=\frac{weight}{Aspect}=2·near·tan\frac{FOV_x}{2}·\frac{1}{Aspect} 近平面height=Aspectweight=2⋅near⋅tan2FOVx⋅Aspect1
变换后的坐标
x ′ = − n x e z e ⋅ n ⋅ t a n F O V x 2 = − x e z e ⋅ t a n F O V x 2 x'=\frac{-nx_e}{z_e·n·tan\frac{FOV_x}{2}}=-\frac{x_e}{z_e·tan\frac{FOV_x}{2}} x′=ze⋅n⋅tan2FOVx−nxe=−ze⋅tan2FOVxxe
同 理 y ′ = − y e ⋅ A s p e c t z e ⋅ t a n F O V x 2 同理y'=-\frac{y_e·Aspect}{z_e·tan\frac{FOV_x}{2}} 同理y′=−ze⋅tan2FOVxye⋅Aspect
但是由于 xy 的变换涉及到 z e z_e ze 很难构建矩阵,所以利用齐次坐标赋予第4个变量其他意义
用数学表示就是让结果同时乘 − z e -z_e −ze
− z e ( x ′ , y ′ , z ′ , 1 ) = ( − z e x ′ , − z e y ′ , − z e z ′ , − z e ) -z_e(x',y',z',1)=(-z_ex',-z_ey',-z_ez',-z_e) −ze(x′,y′,z′,1)=(−zex′,−zey′,−zez′,−ze)
明显可得
a = 1 tan F O V x 2 = cot F O V x 2 a=\frac{1}{\tan\frac{FOV_x}{2}}=\cot\frac{FOV_x}{2} a=tan2FOVx1=cot2FOVx
b = A s p e c t tan F O V x 2 = A s p e c t ⋅ cot F O V x 2 b=\frac{Aspect}{\tan\frac{FOV_x}{2}}=Aspect·\cot\frac{FOV_x}{2} b=tan2FOVxAspect=Aspect⋅cot2FOVx
e = 0 , h = 0 , i = 0 , g = − 1 e=0 , h=0 , i=0 , g=-1 e=0,h=0,i=0,g=−1
z轴的矩阵参数分析方法和正交一样
{ z e = − n , z ′ = − 1 , − z e z ′ = z e = − n z e = − f , z ′ = 1 , − z e z ′ = − z e = f \left\{ \begin{aligned} z_e=-n,z'=-1,-z_ez'=z_e=-n\\ z_e=-f,z'=1,-z_ez'=-z_e=f \end{aligned} \right. {ze=−n,z′=−1,−zez′=ze=−nze=−f,z′=1,−zez′=−ze=f
{ ( − n ) c + d = − n ( − f ) c + d = f \left\{ \begin{aligned} (-n)c+d=-n\\ (-f)c+d=f \end{aligned} \right. {(−n)c+d=−n(−f)c+d=f
解 得 { c = − f + n f − n d = − 2 n f f − n 解得 \left\{ \begin{aligned} c=-\frac{f+n}{f-n}\\ d=-\frac{2nf}{f-n} \end{aligned} \right. 解得⎩⎪⎪⎨⎪⎪⎧c=−f−nf+nd=−f−n2nf
M p e r s p = [ cot F O V x 2 0 0 0 0 A s p e c t ⋅ cot F O V x 2 0 0 0 0 − f + n f − n − 2 n f f − n 0 0 − 1 0 ] M_{persp}=\left[\begin{matrix}\cot\frac{FOV_x}{2}&0&0&0\\0&Aspect·\cot\frac{FOV_x}{2}&0&0\\0&0&-\frac{f+n}{f-n}&-\frac{2nf}{f-n}\\0&0&-1&0\end{matrix}\right] Mpersp=⎣⎢⎢⎡cot2FOVx0000Aspect⋅cot2FOVx0000−f−nf+n−100−f−n2nf0⎦⎥⎥⎤
M p e r s p = [ cot F O V y 2 A s p e c t 0 0 0 0 cot F O V y 2 0 0 0 0 − f + n f − n − 2 n f f − n 0 0 − 1 0 ] M_{persp}=\left[\begin{matrix}\frac{\cot\frac{FOV_y}{2}}{Aspect}&0&0&0\\0&\cot\frac{FOV_y}{2}&0&0\\0&0&-\frac{f+n}{f-n}&-\frac{2nf}{f-n}\\0&0&-1&0\end{matrix}\right] Mpersp=⎣⎢⎢⎢⎡Aspectcot2FOVy0000cot2FOVy0000−f−nf+n−100−f−n2nf0⎦⎥⎥⎥⎤
负号有无看f-n
还是n-f
,再强调一遍 n 和 f 是距离
从推导中可以看出,透视投影后的空间仍然是在一个锥体中(每个图元各自乘自己的 z 坐标),这个空间是裁剪空间,所以可以认为先推导出 CVV 的公式再获得裁剪空间的变换矩阵
裁 剪 空 间 的 图 元 满 足 以 下 不 等 式 , 就 保 留 裁剪空间的图元满足以下不等式,就保留 裁剪空间的图元满足以下不等式,就保留
− z e ≤ x ≤ z e -z_e\leq x\leq z_e −ze≤x≤ze
− z e ≤ y ≤ z e -z_e\leq y\leq z_e −ze≤y≤ze
− z e ≤ z ≤ z e -z_e\leq z\leq z_e −ze≤z≤ze
这一步就要获取图元在屏幕上对应的像素坐标了
正交投影直接获得了 CVV(准确来说是裁剪空间和CVV一样,齐次除法后空间不变),而透视投影需要通过齐次除法(透视除法)来获得 CVV
明白透视投影矩阵的推导过程就很容易理解,透视投影的裁剪空间和 CVV 就差了一个乘除操作 − z e -z_e −ze,而裁剪空间图元的第四个数就是 − z e -z_e −ze,所以把裁剪空间图元的 xyz 除以第 4 个数就可获得其在 CVV 中的坐标了
获得 CVV 后,z 坐标只用来判断深度大小,关注xy就行
获 取 屏 幕 坐 标 矩 阵 = [ w i d t h 2 0 0 w i d t h 2 0 h e i g h t 2 0 h e i g h t 2 0 0 1 0 0 0 0 1 ] 获取屏幕坐标矩阵=\left[\begin{matrix}\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{matrix}\right] 获取屏幕坐标矩阵=⎣⎢⎢⎡2width00002height0000102width2height01⎦⎥⎥⎤
这部分的数学推导相对简单就不再赘述
左上 3 ∗ 3 3*3 3∗3 的矩阵用于旋转和缩放,最后一列用于平移
由于平移不是线性变换,所以采用齐次坐标(齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示,是指一个用于投影几何里的坐标系统)
点 的 移 动 [ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] ⋅ [ x y z 1 ] = [ x + t x y + t y z + t z 1 ] 点的移动\left[\begin{matrix}1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1\\ \end{matrix}\right] · \left[\begin{matrix}x \\y\\z\\1 \end{matrix}\right]=\left[\begin{matrix}x+t_x\\y+t_y\\z+t_z\\1\\ \end{matrix}\right] 点的移动⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤⋅⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x+txy+tyz+tz1⎦⎥⎥⎤
矢 量 移 动 [ 1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1 ] ⋅ [ x y z 0 ] = [ x y z 0 ] 矢量移动\left[\begin{matrix}1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1\\ \end{matrix}\right] · \left[\begin{matrix}x \\y\\z\\0 \end{matrix}\right]=\left[\begin{matrix}x\\y\\z\\0\\ \end{matrix}\right] 矢量移动⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤⋅⎣⎢⎢⎡xyz0⎦⎥⎥⎤=⎣⎢⎢⎡xyz0⎦⎥⎥⎤
绕 x 轴 : [ 1 0 0 0 0 c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 ] 绕 x 轴: \left[\begin{matrix}1&0&0&0\\0&cosθ&-sinθ&0\\0&sinθ&cosθ&0\\0&0&0&1\\ \end{matrix}\right] 绕x轴:⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤
绕 y 轴 : [ c o s θ 0 s i n θ 0 0 1 0 0 − s i n θ 0 c o s θ 0 0 0 0 1 ] 绕 y 轴:\left[\begin{matrix}cosθ&0&sinθ&0\\0&1&0&0\\ -sinθ&0&cosθ&0\\0&0&0&1\\ \end{matrix}\right] 绕y轴:⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤
绕 z 轴 : [ c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 0 0 0 0 1 ] 绕 z 轴:\left[\begin{matrix}cosθ&-sinθ&0&0\\sinθ&cosθ&0&0\\0&0&1&0\\0&0&0&1\\ \end{matrix}\right] 绕z轴:⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤
[ k x 0 0 0 0 k y 0 0 0 0 k z 0 0 0 0 1 ] \left[\begin{matrix}k_x&0&0&0\\0&k_y&0&0\\0&0&k_z&0\\0&0&0&1\\ \end{matrix}\right] ⎣⎢⎢⎡kx0000ky0000kz00001⎦⎥⎥⎤
k x = k y = k z k_x=k_y=k_z kx=ky=kz时为统一缩放