OpenGL 入门11

来源:互联网 发布:老滚捏脸数据 编辑:程序博客网 时间:2024/06/05 02:42

原帖地址:
http://blog.csdn.net/goncely/article/details/5397729
http://ogldev.atspace.co.uk/www/tutorial12/tutorial12.html
http://blog.csdn.net/cordova/article/details/52599161

透视投影

在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正交投影和透视投影。正交投影多用于三维健模,透视投影则由于和人的视觉系统相似,多用于在二维平面中对三维世界的呈现。
这里写图片描述
基本透视投影模型对视点E的位置和视平面P的大小都没有限制,只要视点不在视平面上即可。P无限大只适用于理论分析,实际情况总是限定P为一定大小的矩形平面,透视结果位于P之外的透视结果将被裁减。可以想象视平面为透明的玻璃窗,视点为玻璃窗前的观察者,观察者透过玻璃窗看到的外部世界,便等同于外部世界在玻璃窗上的透视投影(总感觉不是很恰当,但想不出更好的比喻了)。
当限定P的大小后,视点E的可视区间(或叫视景体)退化为一棱椎体,如图3所示。该棱椎体仍然是一个无限区域,其中视点E为棱椎体的顶点,视平面P为棱椎体的横截面。实际应用中,往往取位于两个横截面中间的棱台为可视区域(如图4所示),完全位于棱台之外的物体将被剔除,位于棱台边界的物体将被裁减。该棱台也被称为视椎体,它是计算机图形学中经常用到的一个投影模型。
这里写图片描述
设视点E位于原点,视平面P垂直于Z轴,且四边分别平行于x轴和y轴,如图5所示,我们将该模型称为透视投影的标准模型,其中视椎体的近截面离视点的距离为n,远截面离视点的距离为f,且一般取近截面为视平面。下面推导透视投影标准模型的变换方程。
这里写图片描述
设位于视椎体内的任意一点X (x, y, z) 在视平面的透视投影为Xp (xp, yp, zp),从点X和Xp做z轴的垂线,并分别在X-Z平面和Y-Z平面投影,图6是在X-Z平面上的投影结果。
这里写图片描述
根据三角形相似原理 , 可得 :

xp/n = x/z, yp/n = y/z

解上式得 :

xp = x*n/z, yp = y*n/z, zp = n.
上式便是透视投影的变换公式,非常简单,不是吗?需要说明的是,由于透视点始终位于视平面,所以zp恒等于n,实际计算的时候可以不考虑zp。另外还可以从照相机模型来考虑透视投影。将视点E想象为一个虚拟的照相机,视平面想象为胶片,那么图5 也是一个标准的照相机模型。
PS:上述讨论都是基于矩形视平面来考虑的,其实我们可以取视平面为任意形状,比如圆形,此时视景体变为一个圆锥体,当然现在好像还没有圆形的显示装置。另外,我还曾考虑将视平面取为凹面或凸面,此时的投影结果应该是哈哈镜效果吧(纯属想象,没有验证)。还可以想象将视平面放在E的另外一面,这时的投影图像是倒置的,但是不是更接近人的视觉成像模型?另外还可以考虑有两个甚至更多视点的透视投影,总之充分发挥你的相像,或许能得到意想不到的结果。
4 透视投影的一般模型

令世界坐标系的x轴指向屏幕的右方,y轴指向屏幕的上方,z轴指向屏幕外(右手坐标系)。我们在讨论标准模型的时候,曾假设E的坐标为原点,其实视点E除了有位置属性外,还有姿态属性,通常用[L U D]表示(D3D中用的是[R U D]表示),其中L表示视点的左向(Left),U表示上方(Up),D表示朝向(Direction)。在标准模型中,有L=[-1,0,0]T , U=[0,1,0]T , D=[0,0,-1]T 。
透视投影的一般模型研究视点E在任意位置,任意姿态下透视图的生成算法。思路很简单,先将一般模型变换为标准模型,然后使用标准模型的透视投影公式便能计算透视结果。下面研究一般模型变换为标准模型的数学公式。
设一般模型中的点X,其对应在标准模型中的点为Y,那么当视点位于E,姿态为R时,X和Y有如下关系:

X = E+RY

反过来有:

Y = R-1 (X-E)

通常取R为正交阵,即R-1 =RT ,故有

Y = RT (X-E)

把上式改写成齐次矩阵(Homogeneous matrix )的形式有:

