感觉很多书上都没讲清楚透视投影变换的推导过程,自己推导了下,以前一直含糊的关于方形/非方形的视平面和屏幕的宽高比的问题也有了答案.本文组织如下:
1.相机空间到视平面的变换
2.视平面到屏幕的变换
3.综合
4.一般情形
1.相机空间到视平面的变换
* p (xc,0, zc)
/ |
/ |
/ |
X |/ |
^ *p' |(xp,0,zp)
| / | |
| / | |
| / | |
C(cam) |/ | |
--------*----|----*------------->Z
0 dx zc
(X-Z平面的投影示图)
a.透视投影一般的视景体为棱台,相机空间的物体会投影到视平面z=d,这里考虑左手坐标系,矩阵使用行优先方式。如图所示,由相似三角形知识可知相机空间中的物体投影到视平面上的坐标为:
xp = xc*(dx/zc)
yp = yc*(dy/zc)
其中,xc,yc,zc为相机空间坐标,xp,yp,zp为视平面坐标,dx,dy为x,y轴向的视距view distance,视平面到camera的距离,
故相机空间投影到视平面上的矩阵Tcp为:
|dx 0 0 0 |
|0 dy 0 0 |
|0 0 1 1 |
|0 0 0 0 |
(验证:Tcp右乘点p(xc,yc,zc,1)得点p'(xc*dx, yc*dy, zc, zc),转换为3D坐标为(xc*dx/zc, yc*dy/zc, 1),正确。)
********************************************************************
注:因为转换过程中点使用的是4D齐次坐标,所以最后需转换为3D坐标。4D齐次坐标(x,y,z,w)转换为3D坐标的方法为除以w分量,即对应3D坐标为(x/w,y/w,z/w)。
********************************************************************
考虑dx/zc和dy/zc项,如果dx != dy,则投影后x,y的比例会发生变化(原因:投影前坐标比例为xc/yc,投影后为xp/yp = xc*(dx/zc)/yc*(dy/zc) = xc*dx/yc*dy),从而投影后的图像的x,y比例会发生变形。
---------------------------------------------
结论1:所以,一般都会令d=dx=dy,即x,y向的视距相同。否则,图像失真。
---------------------------------------------
于是,相机到投影平面的透视投影变化矩阵为
|d 0 0 0 |
|0 d 0 0 |
|0 0 1 1 |
|0 0 0 0 |
============================
扩展话题1:这个矩阵是如何得出的?
============================
其实这里有个技巧: 根据方程组
xp = xc*(d/zc)
yp = yc*(d/zc)
zp = ?
原来变换矩阵应该是这样的:Tcp=
|d/zc 0 0 0 |
|0 d/zc 0 0 |
|0 0 k 0 |
|0 0 b 1 |
(其中,k,b 可以为任意值,因为投影变换后z值无所谓)
验证:
转换为齐次坐标:(xc yc zc 1)*Tcp = (x' y' z' w') = (xc*d/zc, yc*d/zc, k*zc+b, 1*1)
转换为3D坐标: (x'/w' y'/w' z'/w') = (xc*d/zc, yc*d/zc, k*zc+b) ------(式1)
正确。
简化1:
我们可以发现可以将zc放到齐次坐标转换到3D坐标的时候考虑,即令w'=zc,这样,
转换为齐次坐标:(xc yc zc 1)*Tcp = (x' y' z' w') = (xc*d yc*d k*zc+b 1*zc)
转换为3D坐标: (x'/w' y'/w' z'/w') = (xc*d/zc, yc*d/zc, (k*zc+b)/zc) -------可以看到
变换后x,y坐标和(式1)一样,但是,矩阵Tcp就可简化了:
|d 0 0 0|
|0 d 0 0|
|0 0 k 1|
|0 0 b 0|
简化2:将d考虑到齐次坐标转换为3D坐标中去,令w'=zc/d
转换为齐次坐标:(xc yc zc 1)*Tcp = (x' y' z' w') = (xc*1 yc*1 k*zc+b 1*zc/d)
这样转换为3D坐标: (x'/w' y'/w' z'/w') = (xc*d/zc, yc*d/zc, (k*zc+b)*d/zc) -------
可以看到变换后x,y坐标和(式1)一样,但是,矩阵Tcp就可简化了:
|1 0 0 0 |
|0 1 0 0 |
|0 0 k 1/d|
|0 0 b 0 |
特别地,令k=1,b=0,还可进一步简化成:
|d 0 0 0|
|0 d 0 0|
|0 0 1 1|
|0 0 0 0|
(此即上面使用的矩阵,此时z被变换为1/z)
以及:
|1 0 0 0 |
|0 1 0 0 |
|0 0 1 1/d|
|0 0 0 0 |
(此时z被变换为d/z)
===============================
扩展话题2:为什么DX/OGL中的投影矩阵这么复杂?
===============================
我们知道DX和OGL使用的投影矩阵要复杂的多。为什么呢,因为DX/OGL会将相机可见范围(frustum)内所有的点经过
投影变换后线性映射到一个单位空间内,这个空间是:
DX:x={-1,1},y={-1,1},z={0,1}
OGL:x={-1,1},y={-1,1},z={-1,1}
(其中frustum为由right,left,top, bottom, near, far构成的一个可见空间-六面体,投影平面为near
平面,所以视距d = near)
经过投影变换Tcp=
|d 0 0 0|
|0 d 0 0|
|0 0 k 1|
|0 0 b 0|
后,坐标转换为(xc*d/zc, yc*d/zc, (k*zc+b)/zc),现在的问题就是如何将(xc*d/zc, yc*d/zc, (k*zc+b)/zc)线性映射到这个空间?
方法:
DX:
对于x,将x'= xc*d/zc从{left,right}线性映射到{-1,1}
简单,构造线性映射x''=kx'+ b, 将边界值代入得k=2*d/(r-l)=2*n/(r-l),b=-(r+l)/(r-l).
对于y,同理k=2*d/(t-b)=2*n/(t-b),b=-(t+b)/(t-b).
对于z,将z'=(k*zc+b)/zc从{near,far}映射到{0,1}
代入边界值得k=f/(f-n),b=-f*n/(f-n)
所以最终矩阵Tcp(dx)=
|2*n/(r-l) 0 0 0|
|0 2*n/(t-b) 0 0|
|-(r+l)/(r-l) -(t+b)/(t-b) f/(f-n) 1|
|0 0 -f*n/(f-n) 0|
这个即是DX使用的投影矩阵。
============================
考虑视角(view angle,或视野filed of view)的问题,视角的大小不会影响到物体投影后的坐标,只会影响可视的范围。
在视距一样的情况下,x,y轴的视角可以不一样。如果一样,那么视平面就是一个正方形的。于是有:
tan(theta_x/2) = width_p/d
tan(theta_y/2) = height_p/d
其中,theta_x,theta_y为x,y轴向的视角,width_p,height_p为视平面z=d的宽度(x轴)和高度(y轴)。
----------------------------------------------------------------
结论2:视平面的宽高比rp=width_p/height_p = tan(theta_x/2)/tan(theta_y/2)。
----------------------------------------------------------------
2.视平面到屏幕的变换
下面就是视平面到屏幕的变换了,这是一个2D到2D的变换(视平面的坐标需伸缩以填满整个屏幕空间,即在视平面中出现的所有的点要变换到屏幕上去,同时x,y轴向的比例还要维持和变换前一样,以免比例失真,同时视平面的坐标原点和屏幕中心点(x0=(width_s)/2, y0=(height_s)/2)对应),其实,就是一个坐标缩放加平移的过程:
xs = xp*kx + x0
ys = -yp*ky + y0
矩阵Tps为:
|kx 0 0 0 |
|0 -ky 0 0 |
|0 0 1 0 |
|x0 y0 0 1 |
(验证:Tps右乘点p(xp,yp,zp,1)得点p'(xp*kx + x0, -yp*ky + y0, zp, 1),转换为3D坐标为(xp*kx + x0, -yp*ky + y0, zp),正确。)
其中,kx,ky(kx>0,ky>0)为x,y轴向的缩放因子(kx=(width_s)/(width_p), ky = (height_s)/(height_p),和视距相同,kx,ky的值必须一样,否则图像的x,y比例又会发生变形。这里-yp*ky是因为一般屏幕的y轴是向下的,跟相机空间和视平面坐标系中的y轴方向相反。
------------------------------------------------------------------------------------------------
结论3: 一般令k=kx=ky,即x,y向的缩放因子相同。否则,图像失真。
于是有,width_s/width_p = height_s/height_p,变化等式得,rp = width_p/height_p = width_s/height_s = rs
所以,在x,y轴视距一样的情况下,要想最后变换到屏幕上的图像x,y比例不失真,必须rp=rs,即视平面的宽高比和屏幕的宽高比一样。
-----------------------------------------------------------------------------------------------
********************************************************************
注:若屏幕宽高为W,H,当W != H,即屏幕不为方形的时候,要确保投影到屏幕上的图像x,y比例不失真,则x,y轴视角(或视野FOV)肯定不能相等。
原因: 由结论2,3知,rp=width_p/height_p = tan(theta_x/2)/tan(theta_y/2)=width_s/height_s=rs=W/H。 故由W/H != 1 => theta_x != theta_Y.
********************************************************************
===============================
扩展话题3:DX/OGL中的投影平面到屏幕的变换矩阵是怎样的?
===============================
因为已经投影平面上的坐标已经映射到{-1,1},{-1,1},{0,1}/{-1,1}
所以kx=W/2,ky=H/2,x0=W/2,y0=H/2,z的变换无所谓。
Tps(dx) =
|W/2 0 0 0 |
|0 -H/2 0 0 |
|0 0 1 0 |
|W/2 H/2 0 1 |
3.综合:
相机空间点p转换到屏幕空间点p',变换公式为:
xs = xc*(dx/zc)*kx + x0 = xc*(d/zc)*k + x0
ys = -yc*(dy/zc)*ky + y0 = -yc*(d/zc)*k + y0
综合变换矩阵(相机空间到屏幕空间)Tcs为:
Tcs = Tcp*Tps =
|d*k 0 0 0 |
|0 -d*k 0 0 |
|x0 y0 1 1 |
|0 0 0 0|
其中,d为视距,k为屏幕和视平面之间的宽度比或高度比,x0,y0为屏幕中心,注:最后需转换为3D坐标。
(验证:Tcs右乘点p(xc,yc,zc,1)得点p'(xc*d*k + x0*zc, -yc*d*k + y0*zc, zc, zc),转换为3D坐标为(xc*(d/zc)*k + x0, -yc*(d/zc)*k + y0, 1),正确。)
4.一般情形:
************************************
视距为1,x轴视角为90度,屏幕宽高为W,H.
************************************
代入d=1,theta_x = PI/2,x0= W/2,y0=H/2,则视平面宽高为width_p = 2。
要确保屏幕上的图像x,y比例不失真,即rs=rp,有
height_p = 2/rp=2/rs=2H/W,
k=kx=ky=width_s/width_p = W/2.
于是,矩阵为:
Tcs1 =
|W/2 0 0 0 |
|0 -W/2 0 0 |
|W/2 H/2 1 1 |
|0 0 0 0 |
或
|W/2 0 0 0 |
|0 -H/2*(W/H) 0 0 |
|W/2 H/2 1 1 |
|0 0 0 0 |
(可以看到,y轴的缩放因子中乘上了宽高比(aspect ratio))
这个矩阵较常用。
===============================
扩展话题4:DX/OGL中的相机到屏幕的变换矩阵和这个一致么?
===============================
Tcs(dx)=Tcp(dx)*Tps(dx)=
|W*n/(r-l) 0 0 0|
|0 -H*n/(t-b) 0 0|
|-W*l/(r-l) -H*b/(t-b) f/(f-n) 1|
|0 0 -f*n/(f-n) 0|
n=1,r=1,l=-1t=1,b=-1代入:
|W/2 0 0 0|
|0 -H*/2 0 0|
|W/2 H*/2 f/(f-1) 1|
|0 0 -f*/(f-1) 0|