透视投影矩阵的完全推导,使用了openGL的坐标系以及NDC,在DirectX3D下需要做一些调整。
对于点P(x,y,z,1),经过投影变换乘以透视矩阵得到P′,P′位于Normalized Device Coordinates(NDC)中。
MprojP=P′
Px′∈[−1,1]
Py′∈[−1,1]
Pz′∈[−1,1]
投影矩阵将ViewFrustum中的点转换到NDC中,我们可以根据视锥中的点在透视投影变换后的位置推导出整个透视投影矩阵Mproj。
视锥体参数
Near Plane 与 Far Plane
Camera矩阵将世界坐标移动到相机的位置,同时视锥体朝向-Z方向。视锥体内被两个面截取为一个Four prism,通过透视投影矩阵的变化转化为一个边长单位为2的立方体。
对于视锥体来说有多个属性。近裁剪屏幕NearPlane,和远裁剪屏幕FarPlane。这两个平面距离相机的距离为n和f,在坐标系中的Z轴上值为-n与-f。
Aspect Ratio 及 Viewport
同时视锥体还有一个近平面的宽高,通常情况下,近平面即viewport的大小。透视投影后在NDC的宽高比是1:1,屏幕的aspect ratio会在透视投影后丢失。所以viewport的宽高与FrameBuffer的大小无关,只要在透视投影矩阵中正确设置了ViewPort的大小,渲染时会被按照比例压缩到整个FrameBuffer的贴图中,最后显示时以ViewPort的宽高比显示整个FrameBuffer就是正确的。
Field of View
在Camera的参数中经常有一个参数Field Of View(FOV)。FoV为视锥垂直界面的夹角。如果设定近平面的顶边的Y值为t,近平面距离为n,同时viewport是相对于坐标轴对称的情况下,则有tan(2FOV)=nt。只要确定了viewport的大小和近平面距离n,就可以求出fov。
通常情况下ViewPort是左右对称上下对称的。即如果我们设近平面四个边的距中心的坐标值为,l、r、t、b,分别代表左右上下四个边,则viewport的高h=t−b=2∗t,宽为w=r−b=2∗b。viewport也可能左右不对称的情况,即∣l∣=∣r∣,或上下不对称∣t∣=∣b∣。不过对于大部分游戏或3D程序来说都是对称的。通用的透视投影矩阵考虑到了不对称的情况,所以对应的矩阵会相对复杂一些。
推导投影矩阵
设点P(x,y,z) 为视锥体内部的一点,根据三角形相似比,其在近平面的投影点为P′(−znx,−zny,f(z)),其中f(z)为关于z的线性函数。由于在三维图形学中,使用齐次坐标来表示点与向量进行计算,同时投影矩阵为四阶矩阵。所以P点坐标表示为P(x,y,z,1),P′点的齐次坐标为P′(−znx,−zny,f(z),1)。
渲染管线在计算完VertexShader后会进行光栅化,在光栅化是需要根据点的z值进行Z-Test,同时f(z)∈[−1,1],f(z)∝−z。由于在顶点着色器返回的顶点位置,渲染管线在进行光栅化裁剪与剔除的步骤前会对坐标进行齐次化,也就是gl_Position
或者sv_position
会被齐次化。在着色器shader返回的坐标中并不需要其z值一定1,只要满足其坐标在齐次化后还保持在对应的NDC中。
所以透视投影矩阵计算出的坐标可以是齐次化前的坐标。根据上面推导的坐标:
P′(−znx,−zny,f(z),1)
Px′ 和Py′都带有-z的系数,并不是关于x,y,z的线性函数。所以不可能通过矩阵变化计算出来,我们必须对其进行齐次变换。P′所有坐标都乘上-z得:
P′(nx,ny,−zf(z),−z)
由此我们可以推算出投影矩阵的第四行。
⎣⎢⎢⎢⎡???0???0???−1???0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡nxnyg(z)−z⎦⎥⎥⎥⎤
其中g(z)=−zf(z)
由于g(z)是矩阵变换的结果,一定是关于z的线性函数,设g(z)=az+b 其中a,b为常数。则矩阵可以变为:
⎣⎢⎢⎢⎡??00??00??a−1??b0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡nxnyaz+b−z⎦⎥⎥⎥⎤
由于经过投影变换后的有效坐标点都在NDC中,我们可以取特殊点推算其参数。
设点L(l,b,−n,1)为近平面的左下角,R(r,t,−f,1)为远平面的右上角的点。其经过透视投影变换后的点为L→(−1,−1,−1,1),R→(1,1,1,1)。
代入上面的向量(nx,ny,az+b,−z)得
L′(nl,nb,−an+b,n)↔(−1,−1,−1,1)↔(−n,−n,−n,n)
R′(nr,nt,−af+b,f)↔(1,1,1,1)↔(f,f,f,f)
取z值的等式
−an+b=−n
−af+b=f
联立求解得:
a=n−fn+f
b=n−f2nf
带入矩阵得:
⎣⎢⎢⎢⎡??00??00??n−fn+f−1??n−f2nf0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡nxnyn−f(n+f)z+2nf−z⎦⎥⎥⎥⎤
这样我们就求出a和b的值。
推导矩阵前两行
首先我们很自然想到,取矩阵前两行为:
⎣⎢⎢⎢⎡n0000n0000n−fn+f−100n−f2nf0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡nxnyn−f(n+f)z+2nf−z⎦⎥⎥⎥⎤
这样矩阵计算就成立了,对于上面的L点与R点同时我们取x值的等式。
nl=−n
nr=f
这样推出l=−1,r=nf,由于l、r为viewport参数是任意值,所以Mproj第一行与第二行不能只有n这个参数。
设完整的矩阵为:
⎣⎢⎢⎢⎡AE00BF00CGn−fn+f−1DHn−f2nf0⎦⎥⎥⎥⎤
对于视锥体内的点来说,如果两个点的x和z值都相同y值不同,而其投影到近投影平面的x坐标都相同
所以对于y的系数 B=0 。
由于对任意的(x,y,z)都成立,则取(0,y,0)时,对于x
分量,我们有Ax+Cz+D=D=0得D=0。
同理 E=0,H=0
化简矩阵:
⎣⎢⎢⎢⎡A0000F00CGn−fn+f−100n−f2nf0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡Ax+CzFx+Gzn−f(n+f)z+2nf−z⎦⎥⎥⎥⎤
取点S(l,b,−n,1),T(r,t,−n,1)。由于这两点分别在近平面的左端与右端,所以透视投影变换后在NDC中的x坐标为-1与1,由于我们经过了齐次化处以-z,所以两个点的x值为-n,n。
联立方程:
Al−Cn=−n,Ar−Cn=n
解得:
A=r−l2n,B=r−lr+l
同理推得:
F=t−b2n,G=t−bt+b
最终的投影矩阵为:
⎣⎢⎢⎢⎡r−l2n0000t−b2n00r−lr+lt−bt+bn−fn+f−100n−f2nf0⎦⎥⎥⎥⎤⎣⎢⎢⎢⎡xyz1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡r−l2nx+(r+l)zt−b2ny+(t+b)zn−f(n+f)z+2nf−z⎦⎥⎥⎥⎥⎤⇔⎣⎢⎢⎢⎢⎡−(r−l)z2nx−r−lr+l−(t−b)z2ny−t−bt+b−n−fn+f−(n−f)z2nf1⎦⎥⎥⎥⎥⎤
简化透视投影矩阵
当viewport关于坐标轴对称时:
w=r−l=2r=−2l
h=t−b=2t=−2b
化简投影矩阵得:
⎣⎢⎢⎢⎡w2n0000h2n0000n−fn+f−100n−f2nf0⎦⎥⎥⎥⎤