转自:http://blog.csdn.net/gggg_ggg/article/details/45969499php
本文乃<投影矩阵的推导>译文,原文地址为:程序员
http://www.codeguru.com/cpp/misc/misc/math/article.php/c10123__1/Deriving-Projection-Matrices.htm,因为本人能力有限,有译的不明白的地方你们能够参考原文,谢谢^-^!app
在3D图形程序的基本矩阵变换中,投影矩阵是其中比较复杂的。平移和缩放浏览一下就能理解,旋转矩阵只要掌握了三角函数知识也能够理解,但投影矩阵有点棘手。若是你曾经看过投影矩阵,你会发现你的常识不足以告诉你它是怎么来的。并且,我在网上还未看到许多关于如何推导投影矩阵的教程资源。本文的话题就是如何推导投影矩阵。函数
对于刚刚开始接触3D图形的人,我应该指出,理解投影矩阵如何推导多是咱们对于数学的好奇心,它不是必须的。你能够只用公式,而且若是你用像Direct3D那样的图形API,你甚至都不须要使用公式,图形API会为你构建一个投影矩阵。因此,若是本文看起来有点难,不要惧怕。只要你理解了投影矩阵作了什么,你不必在你不想的状况下关注它是怎么作的。本文是给那些想了解更多的程序员的。学习
概述: 什么是投影?测试
计算机显示器是一个二维表面,因此若是你想显示三维图像,你须要一种方法把3D几何体转换成一种可做为二维图像渲染的形式。那也正是投影作的。拿一个简单的例子来讲,一种把3D对象投影到2D表面的方法是简单的把每一个坐标点的z坐标丢弃。对立方体来讲,看上去可能像图1:.net
图1: 经过丢弃Z坐标投影到XY平面code
固然,这过于简单,而且在大多数状况下不是特别有用。首先,根本不会投影到一个平面上;相反,投影公式将变换你的几何体到一个新的空间体中,称为规范视域体(canonical view volume),规范视域体的精确坐标可能在不一样的图形API之间互不相同,但做为讨论起见,把它认为是从(-1, -1, 0)延伸至(1, 1, 1)的盒子,这也是Direct3D中使用的。一旦全部顶点被映射到规范视域体,只有它们的x和y坐标被用于映射到屏幕上。这并不表明z坐标是无用的,它一般被深度缓冲用于可见度测试。这就是为何变换到一个新的空间体中,而不是投影到一个平面上。htm
注意,图1描述的是左手坐标系,摄像机俯视z轴正方向,y轴朝上而且x轴朝右。这是Direct3D中使用的坐标系,本文中我都将使用该坐标系。对于右手坐标系系统来讲,在计算方面没有明显差别,在规范视域体方面有一点区别,因此一切讨论仍将适用即便你的图形API使用与Direct3D不一样的规定。对象
如今,能够进入实际的投影变换了。有许多投影方法,我将介绍最多见的2种:正交和透视。
正交投影(Orthographic Projection)
正交投影,之因此这么称呼是由于全部的投影线都与最终的绘图表面垂直,是一种相对简单的投影技术。视域体,也就是包含全部你想显示的几何体的可视空间——是一个将被变换到规范视域体的轴对齐盒子,见图2:
图2: 正交投影
正如你看见的,视域体由6个面定义:
由于视域体和规范视域体都是轴对齐盒子,这种类型的投影没有距离更正。最终的结果是,事实上,很像图1那样每一个坐标点只是丢弃了z坐标。对象在3D空间中的大小和在投影中的大小相同,即便一个对象比另外一个对象距离摄像机远不少。在3D空间中平行的直线在最终的图像上也是平行的。使用这种类型的投影将出现一些问题像第一人称射击游戏——试想一下在不知道任何东西有多远的状况下玩!但它也有它的用处。你可能在格子游戏中使用它,例如,特别是摄像机被绑定在一个固定角度的一款格子游戏中,图3显示了1个简单的例子:
图3: 正交投影的一个简单例子
因此,事不宜迟,如今开始弄清楚它是如何工做的。最简单的方法多是3个坐标轴分开考虑,而且计算如何沿着每一个坐标轴将点从视域体映射到规范视域体。从x轴开始,视域体中的点的x坐标范围在[l, r],想把它变换到范围在[-1, 1]:
如今,准备把范围缩小到咱们指望的,各项减去l,这样,最左边的项变为0。另外一种可能考虑的作法是平移范围使其以0为中心,而不是一端为0,但如今这种方式代数式更整洁,因此为了可读性起见我将以如今这种方式作:
如今,范围的一端是0,你能够缩小到指望的大小。你指望x值的范围是2个单位宽,从1到-1,因此把各项乘以2/(r-l)。注意r-l是视域体的宽度,所以始终是一个正数,因此不用担忧不等号会改变方向:
下一步,各项减去1就产生了咱们指望的范围[-1,1]:
基本代数容许咱们将中间项写成一个单一的分数:
最后,把中间项分红两部分使它形如px+q的形式,咱们须要把项组织成这种形式这样咱们推导的公式就能够简单的转换成矩阵形式:
这个不等式的中间项告诉了咱们把x转换到规范视域体的公式:
获取y的变换公式的步骤是彻底同样的——只要用y替代x,用t替代r,用b替代l——因此这里不重复它们了,只是给出结果:
最后,须要推倒z的变换公式。z的推导有点不一样,由于须要把z映射到范围[0, 1]而不是[-1, 1],但看上去很类似。z坐标最开始在范围[n,f]:
把各项减去n,这样的话范围的下限就变为了0:
如今剩余要作的就是除以f-n,这样就产生了最终的范围[0,1]。和前面相同,注意f-n是视域体的深度因此绝对不会为负:
最后,把它分红两部分使它形如px+q的形式:
这样便给出了z的变换公式
如今,能够准备写正交投影矩阵了。总结到目前为止的工做,推导了3个投影公式:
若是写成矩阵形式,就获得了:
就是这样!Direct3D提供了D3DXMatrixOrthoOffCenterLH()(what a mouthful!)方法构造一个和这个公式相同的正交投影矩阵;你能够在DirectX文档中找到。方法名中的"LH"表明了你正在使用左手坐标系。可是,究竟"OffCenter"的意思是什么呢?
这一问题的答案引导你到一个正交投影矩阵的简化形式。考虑几点: 首先,在可见空间中,摄像机定位在原点而且沿着z轴方向观看。第二,你一般但愿你的视野在左右方向上延伸的一样远,而且在z轴的上下方向上也延伸的一样远。若是是这样的状况,那么z轴正好直接穿过你视域体的的中心,因此获得了r = -l而且t = -b。换句话说,你能够把r, l, t和b一块儿忘掉,简单的把视域体定义为1个宽度w和1个高度h,以及裁剪面f和n。若是你在正交投影矩阵中应用上面说的,那么你将获得这个至关简化的版本:
这个公式是Direct3D中D3DXMatrixOrthoLH()方法的实现。你几乎能够一直使用这个矩阵替代上面那个你推导的更通用的"OffCenter"版本,除非你用投影作些奇怪的事情。
在完成这部分以前还有一点。它启发咱们注意到这个矩阵能够用两个简单的变换串联替代:平移其次是缩放。若是你思考几何的话这对你是有意义的,由于全部你在正交投影中作的就是从一个轴对齐盒子转向另外一个轴对齐盒子;视域体不改变它的形状,只改变它的位置和大小。具体来讲,有:
这种投影方式可能更直观一点由于它让你更容易想象发生了什么。首先,视域体沿着z轴平移使它的近平面和原点重合;而后,应用一个缩放把它缩小到规范视域体大小。很容易理解吧,对不对?一个偏离中心(OffCenter)的正交投影矩阵也能够用一个变换和一个缩放代替,它和上面的结果很类似因此我在这里不列出了。
上面就是正交投影,如今能够去接触一些更有挑战性的东西了。
透视投影(Perspective Projection)
透视投影是稍复杂的一种投影方法,而且用的愈来愈平凡,由于它创造了距离感,所以会生成更逼真的图像。从几何上说,这种方法与正交投影不一样的地方在于透视投影的视域体是一个平截头体——也就是,一个截断的金字塔,而不是一个轴对称盒子。见图4:
图4: 透视投影
正如你所看见的,视域体的近平面从(l,b, n)延伸至(r, t, n)。远平面范围是从原点发射穿过近平面四个点的射线直至与平面z=f相交。因为视域体从原点进一步延伸,它变得愈来愈宽大;同时你将这个形状变换到规范视域体盒子;视域体的远端比视域体的近端压缩的更厉害。所以,视域体远端的物体会变得更小,这就给了你距离感。
因为空间体形状的这种变换,透视投影不能像正交投影那样简单的表达为一个平移和一个缩放。你必须制定一些不一样的东西。可是,这并不意味着你在正交投影上作的工做是无用的。一个方便的解决数学问题的方法是把问题减小到你已经知道怎么解决的那一个。因此,这就是你在这里能够作的。上一次,你一次检查一个坐标,但此次,你将把x和y坐标合起来一块儿作,而后再考虑z坐标。你对x和y的处理能够分2个步骤:
第1步: 给定视域体中的点(x,y, z),把它投影到近平面z=n。因为投影点在近平面上,因此它的x坐标范围在[l, r],y坐标范围在[b, t]。
第2步: 使用你在正交投影中学会推导的公式,把x坐标从[l, r]映射到[-1, 1],把y坐标范围从[b, t]映射到[-1, 1]。
听上去很棒吧?看一看图5:
图5: 使用类似三角形投影一个点到z=n平面
在这个图中,你从点(x, y, z)到原点画了条直线,注意直线与z=n平面相交的那个点——用黑色标记的那个。经过这些点,你画了2条相对于z轴的垂线,忽然你获得了一对类似三角形。若是你可以回想起高中的几何知识,类似三角形是拥有相同形状但大小不必定相同的三角形。为了证实2个三角形是类似的,必须证实它们的同位角相等,在这里不难作到。角1被两个三角形共享,显然它和自身相等。角2和角3是穿越两条平行线造成的同位角,因此它们是相等的。同时,直角固然是彼此相等的,因此两个三角形是类似的。
对于类似三角形你应该感兴趣的是它们的每对对应边都是同比例的。你知道沿着z轴的边的长度,它们是n和z。那意味着其余对应边的比例也是n/z。因此,考虑下你知道了什么。根据勾股定理,从(x, y, z)相对于z轴作的垂线具备如下长度:
若是你知道了从你的投影点到z轴的垂线的长度,那么你就能够计算出该点的x和y坐标。长度怎么求?那太简单了!由于你有了类似三角形,因此长度就是简单的L乘以n/z:
所以,x坐标是x * n/z,y坐标是y * n/z。第一步作完了。
第二步只是简单的执行你上一部分作的一样的映射,因此是时候回顾下你在正交投影中学习到的推导公式了。回想下把x和y坐标映射到规范视域体,像这样:
如今你能够再次调用这些公式,除非你要考虑到投影;因此,把x用x * n/z代替,把y用y * n/z代替:
如今,经过乘以z:
这些结果有点奇怪。为了把这些等式写进矩阵,你须要把它们写成这种形式:
但很明显,如今还作不到,因此如今看起来进入了僵局。应该作什么呢?若是你能找到个办法得到z'z的公式就像x'z和y'z那样,你就能够写一个变换矩阵把(x, y, z)映射到(x'z, y'z, z'z)。而后,你只须要把各部分除以点z,你就会获得你想要的(x', y', z')。
由于你知道z到z'的转换不依赖于x和y,你知道你想要一个公式形如z'z= pz + q,p和q是常量。而且,你能够很容易的找到那些常量,由于你知道在两种特殊状况下如何获得z': 由于你要把[n, f]映射到[0, 1],你知道当z=n时z'=0,和z=f时z'=1。当你把第一组值代入z'z = pz + q,你能够解得:
如今,把第二组值代入,获得:
把q的值代入等式,你能够很容易的解得p:
如今你有p的值了,而且刚刚你求得了q= –pn,因此你能够解得q:
最后,把p和q的表达式代入最原始的公式中,得:
你就快完成了,可是你处理这个问题的不寻常的性质须要你也处理齐次坐标w。一般状况下,只是简单的设置w' = 1 ——你可能已经注意到在一个基本的变换下最后一行老是[0, 0, 0, 1]---可是如今你在为点(x'z, y'z, z'z, w'z)写一个变换。因此取而代之的,把w' = 1写成w'z = z。所以最后用于透视投影的等式以下:
如今,当你把这个等式写成矩阵的形式,获得:
当你把这个矩阵用于点(x, y, z,1),它将产生(x'z, y'z, z'z, w'z)。而后,你应用一般的步骤去除以齐次坐标,获得(x', y', z', 1)。那就是透视投影。Direct3D的D3DXMatrixPerspectiveOffCenterLH()方法也实现了上述公式。正如正交投影,若是你假设视域体是对称的而且中心是z轴(也就是r = -l,t = -b),你能够简单的用视域体的宽w和高h改写矩阵中的各项:
Direct3D的D3DXMatrixPerspectiveLH()方法也生成这个矩阵。
最后,还有个常常用的上的透视投影的表示。在这种表示中,你根据摄像机的可视范围定义视域体,而不用去担忧视域体的尺寸。此概念参阅图6:
图6: 视域体的高由垂直可视范围的角度a定义
垂直可视范围的角度是a。这个角度被z轴一分为二,因此根据基本的三角函数,你能够写下面的方程,关联a和近平面n以及屏幕高度h:
这个表达式能够取代投影矩阵中的高度。此外,使用横纵比r代替宽度,r定义为显示区域的宽比高的横纵比。因此,获得:
所以,有了用垂直可视范围角度a和横纵比r构成的透视投影矩阵:
在Direct3D中,你可使用D3DXMatrixPerspectiveFovLH()方法获得这种形式的矩阵。这种形式特别有用,由于你能够直接把r设置成渲染窗口的横纵比,而且可视范围角度为p / 4比较好。因此,你真正须要担忧的事情只是定义视域体沿着z轴的范围。
总结
这就是全部的你须要的投影变换背后的数学概念。还有一些其余的不太经常使用的投影方法,而且若是你使用右手坐标系或者一个不一样的规范视域体就会和咱们讨论的有点不一样,可是以本文的结论做为基础你应该很容易可以推导出那些公式。若是你想知道更多的关于投影或者其余变换的信息,看一看Tomas Moller和Eric Haines的Real-Time Rendering,或者James D. Foley, Andries van Dam, Steven K. Feiner和John F.Hughes的Computer Graphics: Principles and Practice;这两本是优秀的关于计算机图形的书。
若是你对本文有任何问题,或者须要指出任何须要更正的地方,你能够经过CodeGuru论坛联系我,个人名字是Smasher/Devourer。
Happy coding!
译者: 流星上的潴
如需转载,请注明出处,感谢!