式中Hview 便是透视投影从一般模型到标准模型的变换矩阵。
对于透视投影的标准模型,视平面的坐标模型如图 7 所示,它的坐标原点位于视平面的中心, x 轴正向水平向右, y 轴正向垂直向上。要把透视投影的结果在计算机屏幕上显示的话,需要对透视图进行坐标变换,将其从视平面坐标系转换到屏幕坐标系。
这里写图片描述
计算机屏幕的坐标模型如图 8 所示,它的原点位于屏幕的坐上角, y 轴正向垂直向下。设视平面的宽度为 Wp ,高度为 Hp ;屏幕的宽度为 Ws ,高度为 Hs 。
这里写图片描述
令视平面坐标系中的点( xp, yp )对应于屏幕坐标系中的点( xs, ys ),它们的变换关系如下:
xs = a*xp + b;
ys = c*yp + d
由图 7 和图 8 可知,视平面中的( 0, 0 )点对应于屏幕坐标系中的中心点( 0.5*Ws-0.5, 0.5*Hs-0.5 )( PS :由于屏幕坐标系是离散坐标系,所有屏幕右下点的坐标为( Ws-1, Hs-1 ),而不是( Ws, Hs ));另外,视平面的( -0.5*Wp, -0.5*Hp )对应于屏幕的( 0, 0 )点。将上述两种取值代入变换方程可以得出:
这里写图片描述
上式便为视平面坐标系到屏幕坐标系的变换方程。

英文翻译(我是英语渣男)

这里所要讨论的内容能够最贴切的体现3D图形这一概念—也就是将3D图像投影到2D平面上,同时保留景深感。我们可以想象这样一种图像:两条铁轨在极远处交于一点。
我们需要计算出一个变换矩阵将我们的三维物体坐标变换到一个平面中且满足上述的保留景深的要求(就像照照片一样,不过这里是将三维空间中的物体通过这个矩阵变换到我们的屏幕上,而不是胶卷上。所以我们要解决的等价问题就是:如何将一个三维空间的坐标,变换为屏幕中的二维坐标)。在将我们的三维坐标变换到屏幕坐标后,我们希望屏幕坐标处于[-1,1]之间,以方便将不出现在屏幕中的点丢弃掉。这就意味着我们的裁剪器不用知道屏幕的尺寸以及远近平面的位置。
生成透视投影矩阵需要4个参数:
1.纵宽比—投影的区域是一个矩形的区域,纵宽比就是这个矩形的长/宽
2.视野垂直方向的角度—我们用于观察这个世界的摄像机的垂直方向的角度
3.近平面的位置—-近平面用去剔除离摄像机太近的点
4.远平面的位置—-远平面用去剔除离摄像机太远的点
我们需要纵宽比这个参数来将坐标规范化到[-1,1]的正方形之内。在大多数情况下,屏幕的长都比宽要长一些。所以要将这些坐标对应到一个正方形里面,那么在水平方向的点的密度就要高于垂直方向的点的密度。也就是说x的坐标数量要多于y的坐标数量。
视野垂直方向的夹角允许我们将整个世界进行放大或者缩小。如下图:
这里写图片描述
左侧摄像机的仰角要大于右侧摄像机的仰角,但是左侧目标所投影的像的高度却要小于右侧相同的对象的投影的高度。对于摄像机的位置来说,这一特性有点反常识,因为离摄像机越近的物体显得更小。然而我们的摄像机位置对最终的渲染结果没有什么影响,因为投影是投影到近平面上的,无论摄像机如何运动,近平面离摄像机的位置是固定的。
我们通过近平面离摄像机的距离来开始我们的计算。投影平面是一个与xy平面平行的平面。显而易见,我们并不会用整个平面,因为摄像机更本看不完整个平面,他只能看到这个平面的某个矩形范围(叫做projection window)。这个projection window和屏幕的纵宽比是相同的。假设纵宽比是ar,那么
ar = screenWidth/screenHeight
假设projection window的高是2,那么screenwidth = 2*ar
如果我们把摄像机放在原点,并且往projection window的方向看的话,我们将看到下图这样的坐标
这里写图片描述
那些在这个projection window之外的点都被剔除了,不会出现在屏幕上。这个坐标范围的Y已经是合乎要求的[-1,1]的范围,但是x的坐标范围不满足这个条件。我们将在接下来的几步修复这个问题,现在让我们以平行于x轴的方向观察这个摄像机,如图:
这里写图片描述
d是摄像机离projection window的距离,因此tan(α/2) = 1/d
得到 d = 1/(tan(α/2))

假设我们在3D空间中有一个点(x,y,z),投影到projection window上的点的Y坐标是yp,根据三角形相似的定理:
这里写图片描述
d/yp = z/y
所以 yp = d*y/z= y/(tan(α/2)·z)

同理可以推出 xp = x/(tan(α/2)·z)

