透视转换是基本转换中最复杂的一个。
在3D API中常用的透视转换是,从椎状的视景体空间转换到标准的设备空间,即X,Y,Z均为[-1,1]的立方体空间。这样做的好处包括:1,简化裁剪;2,用浮点数存储的坐标可以获得理想的精度;3,简化后继的转换中涉及的计算,例如转换到各种屏幕坐标就很方便(暂时只想到这些)。
OpenGL透视转换矩阵如下(右手坐标系,Z轴正方向离开屏幕):
row0 = [ 2n/(r-l), 0, 0, 0 ]
row1 = [ 0, 2n/(t-b), 0, 0 ]
row2 = [ (r+l)/(r-l), (t+b)/(t-b), -(f+n)/(f-n), -1 ]
row3 = [ 0, 0, -2fn/(f-n), 0 ]
注意,n和f分别为近裁剪面和远裁剪面到视点的距离,而l,r,t,b则是视口矩形的坐标
难点在于对Z坐标的转换。为了保留深度信息,所以构造了一个线性转换:T(z)=a*z+b,当z=-n时,T(z)=-1;当z=f时,T(z)=1。联立解出a,b即可。但上面的透视矩阵还要再复杂一点。
因为当投影到屏幕坐标后,透视变换扭曲了Z值,所以Z值不再是线性的了,通过线性插值得出的中间点的Z坐标是错误的,会导致深度检测出现误差。同样的问题还出现在光照计算和纹理映射中。插值得出的是错误颜色值或纹理坐标会导致图像失真。
更好的方法是使用1/z代替z。可以证明,1/z在屏幕坐标中是线性的:
1/z=1/z1*(1-t)+1/z2*t
1/z在Z-Buffer中也同样管用,深度测试的时候与使用z值相比,只需简单地反转比较的符号就可以了。而在光照和纹理映射中,将颜色值或纹理坐标用乘以1/z:p=c/z,然后插值得出中间点的p,最后将p除以1/z,就可以得出该点处正确的颜色值和纹理坐标。
事实上,任何附加在屏幕坐标点(即像素点)上的,并在3D空间中线性变化的元素都可以使用这种方法来进行正确的插值。这称为透视修正。
最后,将T(z)修改为:T(z)=a/z+b,这就是OpenGL透视矩阵的最终版本。
没有评论:
发表评论