投影矩阵
什么是投影?为什么投影?
把3维对象投影到2维平面,相当于舍弃了z坐标。计算机屏幕是一个二维平面,需要把3D几何转换为2D图像形式。
如图舍弃了z坐标在xy平上的投影,但并不意味着z坐标无用,他通常被深度缓冲用于可见度测试。
投影公式将变换你的几何体到一个新的空间体中,称为规范视域体。为讨论起见,把它认为是从(-1, -1, 0)延伸至(1, 1, 1)的盒子,一旦所有顶点被映射到规范视域体,只有它们的x和y坐标被用于映射到屏幕上。
1.正交投影
如图可见,视域体由6个面定义:
l e f t : x = l left:x=l left:x=l
r i g h t : x = r right:x=r right:x=r
b o t t o m : y = b bottom:y=b bottom:y=b
t o p : y = t top:y=t top:y=t
n e a r : z = n near:z=n near:z=n
f a r : z = f far:z=f far:z=f
则有
{ x ∈ [ l , r ] y ∈ [ b , t ] z ∈ [ n , f ] \begin{cases}\\ \\ \\ \end{cases} \begin{gather*} x \in [l,r] \\ y \in [b,t] \\ z \in [n,f] \end{gather*} ⎩
⎨
⎧x∈[l,r]y∈[b,t]z∈[n,f]
我们将3个坐标轴分开考虑,并且计算如何沿着每个坐标轴将点从视域体映射到规范视域体。从x轴开始,视域体中的点的x坐标范围在[l, r],把它变换到范围在[-1, 1],则由 x ∈ [ l , r ] x \in [l,r] x∈[l,r] 可知:
l ≤ x ≤ r l\leq x\leq r l≤x≤r
然后各项减去 l l l,使左边的项变为0:
0 ≤ x − l ≤ r − l 0\leq x-l\leq r-l 0≤x−l≤r−l
现在有一端是0,于是可以设置我们期望的范围,x从-1到1的宽为2个单位,另一端的值要为2,所以把各项乘以 2 r − l \frac 2 {r-l} r−l2:
0 ≤ 2 x − 2 l r − l ≤ 2 0\leq \frac {2x-2l} {r-l} \leq 2 0≤r−l2x−2l≤2
注意:这里的 r − l r-l r−l 是视域体的宽度,因此始终是一个正数,所以不用担心不等号会改变方向.
然后,各项减去1就得到了我们期望的范围[-1,1]:
− 1 ≤ 2 x − 2 l r − l − 1 ≤ 1 -1\leq \frac {2x-2l} {r-l} -1\leq 1 −1≤r−l2x−2l−1≤1
将中间项写成一个单一的分数:
− 1 ≤ 2 x − r − l r − l ≤ 1 -1\leq \frac {2x-r-l} {r-l} \leq 1 −1≤r−l2x−r−l≤1
最后,把中间项分成两部分使它形如 p x + q px+q px+q 的形式,这样就可以简单的转换成矩阵形式:
− 1 ≤ 2 x r − l − r + l r − l ≤ 1 -1\leq \frac {2x} {r-l} -\frac {r+l} {r-l}\leq 1 −1≤r−l2x−r−lr+l≤1
这个不等式的中间项就是我们把x转换到规范视域体的公式:
x ′ = 2 x r − l − r + l r − l x'=\frac {2x} {r-l} -\frac {r+l} {r-l} x′=r−l2x−r−lr+l
同理,可得 y y y转换到规范视域体的公式:
y ′ = 2 y t − b − t + b t − b y'=\frac {2y} {t-b} -\frac {t+b} {t-b} y′=t−b2y−t−bt+b
最后是z的变换公式,z映射到范围为[0, 1]。z坐标最开始的范围在[n,f]:
n ≤ z ≤ f n\leq z \leq f n≤z≤f
各项减去n让下限变为0:
0 ≤ z − n ≤ f − n 0\leq z-n \leq f-n 0≤z−n≤f−n
我们要构造[0, 1]的范围,于是将各项除 f − n f-n f−n:
0 ≤ z − n f − n ≤ 1 0\leq \frac {z-n} {f-n} \leq 1 0≤f−nz−n≤1
注意f-n是视域体的深度所以绝对不会为负
最后,把它分成两部分使它形如px+q的形式:
0 ≤ z f − n − n f − n ≤ 1 0\leq \frac {z} {f-n} -\frac {n} {f-n} \leq 1 0≤f−nz−f−nn≤1
得到z的变换公式:
z ′ = z f − n − n f − n z'=\frac {z} {f-n} -\frac {n} {f-n} z′=f−nz−f−nn
综上可得,3个投影公式:
x ′ = 2 x r − l − r + l r − l y ′ = 2 y t − b − t + b t − b z ′ = z f − n − n f − n \begin{align*} x'&=\frac {2x} {r-l} -\frac {r+l} {r-l} \\ y'&=\frac {2y} {t-b} -\frac {t+b} {t-b} \\ z'&=\frac {z} {f-n} -\frac {n} {f-n} \end{align*} x′y′z′=r−l2x−r−lr+l=t−b2y−t−bt+b=f−nz−f−nn
写成矩阵形式:
P 0 = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 1 f − n − n f − n 0 0 0 1 ] P_0=\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 {1} {f-n} & -\frac {n} {f-n}\\ 0 & 0 & 0 & 1 \end{bmatrix} P0=⎣
⎡r−l20000t−b20000f−n10−r−lr+l−t−bt+b−f−nn1⎦
⎤
这样我们就到了正交投影矩阵了。
我在根据这个矩阵,进一步思考。首先,在可见空间中,摄像机定位在原点并且沿着z轴方向观看。其次,你通常希望你的视野在左右方向上延伸的同样远,并且在z轴的上下方向上也延伸的同样远。如果是这样的情况,那么z轴正好直接穿过你视域体的的中心,所以得到了 r = − l r = -l r=−l并且 t = − b t = -b t=−b。简单点说,就是把范围看做长度:
{ [ l , r ] ⟹ r − l = w 宽 [ b , t ] ⟹ t − b = h 高 \begin{cases} [l,r] \implies r-l =w_{宽} \\ [b,t] \implies t-b =h_{高} \end{cases} {
[l,r]⟹r−l=w宽[b,t]⟹t−b=h高
再根据裁剪面f和n,你就可以得到这个矩阵的简化版:
P 0 = [ 2 w 0 0 0 0 2 h 0 0 0 0 1 f − n − n f − n 0 0 0 1 ] P_0=\begin{bmatrix} \frac {2} {w} & 0 & 0 & 0 \\ 0 & \frac {2} {h} & 0 & 0 \\ 0 & 0 & \frac {1} {f-n} & -\frac {n} {f-n}\\ 0 & 0 & 0 & 1 \end{bmatrix} P0=⎣
⎡w20000h20000f−n1000−f−nn1⎦
⎤
你几乎可以一直使用这个矩阵替代上面那个你推导的更通用版的矩阵,除非你要做些奇怪的事情。
然而这还没有结束,根据简化后的矩阵很像是一个平移缩放后的复合矩阵,很容易启发我们思考,这个正交投影矩阵是否可以经过两次简单的变换得到?再联想到我们做正交投影的过程,将一个轴对齐盒子转向另一个轴对齐盒子;视域体不改变它的形状,只改变它的位置和大小。尝试推导后得出:
P 0 = S T = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 1 f − n 0 0 0 0 1 ] [ 1 0 0 0 0 1 0 0 0 0 1 − n 0 0 0 1 ] P_0=ST=\begin{bmatrix} \frac {2} {r-l} & 0 & 0 & 0 \\ 0 & \frac {2} {t-b} & 0 & 0\\ 0 & 0 & \frac {1} {f-n} & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & -n\\ 0 & 0 & 0 & 1 \end{bmatrix} P0=ST=⎣
⎡r−l20000t−b20000f−n100001⎦
⎤⎣
⎡10000100001000−n1⎦
⎤
这样转化之后你更容易想象发生了什么。首先,视域体沿着z轴平移使它的近平面和原点重合;然后,应用一个缩放把它缩小到规范视域体大小。
2.透视投影
x透视投影是稍复杂的一种投影方法,并且用的越来越平凡,因为它创造了距离感,因此会生成更逼真的图像。它的视域体像是一个顶部被截去了一个四棱锥的金字塔。
如图所示,透视投影的视域体的近平面从(l,b, n)延伸至(r, t, n)。远平面范围是从原点发射穿过近平面四个点的射线直至与平面z=f相交。由于视域体从原点进一步延伸,它变得越来越大;同时你将这个形状变换到规范视域体盒子;视域体的远端比视域体的近端压缩的更厉害。因此,视域体远端的物体会变得更小,这就给了你距离感。
分别对x和y的进行处理:
第1步:给定视域体中的点(x,y, z),把它投影到近平面z=n。由于投影点在近平面上,所以它的x坐标范围在[l, r],y坐标范围在[b, t]。
第2步:使用你在正交投影中学会推导的公式,把x坐标从[l, r]映射到[-1, 1],把y坐标范围从[b, t]映射到[-1, 1]。
从点(x, y, z)到原点画了条直线,注意直线与z=n平面相交于点(?,?,n)和z平面相交的点(x,y,z)。通过这些点,画2条相对于z轴的垂线,然后得到了一对相似三角形。
对于相似三角形它们的每对对应边都是同比例的。如图知道了沿着z轴的边长,它们是n和z。那意味着其他对应边的比例也是 n z \frac {n}{z} zn。所以,根据勾股定理,从(x, y, z)相对于z轴做的垂线的长度:
l = x 2 + y 2 l=\sqrt{\smash[b]{x^2 + y^2}} l=x2+y2
如果你知道了从你的投影点到z轴的垂线长度,那么你就可以计算出该点的x和y坐标。因为有了相似三角形,所以长度就是简单的 l l l 乘以 n z \frac {n}{z} zn:
l 2 = n z x 2 + y 2 l 2 = n 2 z 2 ( x 2 + y 2 ) l 2 = ( x n z ) 2 + ( y n z ) 2 \begin{align*} l_2&=\frac {n}{z}\sqrt{x^2 + y^2} \\ l_2&=\sqrt{\frac {n^2}{z^2}(x^2 + y^2)} \\ l_2&=\sqrt{
{(\frac {xn}{z})}^2 + {(\frac {yn}{z})}^2} \end{align*} l2l2l2=znx2+y2=z2n2(x2+y2)=(zxn)2+(zyn)2
因此,x坐标是 x n z \frac {xn}{z} zxn,y坐标是 y n z \frac {yn}{z} zyn。
回顾正交投影中学习到的推导公式:
x ′ = 2 x r − l − r + l r − l y ′ = 2 y t − b − t + b t − b \begin{align*} x'&=\frac {2x} {r-l} -\frac {r+l} {r-l} \\ y'&=\frac {2y} {t-b} -\frac {t+b} {t-b} \end{align*} x′y′=r−l2x−r−lr+l=t−b2y−t−bt+b
像那样把x和y坐标映射到规范视域体,所以把x用 x n z \frac {xn}{z} zxn代替,把y用 y n z \frac {yn}{z} zyn代替:
x ′ = ( 2 n r − l ) x z − r + l r − l y ′ = ( 2 n t − b ) y z − t + b t − b \begin{align*} x'&=(\frac {2n} {r-l})\frac{x}{z} -\frac {r+l} {r-l} \\ y'&=(\frac {2n} {t-b})\frac{y}{z} -\frac {t+b} {t-b} \end{align*} x′y′=(r−l2n)zx−r−lr+l=(t−b2n)zy−t−bt+b
两边乘以z:
x ′ z = ( 2 n r − l ) x − r + l r − l z y ′ z = ( 2 n t − b ) y − t + b t − b z \begin{align*} x'z&=(\frac {2n} {r-l})x -\frac {r+l} {r-l} z\\ y'z&=(\frac {2n} {t-b})y -\frac {t+b} {t-b}z \end{align*} x′zy′z=(r−l2n)x−r−lr+lz=(t−b2n)y−t−bt+bz
然后,写成代数形式,如:
x ′ = c 1 x + c 2 y + c 3 z + c 4 y ′ = c 5 x + c 6 y + c 7 z + c 8 x' = c_1x + c_2y + c_3z +c_4 \\ y' = c_5x + c_6y + c_7z +c_8 x′=c1x+c2y+c3z+c4y′=c5x+c6y+c7z+c8
其中, c 1 c 2 . . . c 8 c_1c_2...c_8 c1c2...c8为常数。
但很明显,现在还做不到直接写出z’z,所以现在看起来进入了僵局。应该做什么呢?如果你能找到个办法获得z’z的公式就像x’z和y’z那样,你就可以写一个变换矩阵把(x, y, z)映射到(x’z, y’z, z’z)。然后,你只需要把各部分除以点z,你就会得到你想要的(x’, y’, z’)。
因为你知道z到z’的转换不依赖于x和y,你知道你想要一个公式形如z’z= pz + q,p和q是常量。并且,你可以很容易的找到那些常量,因为你知道在两种特殊情况下如何得到z’: 因为你要把[n, f]映射到[0, 1],你知道当z=n时z’=0,和z=f时z’=1。
所以把第一组值代入 z ′ z = p z + q z'z = pz + q z′z=pz+q,得到:
0 = p n + q q = − p n \begin{align*} 0&=pn + q \\ q&= -pn \end{align*} 0q=pn+q=−pn
再把第二组值代入 z ′ z = p z + q z'z = pz + q z′z=pz+q,得到:
f = p f + q f=pf+q f=pf+q
则有
{ q = − p n f = p f + q ⇒ 解得 { p = f f − n q = − f n f − n \begin{cases} q= -pn \\ f=pf+q \end{cases}\xRightarrow{解得} \begin{cases} p = \frac{f}{f-n} \\ q = -\frac{fn}{f-n} \end{cases} {
q=−pnf=pf+q解得{
p=f−nfq=−f−nfn
最后,把p和q的表达式代入最原始的公式中,得:
z ′ z = f f − n z − f n f − n z'z = \frac{f}{f-n}z - \frac{fn}{f-n} z′z=f−nfz−f−nfn
但在处理这个问题的不寻常的性质需要你也处理齐次坐标w。通常情况下,只是简单的设置w’ = 1 ,你可能已经注意到在一个基本的变换下最后一行总是[0, 0, 0, 1],但是现在你在为点(x’z, y’z, z’z, w’z)写一个变换。所以取而代之的,把w’ = 1写成w’z = z。因此最后用于透视投影的等式如下:
x ′ z = 2 n r − l x − r + l r − l z y ′ z = 2 n t − b y − t + b t − b z z ′ z = f f − n z − f n f − n w ′ z = z \begin{align*} x'z &= \frac{2n}{r-l}x - \frac{r+l}{r-l} z \\ y'z &= \frac{2n}{t-b}y - \frac{t+b}{t-b} z \\ z'z &= \frac{f}{f-n}z - \frac{fn}{f-n} \\ w'z &=z \end{align*} x′zy′zz′zw′z=r−l2nx−r−lr+lz=t−b2ny−t−bt+bz=f−nfz−f−nfn=z
现在,当你把这个等式写成矩阵的形式,得到:
P 0 = [ 2 n r − l 0 − r + l r − l 0 0 2 n t − b − t + b t − b 0 0 0 f f − n − f n f − n 0 0 1 0 ] P_0=\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} {f-n} & -\frac {fn} {f-n}\\ 0 & 0 & 1 & 0 \end{bmatrix} P0=⎣
⎡r−l2n0000t−b2n00−r−lr+l−t−bt+bf−nf100−f−nfn0⎦
⎤
你把这个矩阵用于点(x, y, z,1),它将产生(x’z, y’z, z’z, w’z)。然后,你应用通常的步骤去除以齐次坐标,得到(x’, y’, z’, 1)。那就是透视投影。Direct3D的D3DXMatrixPerspectiveOffCenterLH()方法也实现了上述公式。正如正交投影,如果你假设视域体是对称的并且中心是z轴(也就是r = -l,t = -b),你可以简单的用视域体的宽w和高h改写矩阵中的各项:
P 0 = [ 2 n w 0 − r + l r − l 0 0 2 n h − t + b t − b 0 0 0 f f − n − f n f − n 0 0 1 0 ] P_0=\begin{bmatrix} \frac {2n} {w} & 0 & -\frac {r+l} {r-l} & 0 \\ 0 & \frac {2n} {h} & -\frac {t+b} {t-b} & 0 \\ 0 & 0 & \frac {f} {f-n} & -\frac {fn} {f-n}\\ 0 & 0 & 1 & 0 \end{bmatrix} P0=⎣
⎡w2n0000h2n00−r−lr+l−t−bt+bf−nf100−f−nfn0⎦
⎤
最后,还有个经常用得上的透视投影的表示。在这种表示中,你根据摄像机的可视范围定义视域体,而不用去担心视域体的尺寸。如图:
垂直可视范围的角度是 α \alpha α。这个角度被z轴一分为二,所以根据基本的三角函数,你可以写下面的方程,关联 α \alpha α和近平面n以及屏幕高度h:
cot α 2 = 2 n h \cot\frac {\alpha}{2} =\frac {2n}{h} cot2α=h2n
这个表达式可以取代投影矩阵中的高度。此外,使用横纵比r代替宽度,r定义为显示区域的宽比高的横纵比。则有:
2 n w = 2 n r h = 1 r cot α 2 \frac {2n}{w} = \frac {2n}{rh} = \frac {1}{r}\cot \frac {\alpha}{2} w2n=rh2n=r1cot2α
因此,有了用垂直可视范围角度a和横纵比r构成的透视投影矩阵:
P 0 = [ 1 r cot α 2 0 0 0 0 cot α 2 0 0 0 f f − n − f n f − n 0 0 1 0 ] P_0=\begin{bmatrix} \frac {1} {r}\cot \frac {\alpha}{2} & 0 & 0 & 0 \\ 0 & \cot \frac {\alpha}{2} & 0 \\ 0 & 0 & \frac {f} {f-n} & -\frac {fn} {f-n}\\ 0 & 0 & 1 & 0 \end{bmatrix} P0=⎣
⎡r1cot2α0000cot2α0000f−nf10−f−nfn0⎦
⎤
这种形式特别有用,因为你可以直接把r设置成渲染窗口的横纵比,并且可视范围角度为p / 4比较好。所以,你只需要在意定义视域体沿着z轴的范围。
参考文章:https://blog.csdn.net/stl112514/article/details/8392764
参考视频:https://www.bilibili.com/video/BV1X7411F744?p=4&vd_source=ec9fa87c00615ecc3fb1703022f7e4a6