因为我们的纵宽比是ar,为了让x坐标也处于[-1,1]之间,我们需要将x的坐标除以纵横比,所以
xp = x/(tan(α/2)·z·ar)
总的来说,这个除法让我们x坐标上的点的坐标被压缩了。
到目前为止,我们得出了两个公式:
这里写图片描述
现在我们尝试由这两个公式构建出一个变换矩阵,不过我们遇到一个问题:在两个等式里面我们都要除以z坐标。因为不同的点有不同的z坐标,所以我们没有办法用一个矩阵来变换各个不同的点。假设我们变换矩阵的第一行是(a,b,c,d),那么变换 之后的x坐标的算式如下:
a·x+b·y+c·z+d·w = x/(z*tan(α/2)*ar)

因为结果里没有y和w,所以我们可以让b和d为0得到如下等式:
a·x + c·z = x/(z*tan(α/2)*ar)

我们发现根本找不出何事的a和c让等式成立。OpenGL的方法是将这个投影变换矩阵变为两步来实现。先将顶点乘以一个变换矩阵,然后将除以z这个操作单独处理。除以z的这个操作实在光栅器里面进行的(光栅器处理处于vertex shader和fragment shader处理之间)。vertex shader是如何知道哪一个vertex shader的输出用于除以z呢?答案是gl_Position这个内置变量。所以现在我们只需要找到一个矩阵满足下面这两个等式即可:
这里写图片描述
但是这里又出现了另一个问题,如果我们将所有变换之后的顶点都除以z之后,这些顶点的z信息就随之丢失了,因为所有的z都变成了1,这就没有办法用z来进行深度测试了。解决办法是将转换前的点的z坐标存储到转换后的点的w分量中去。所以自动除以z的操作就变成了自动除以w的操作,而这个操作的名字就叫做perspective divide。
因此,得到的变换矩阵如下:
这里写图片描述
正如之前所说,我们要将z的坐标也规格化到[-1.1]这个范围之内,以方便裁剪器来裁剪点。然而经过上面的矩阵处理的顶点的z坐标都变成了0。因为通过上面矩阵变换的点会自动的进行perspective divide,也就是除以z的过程。所以我们要为该变换矩阵的第三行选择一组数据,而这组数据满足的要求就是除以在视锥台中的任何z都会产生[-1,1]之间的值。
假设我们的这个变换矩阵的第三个向量是(A,B,C,D),这个向量将我们的点(x,y,z,1)变换之后得到一个变换之后的点的z坐标,记为:
f(z) = Ax+By+Cz+D
因为同一z值的xy平面具有相同的变换之后的f(z),所以x,y坐标的变化对结果没有影响,因此A=0,B=0。
这样算式就变成了
f(z) = Cz+D
在执行perspective divede的时候,变换的结果会除以z,切除以z之后的值的取值范围是从[-1.1]
那么-1<= c+D/z <=1
当z取NearZ时得到-1,当z取FarZ时得到1,根据此条件解出C = (-Nearz-FarZ)/(NearZ-FarZ),D = 2FarZ*NearZ/(NearZ - FarZ)
因此最终的变换矩阵的结果如下:
这里写图片描述
之前的示例中,我们在vertex shader中手动将w的值设置为1,那么在perspective divide中其实什么都没有做,因为w为1,除以1等于啥都没做。所以如果我们传入vertex buffer中的顶点的位置如果超出了[-1,1]的范围,那么我们画出的图像将超出屏幕。

代码

void Pipeline::InitPerspectiveProj(Matrix4f& m) const>{    const float ar = m_persProj.Width / m_persProj.Height;    const float zNear = m_persProj.zNear;    const float zFar = m_persProj.zFar;    const float zRange = zNear - zFar;    const float tanHalfFOV = tanf(ToRadian(m_persProj.FOV / 2.0));    m.m[0][0] = 1.0f / (tanHalfFOV * ar);     m.m[0][1] = 0.0f;    m.m[0][2] = 0.0f;    m.m[0][3] = 0.0f;    m.m[1][0] = 0.0f;    m.m[1][1] = 1.0f / tanHalfFOV;     m.m[1][2] = 0.0f;     m.m[1][3] = 0.0f;    m.m[2][0] = 0.0f;     m.m[2][1] = 0.0f;     m.m[2][2] = (-zNear - zFar) / zRange;     m.m[2][3] = 2.0f * zFar * zNear / zRange;    m.m[3][0] = 0.0f;    m.m[3][1] = 0.0f;     m.m[3][2] = 1.0f;     m.m[3][3] = 0.0f;}

初始化透视投影矩阵。

m_transformation = PersProjTrans * TranslationTrans * RotateTrans * ScaleTrans;

算出我们的变换矩阵,注意顺序,透视投影矩阵是最后一步进行的操作。

p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 1000.0f);

设置生成透视投影矩阵的参数。

0 0
原创粉丝点击