转:深入探讨透视投影坐标变换


深入探讨透视投影坐标变换
 


写3d图形程序,就一定会做坐标变换。而谈到坐标变换,就不得不提起投影变换,因为它是所有变换中最不容易弄懂的。但有趣的是,各种关于透视变换的文档却依然是简之又简,甚至还有前后矛盾的地方。看来如此这般光景,想要弄清楚它,非得自己动手不可了。所以在下面的文章里,作者尝试推导一遍这个难缠的透视变换,然后把它套用到 DX和 PS2lib 的实例中去。

    1. 一般概念

    所谓透视投影变换,就是view 空间到project 空间的带透视性质的坐标变换步骤(这两

    个空间的定义可以参考其他文档和书籍)。我们首先来考虑它应该具有那些变换性质。很显然,它至少要保证我们在view空间中所有处于可视范围内的点通过变换之后,统统落在project空间的可视区域内。好极了,我们就从这里着手——先来看看两个空间的可视区域。

    由于是透视变换,view空间中的可见范围既是常说的视平截体(view frustum)。如图,

    (图1)

    它就是由前后两个截面截成的这个棱台。

    从view空间的x正半轴看过去是下图这个样子。

    (图2)

    接下来是project空间的可视范围。这个空间应当是处于你所见到的屏幕上。实际上将屏幕表面视作project空间的xoy平面,再加一条垂直屏幕向里(或向外)的z轴(这取决于你的坐标系是左手系还是右手系),这样就构成了我们想要的坐标系。好了,现在我们可以用视口(view port)的大小来描述这个可视范围了。比如说全屏幕640*480的分辨率,原点在屏幕中心,那我们得到的可视区域为一个长方体,它如下图(a)所示。

    (图3)

    但是,这样会带来一些设备相关性而分散我们的注意力,所以不妨先向DirectX文档学学,将project空间的可视范围定义为x∈[-1,1], y∈[-1,1], z∈[0,1]的一个立方体(上图b)。这实际上可看作一个中间坐标系,从这个坐标系到上面我们由视口得出的坐标系,只需要对三个轴向做一些放缩和平移操作即可。另外,这个project坐标系对clip操作来说,也是比较方便的。

      1. 推导过程

      先从project空间的x正半轴看看我们的变换目标。

      (图4)

      这个区域的上下边界为y’=±1, 而图2中的上下边界为y = ± z * tan(fov/2),要实现图

      2到图4的变换,我们有y’ = y * cot(fov/2) / z。这下完了,这是一个非线性变换,怎么用矩阵计算来完成呢?还好我们有w这个分量。注意到我们在做投影变换之前所进行的两次坐标变换——world变换和view变换,他们只是一系列旋转平移和缩放变换的叠加。仔细观察这些变换矩阵,你会发现它们其实不会影响向量的w分量。换句话说,只要不是故意,一个w分量等于1的向量,再来到投影变换之前他的w分量仍旧等于1。好的,接下来我们让w’= w*z, 新的w就记录下了view空间中的z值。同时在y分量上我们退而求其次,只要做到y’ = y * cot(fov/2)。那么,在做完线性变换之后,我们再用向量的y除以w,就得到了我们想要的最终的y值。

      x分量的变换可以如法炮制,只是fov要换一换。事实上,很多用以生成投影变换矩阵的函数都使用了aspect这个参数。这个参数给出了视平截体截面的纵横比(这个比值应与view port的纵横比相等,否则变换结果会失真)。如果我们按照惯例,定义aspect = size of X / size of Y。那么我们就可以继续使用同一个fov而给出x分量的变换规则:x’ = x * cot(fov/2) / aspect。

      现在只剩下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分量去除结果向量。

        现在我们考虑一下这个变换对全view空间的点的作用。首先是x和y分量,明了地,当z>0时,一切都如我们所愿;当z<0时,x和y的符号在变换前后发生了变化,从图象上来说,view空间中处于camera后面的图形经过变换之后上下颠倒,左右交换;当z= 0 时,我们得到的结果是无穷大。这个结果在实际中是没有意义的,以后我们得想办法弄掉它。再来看z,

      仍旧拿我们上面定义的f(z)函数来看,我们已经知道当z≥Zfar时,f(z)≥1;同时当z→+∞,f(z)→Zfar/(Zfar-Znear);当z→+0时,f(z)→-∞; z→-0时,f(z)→+∞; z→∞时,f(z)→Zfar/(Zfar-Znear).由此我们画出f(z)的图像。

      (图5)

      由此图可以看出当z≤0时,如果我们仍旧使用f(z)进行绘制会产生错误。所以我们会想需要clip操作——只要这个三角形有任意一个顶点经过变换后z值落在[Zfar/(Zfar-Znear), +∞]区间中,我们就毫不怜悯地抛弃她——因为无论如何,这个结果是错的。那么万一有三角形在view空间内横跨了Znear到0的范围,按我们想应该是画不出来了。但是回想一下我们所看见过的DirectX程序,似乎从未看到过这种情况。有点奇怪,但是不得不先放放,稍后再说。

      3.到DirectX中求证

      在DirectX中拿一个用fov生成投影矩阵的函数来看。

      D3DXMATRIX* D3DXMatrixPerspectiveFovLH( D3DXMATRIX* pOut, FLOAT fovy, FLOAT Aspect,

      FLOAT zn, FLOAT zf )

      这个函数恰好使用了我们刚才推导所使用的几个参数,经过一些数据的代入计算之后,我们就会发现它所产生的矩阵就是我们计算出来的。看来,DirectX的思路和我们是一致的。好的,一个问题解决了,但一个新的问题接着产生——DirectX是怎么做clip的?我不知道,而且看样子现在也知道不了,只能期待牛人相助或者是碰到一本好书了。

      4.研究ps2lib的投影变换

        其实投影变换都是一回事,但是PS2lib的函数怎么有点不一样呢?仔细看看,原来我们的思路是先做“归一化”,然后再做view port的放缩和平移,而PS2不是这样——它把“归一化”放在最后。接下来,我们就按这个顺序试试。

      先看缩放操作,把它和除z交换顺序很方便,直接换便是了。于是我们记view port 的宽度为Vw,高度为Vh, Z缓存的最大值为Zmax, 最小值为Zmin则有

      x’ = x * cot(fov/2)/aspect*(Vw/2)

      y’ = y * cot(fov/2)*(Vh/2)

      z’ = Zfar(z-Znear)/(Zfar-Znear) * (Zmax-Zmin);

      w’ = z

      再看平移部分,既然是要平移后再除,则必须平移原来的z倍,于是我们又记view port中心坐标为(Cx, Cy),就有

      x’’ = x’ + z * Cx

      y’’ = y’ + z * Cy

      z’’ = z’ + z * Zmin

      w’’ = w

      好的,我们看看cot(fov/2)等于什么,从图2看,实际上它就是D/(Vh/2),那么cot(fov/2)/aspect实际上就是D/(Vw/2)。但是,ps2在这上面耍了个小花招,它在view空间中的view port和project空间的view port可以不相等。最明显的一点是,它在view空间中的view port的高度为480,但实际上它的输出的y向分辨率只有224。也就是说,ps2想要输出纵横比等于电视机的图像,就必须在y向上再加一个缩放。这个缩放在我们的变换中体现在哪呢?就在y’ = D/(Vh/2) * (Vhscr/2)中,注意到两个Vh不相等(project空间中的Vh记成Vhscr),两个值一运算就得到x’ = D*(224/480) = 0.466667D。这个0.4666667就是ps2lib函数参数ay的由来。同理,我们亦可得知ax一般应取值为1。那么,实际上ps2lib函数的scrz,ax, ay三个参数的作用等同于DirectX的象形函数的fov和aspect,在确定的规则下,他们可以相互转换,得到性质完全相同的透视变换。至于这个规则,这里就不给出了。

      转回正题,有了上面的讨论,我们就可以展开我们的变换表达式如下,

      x’’ = x * scrz * ax + z * Cx

      y’’ = x * scrz * ay + z * Cy

      z’’ = z * (Zfar*Zmax–Znear*Zmin)/(Zfar – Znear)

      –Zfar*Znear*(Zmax-Zmin)/(Zfar-Znear)

      w’’ = z

      z分量好像还有点不一样,注意到一般ps2程序在z buffer的操作为greater&equal,而DirectX的操作为less&equal,就是说,z方向得做些变动——得把z=Znear映射到z’’ = Zmax,z=Zfar映射到z’’=Zmin。说变就变,我们马上有

      z’ = Zfar(z-Znear)/(Zfar-Znear)*(Zmin-Zmax)

      z’’ = z’+Zmax

      再次展开,得到z’’ = z * (Zfar*Zmin–Znear*Zmax)/(Zfar – Znear )

      + Zfar*Znear*(Zmax-Zmin)/(Zfar-Znear)

      好了,用矩阵把这个变换写出来,

      scrz*ax 0 0 0

      0 scrz*ay 0 0

      Cx Cy (Zfar*Zmin–Znear*Zmax)/(Zfar – Znear ) 1

      0 0 Zfar*Znear*(Zmax-Zmin)/(Zfar-Znear) 0,

      这下就完全一样了。下面的任务就是看看这个变换的性质。因为最后同样要除以z,所以x,y分量上的情形的和原来我们推导的DirectX的投影变换是一样的,区别在z分量上。来看新的f(z)函数,它的图像为

      (图6)

      5.结论

      至此,我们已经完成了预定的目标。但是,将坐标变换完全掌握之后,为了做一个像样的图形程序,我们还有更多事情要做——至少在PS2上是这样?。

