深入研究透视投影变换
3D世界变换主要有世界变换、观察变化、投影变换。世界变换做的事情是把坐标从模型空间变换到世界空间,而观察变换是把坐标从世界空间变换到观察空间。3D世界里,所有的物体(包括相机等)都可以认为是3D模型,他们开始创建的时候都是处于自身坐标系内,渲染的时候,我们需要把这些模型从模型空间最终变换到统一的观察空间(相机自身空间)以方便进行裁剪等操作,最后,再经过透视投影变换将坐标变换到统一的立方体空间内。
在这三种变换中,投影变换最麻烦,越是麻烦的事情,越是需要整理一下思路,下面是我在做“将屏幕坐标转换为三维射线”算法研究中的一些想法,记录下来以供将来察看.
投影变换就做一件事:将锥形的观察空间转化为单位立方体空间。
上图是观察空间,其中近裁剪面可以认为是计算机内的一个3D图形显示窗口或者屏幕,经过观察变化后,只有处于此锥形空间内的物体才会最终显示在用户面前。
上图是观察空间的YZ图,我们据此推出坐标之间的关系:
y = ± z * tan(fov/2),x = y*Aspect
上图是投影空间的可见范围,这个空间处于你所见到的屏幕上。实际上将屏幕表面视作投影空间的xoy平面,再加一条垂直屏幕向里(或向外)的z轴(这取决于你的坐标系是左手系还是右手系),这样就构成了我们想要的坐标系。好了,现在我们可以用视口(view
port)的大小来描述这个可视范围了。比如说全屏幕640*480的分辨率,原点在屏幕中心,那我们得到的可视区域为一个长方体,它如图(a)所示。
从图(a)变换到图(b)需要经过视口变换,目的是将屏幕坐标(0< SPAN>)变换到[-1,+1]的范围内。下面是将屏幕坐标变换到视口坐标的转换过程
接下来,我们处理视口空间到观察空间的转换。
由于在观察空间内,y = ± z * tan(fov/2),x = y*Aspect
而在投影空间内,x’和y’都属于[-1,+1],因此我们推出从观察空间到投影空间的变换公式,如下:
y’ = y * cot(fov/2) / z,
x’ = x * cot(fov/2) /(z*
aspect)
我们可以看到,从(x,y)变换到(x’,y’)是一次非线性变换,为了用矩阵来表达这个转换过程,我们就得用w这个分量了,为什么这么有用呢?我们可以想到,在世界变换和观察变换这两个过程内,我们对坐标的操作都是一些平移、旋转、缩放操作,仔细观察这些变换矩阵,我们可以发现这些矩阵的第四列元素均是(0
0 0
1),因此向量与矩阵相乘后,w分量并不会改变。换句话说,只要不是故意,一个w分量等于1的向量,再来到投影变换之前他的w分量仍旧等于1。好的,接下来我们让w’=
w*z,
新的w’就记录下了view空间中的z值;同时在x,y分量上我们退而求其次,只要做到y’
= y * cot(fov/2), x’ = x * cot(fov/2)
/aspect。那么,在做完线性变换之后,我们再用向量的y除以w,就得到了我们想要的最终的y值。
现在只剩下z分量了。我们所渴望的变换应将z = Znear 变换到z = 0,将z =
Zfar变换到z = 1。这个很简单,但是等等,x,
y最后还要除以w,你z怎能例外。既然也要除,那么z = Zfar 就不能映射到z
= 1了。唔,先映射到z = Zfar试试。于是,有z’ = Zfar*(z-Znear)/(Zfar
– Znear)。接下来,看看z’/z的性质。令f(z) = z’/z =
Zfar*(z-Znear)/(z*(Zfar – Znear))。
则f’(z) = Zfar * Znear / ( z^2 * (Zfar –Znear )),
显而易见f’(z) > 0。所以除了z =
0是一个奇点,函数f(z)是一个单调增的函数。因此,当Znear≤z≤Zfar时,f(Znear)≤f(z)≤f(Zfar),
即0≤f(z)≤1。
至此,我们可以给出投影变换的表达式了:
x’ = x*cot(fov/2)/aspect
y’ = y*cot(fov/2)
z’ = z*Zfar / ( Zfar – Znear ) – Zfar*Znear / ( Zfar –
Znear )
w’ = z
以矩阵表示,则得到变换矩阵如下,
cot(fov/2)/aspect
0 0
0
0
cot(fov/2) 0
0
0 0 Zfar/(Zfar-Znear) 1
0
0 -Zfar*Znear/(Zfar-Znear) 0
做完线性变换之后,再进行所谓的“归一化”,即用w分量去除结果向量。
为了验证这一研究结果,我们可以察看DX9文档,发现投影矩阵就是我们在上面推出的矩阵,因此,我们可以得出结论,经过D3D提供的投影函数,我们可以得到投影矩阵M,为了将观察空间内的坐标变换到投影空间,我们首先要将观察空间内的向量称上矩阵M以执行线性变换,然后再将得到的向量除以w分量,就可以得到最终的投影空间内的向量了。
为了简化上述步骤,Direct
manager提供了一个函数TransformCoordinate函数,该函数内部直接将w分量“归一化”为1了。因此,我们只需将观察空间内的向量乘上此投影矩阵就可以得到投影空间内的向量了。
同理,如果我们想从二维的屏幕坐标转化到模型空间内的坐标,就需要执行上述过程的逆过程:视口变换-->投影逆变换-->观察逆变换-->世界逆变换。