参考:添加链接描述
在任何3D图形程序员工具包中的基本矩阵变换中,投影矩阵都更为复杂。一眼就能理解平移和缩放,任何对三角学有基本了解的人都可以想到旋转矩阵,但是投影有点棘手。如果您曾经查询过此类矩阵的公式,就会知道常识不足以告诉您它的来源。但是,我还没有在网上看到很多资源,它们仅描述了如何推导投影矩阵。这就是我将在本文中讨论的主题。
对于刚开始使用3D图形的人们,我应该提到了解投影矩阵的来源可能是我们之间数学上倾向于的好奇心,但这不是必须的。您可以仅使用公式即可;如果您使用的是Direct3D之类的图形API,它将为您构建一个投影矩阵,那么您甚至不需要它。因此,如果本文的细节看起来有些让人不知所措,请不要担心。只要您了解投影的作用,就不必担心投影的工作原理。本文适用于那些想知道比严格必要更多细节的程序员。
概述:什么是投影?
计算机监视器是二维表面,因此,如果要显示三维图像,则需要一种将3D几何形状转换为可以呈现为2D图像的形式的方法。这正是投影所做的。使用一个非常简单的示例,将3D对象投影到2D曲面上的一种方法是简单地丢弃每个点的z坐标。对于多维数据集,它可能类似于图1。
当然,这过于简单,在大多数情况下并不是特别有用。对于初学者来说,您根本不会投影到飞机上。相反,您的投影公式会将您的几何图形转换为一个新的体积,称为规范视图体积。规范视图体积的确切坐标可能从一个图形API到另一个图形API有所不同,但是出于本讨论的目的,请考虑将其视为从(–1,–1,0)到(1,1,1 ),这是Direct3D使用的约定。将所有顶点映射到规范视图体积后,仅使用它们的x坐标和y坐标将它们映射到屏幕。但是,z坐标并非没有用。深度缓冲区通常使用它来确定可见性。这就是您转换为新体积而不是投影到平面上的原因。
请注意,图1还描绘了一个左手坐标系,其中相机向下看正z轴,y轴指向上方,x轴指向右侧。这又是Direct3D所使用的一种约定,我将在本文中通篇使用。对于右手坐标系或对于标准视图体积而言,所有计算均无显着差异,因此,即使您选择的API使用与Direct3D使用的约定不同的约定,所讨论的所有内容仍然适用。
这样,您就可以进入实际的投影变换。那里有很多不同的投影方法,我将介绍两种最常见的投影方法:正交投影和透视。
正投影
正投影法是一种相对简单的投影技术,之所以称为正投影法,是因为所有的投影线都垂直于最终的绘图表面。视图体积(即包含您要显示的所有几何图形的眼睛空间区域)是一个与轴对齐的框,您可以将其转换为标准的视图体积,如图2所示。
如您所见,视图体积由六个平面定义:
因为视图体积和规范视图体积都是轴对齐的框,所以在这种类型的投影中无法校正距离。实际上,最终结果与图1中的结果非常相似,在图1中,您刚刚删除了每个点的z坐标。即使3D空间中相同大小的对象与摄像机相距较远,它们在投影中的显示尺寸也相同。在3D空间中平行的线在最终图像中保持平行。对于像第一人称射击游戏这样的东西,使用这种投影是不可能的(想象一下尝试玩其中的一个而又不知道任何东西有多远!),但是它确实有其用途。例如,您可能会在基于图块的游戏中使用它,尤其是在摄像机以固定角度定位的游戏中。图3显示了一个简单的示例。
图3:正交投影的简单示例。
因此,事不宜迟,开始弄清楚它是如何工作的。最简单的方法可能是分别考虑三个轴中的每个轴,并计算如何沿该轴将点从原始视图体积映射到规范视图体积。您将从x坐标开始。视图体积内的一个点在[l,r]范围内具有x坐标,并且您希望将其转换为[–1,1]范围。
现在,为了将范围缩小到所需大小,请从所有项中减去l,以在左侧产生一个零。您可以在此处采取的另一种方法是平移范围,使其以零为中心,而不是使其端点之一为零,但是代数则这样更整洁,因此我会这样做可读性。
现在,范围的一端位于零,可以将其缩小到所需的大小。您希望x值的范围是两个单位宽,从1到–1,因此您要乘以2 /(r – l)。请注意,r – l是视图体积的宽度,因此始终为正数,因此您不必担心不等式会改变方向。
接下来,从所有项中减去一个,以生成所需的[–1,1]范围。
一些基本的代数使您可以将中心术语写为一个分数:
最后,将中心项分成两个分数,使其形式为px + q; 您需要以这种方式对术语进行分组,以便可以轻松将导出的方程式转换为矩阵形式。
现在,该不等式的中心项为您提供了将x转换为标准视图体积所需的方程式。
获得y的公式所需的步骤完全相同-只需将y替换为x,将t替换为r,将b替换为l-因此,在这里,我将仅显示结果,而不是重复它们:
最后,您需要导出z的公式。在这种情况下,这有点不同,因为您将z映射到[0,1]而不是[–1,1]范围,但这看起来非常熟悉。这是您的起始条件,在[n,f]范围内的z坐标:
您从所有项中减去n,因此范围的下限位于零:
现在,剩下的就是除以f – n以产生最终范围[0,1]。如前所述,请注意,f – n表示观看音量的深度,因此永远不会为负。
最后,将其分为两个部分,使其形式为pz + q:
这为您提供了转换z的公式:
现在,您准备编写正投影投影矩阵。回顾到目前为止的工作,这里是您导出的三个投影方程式:
如果以矩阵形式编写此代码,则会得到:
而已!Direct3D提供了一个称为D3DXMatrixOrthoOffCenterLH()的函数(真是令人满口!),该函数基于该相同公式构造正交投影矩阵。您可以在DirectX文档中找到它。该笨拙的函数名称中的“ LH”是指您使用的是左手坐标系。但是,“ OffCenter”到底是什么意思?
该问题的答案将使您得到正交投影矩阵的简化形式。请考虑以下几点:首先,在眼睛空间中,相机位于原点并直接沿z轴向下看。其次,您通常希望视场向左延伸的距离与向右延伸的距离相等,并且在z轴上方的距离也相等,如下所示。如果真是这样,z轴将直接通过您视线体积的中心,因此您具有r = –l和t = –b。换句话说,您可以完全不用理会r,l,t和b,而只需按照宽度w和高度h以及其他剪切平面f和n定义视图体积。如果将这些替换代入上面的正交投影矩阵中,则会得到以下相当简化的版本:
该方程式由Direct3D函数D3DXMatrixOrthoLH()实现。除非您对投影做一些奇怪的事情,否则您几乎总是可以使用此矩阵来代替上面导出的更通用的“偏离中心”版本。
在完成本节之前,还有一点要讲。值得注意的是,此矩阵可以表示为两个更简单的变换的串联:平移后跟缩放。如果您从几何角度考虑它,这对您应该是有意义的,因为您在正交投影中所做的就是将点从一个与轴对齐的框移到另一个与之对齐的框。观看体积不会改变其形状,只会改变其位置和大小。具体来说,您有:
投影的这种产品形式可能更直观,因为它使您可以更轻松地可视化正在发生的事情。首先,观看量沿z轴平移,以使其近平面与原点重合;然后,应用比例尺将其缩小到标准视图体积的尺寸。这很容易理解,对吗?偏心正投影的矩阵也可以表示为变换和比例的乘积,但是它与上面显示的结果足够相似,因此在此不再列出。
关于它的内容可以进行正交投影,因此现在您可以进行一些更具挑战性的工作。
透视投影
透视投影是一种稍微复杂一些的投影方法,并且更常用,因为它会产生距离的错觉,从而产生更逼真的图像。从几何学上讲,此方法与正投影法之间的区别在于,在透视投影法中,视图体积是一个平截头体(即,一个截顶的金字塔),而不是一个与轴对齐的框。您可以在图4中看到这一点。
如您所见,视锥面的近平面从(l,b,n)延伸到(r,t,n)。通过从原点到近平面上四个点中的每一个点跟踪一条线,直到它们与平面z = f相交,可以找到远平面的范围。因为视锥面距原点的距离越远,它的范围就越广。并且因为您正在将该形状转换为标准的视图体积(即一个盒子);视锥的远端比近端压缩的程度更大。因此,使视锥中更远的对象看起来更小,这给您带来距离的错觉。
由于体积的形状在此变换中发生变化,因此透视投影无法像正投影一样表示为简单的平移和缩放。您将不得不做出一些不同的事情。但是,这并不意味着您在正交投影上所做的工作是没有用的。数学中一种便捷的解决问题的技术是将问题简化为您已经知道如何解决的问题。因此,这就是您可以在此处执行的操作。上一次,您一次检查了一个坐标,但是这次您将同时处理x和y坐标,然后再担心z。您对x和y的攻击计划可以分为两个步骤:
步骤1:在视锥体中给出一个点(x,y,z),将其投影到近平面z = n上。因为投影点在近平面上,所以它的x坐标将在[l,r]范围内,其y坐标将在[b,t]范围内。
步骤2:使用您在正交投影研究中得出的公式,将新的x坐标从[l,r]映射到[–1,1],将新的y坐标从[b,t]映射到[– 1,1]。
听起来不错?然后,看看图5。
在此图中,您绘制了从点(x,y,z)到原点的线,并注意了该线与平面z = n相交的点-这是一个用黑色标记的点。从这些点开始,您将两个垂直于z轴的垂直线放下,突然有了一对相似的三角形。如果您取消了对高中几何的记忆,则相似的三角形是具有相同形状但不一定相同大小的三角形。为了表明两个三角形相似,足以表明它们对应的角度相等,在这种情况下这样做并不难。角度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。这样就得出了步骤1的结论。步骤2只是要求您执行与上一部分相同的映射,因此现在该重新回顾在正交投影研究中得出的公式。回想一下,您将x坐标和y坐标映射到规范视图体积中,如下所示:
现在,您可以再次调用这些相同的公式,除了需要考虑投影之外。因此,将x替换为x * n / z,将y替换为y * n / z:
现在,您乘以z:
这些结果有点奇怪。要将这些方程式直接写到矩阵中,您需要以以下形式编写它们:
但是很明显,现在不会发生这种情况,因此您似乎在这里陷入了僵局。该怎么办?好吧,如果您能像找到x’z和y’z一样找到获取z’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时,就可以用p来求解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,z)。但是,然后,您应用了除以齐次坐标的通常步骤,因此最终得到(x’,y’,z’,1)。这就是透视投影。Direct3D在函数D3DXMatrixPerspectiveOffCenterLH()中实现以上公式。与正射投影一样,如果您假设视锥的视图是对称的并且以z轴为中心(意味着r = –l和t = –b),则可以根据视锥的视图来编写矩阵,从而大大简化事情。宽度w及其高度h:
Direct3D对此矩阵也有一个函数,称为D3DXMatrixPerspectiveLH()。
最后,透视图投影的另一种表示形式经常派上用场。通过这种形式,您不必严格担心视锥的尺寸,而可以根据相机的视场对其进行定义。有关此概念的说明,请参见图6。
图6:视锥台的高度是根据垂直视场角α定义的。
垂直视角为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);这是我在撰写本文时提到的两本关于计算机图形学的优秀书籍。
如果您对本文有任何疑问,或者有任何需要纠正的地方,可以通过CodeGuru论坛上的PM与我联系,我以Smasher / Devourer为名。编码愉快!