转:深入探讨透视投影坐标变换,布布扣,bubuko.com

时间: 2024-10-08 10:13:57

转:深入探讨透视投影坐标变换的相关文章

透视投影矩阵的构建

透视投影矩阵的构建 投影矩阵最终建立的是一个平截头体(也可以称为台),在这种变换下呈现远小近大的效果.这里我将我学到知识记录下来,以后备忘用. 蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44926299.欢迎同行前来探讨. 首先是使用OpenGL的glFrustum函数,它要求传入的是前后.左右.上下等参数,这要求这个平截头体是轴对称的.由它构成的矩阵是为: 如果我们使用的不是glFrustum,而是glPerspeti

说说我对OpenGL坐标变换几个关键点的理解

刚接触OpenGL的朋友们,可能对坐标变换不太理解. 本人不才, 接触了三维一段时间后,冒昧说说我的理解, 如有偏差, 请指正. 一:  首先说说什么是世界坐标. 每个三维模型都有自己的局部坐标, 这个大家都好理解,  这个称作模型坐标, 坐标原点可以是模型的中心.   但是一个场景中如果有许多个三维模型,  那要想标准其每个位置, 就需要一个统一的坐标来标定,  那么这个坐标就叫世界坐标.  这都好理解对吧,  下面说点难的. 二:  再说说世界坐标怎么转换到摄像机坐标. 我们知道,  观察三

OpenGL图形管线和坐标变换[转]

1. OpenGL 渲染管线 OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)和投影变换(Projection Transformation).做个比喻,计算机图形开发就像我们照相一样,目的就是把真实的场景在一张照相纸上表现出来.那么观测变换的过程就像是我们摆设相机的位置,选择好要照的物体,摆好物体的造型.而投影变换就像相机把真实的三维场景显示在相纸上一样.下面就分别详细的讲一下这两个过程. 1.1模型观测变换 让我们先来弄清楚OpenGL中的渲

关于科学选择地图投影类型的探讨

关于科学选择地图投影类型的探讨 作者:左正康(11级GIS 2011221108120027) 摘要:地图投影是利用一定数学法则把地球表面的经纬线转换到平面上的理论和方法.由于地球是一个赤道略宽两级略扁的不规则的梨形球体,故其表面是一个不可展平的曲面,所以运用任何数学方法进行这种转换都会产生误差和变形,为按照不同的需求缩小误差,就产生了各种投影方法.投影方法按变形方式可以分为等角投影.等积投影.等距投影:按正轴投影时经纬网的形状可以分为几何投影(平面投影.圆锥投影.圆柱投影.多圆锥投影,几何投影

转载:透视投影的原理和实现

透视投影的原理和实现 by Goncely 转载:http://blog.csdn.net/wong_judy/article/details/6283019 摘  要 :透视投影是3D渲染的基本概念,也是3D程序设计的基础.掌握透视投影的原理对于深入理解其他3D渲染管线具有重要作用.本文详细介绍了透视投影的原理和算法实现,包括透视投影的标准模型.一般模型和屏幕坐标变换等,并通过VC实现了一个演示程序. 1 概述 在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正

OpenGL坐标变换专题

OpenGL坐标变换专题(转) OpenGL通过相机模拟.可以实现计算机图形学中最基本的三维变换,即几何变换.投影变换.裁剪变换.视口变换等,同时,OpenGL还实现了矩阵堆栈等.理解掌握了有关坐标变换的内容,就算真正走进了精彩地三维世界. 一.OpenGL中的三维物体的显示 (一)坐标系统 在现实世界中,所有的物体都具有三维特征,但计算机本身只能处理数字,显示二维的图形,将三维物体及二维数据联系在一起的唯一纽带就是坐标. 为了使被显示的三维物体数字化,要在被显示的物体所在的空间中定义一个坐标系

OpenGl从零开始之坐标变换

http://www.tuicool.com/articles/uiayYrI OpenGL学习脚印: 坐标变换过程(vertex transformation) http://blog.csdn.net/wangdingqiaoit/article/details/51594408 写在前面 前面几节分别介绍了模型变换,视变换,以及给出了投影矩阵和视口变换矩阵的推导,本节从全局把握一遍OpenGL坐标转换的过程,从整体上认识坐标变换过程.相关矩阵的数学推导过程请参考前面几节对应的内容. 通过本

DX 中的坐标变换

Directx中的坐标变换 (1)世界变换和世界坐标系:物体在三维空间的运动和变形过程称为世界变换,如平移.旋转.缩放等.物体在其中运动的三维空间称为世界空间,它的三维坐标系表示称为世界坐标系,物体顶点在世界坐标系里的坐标变换称为世界变换. (2)取景变换和观察坐标系:把图形显示想象成摄像过程,取景变换就像摄像机中摄像机的摆放一样,在三维图形显示中,需要设置一个虚拟摄像机,屏幕显示的图形就是虚拟摄像机拍摄在胶片上的景物.以摄像机位置为参考原点,摄像机观察的方向为坐标轴,建立的坐标系称为观察坐标系

透视投影的原理和实现

透视投影的原理和实现 摘  要 :透视投影是3D渲染的基本概念,也是3D程序设计的基础.掌握透视投影的原理对于深入理解其他3D渲染管线具有重要作用.本文详细介绍了透视投影的原理和算法实现,包括透视投影的标准模型.一般模型和屏幕坐标变换等,并通过VC实现了一个演示程序. 1 概述 在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正交投影和透视投影.正交投影多用于三维健模,透视投影则由于和人的视觉系统相似,多用于在二维平面中对三维世界的呈现. 透视投影(Perspec