用opengl绘制曲线多边形涉及的变换矩阵及对应的openGL函数调用

OpenGL(6)
学了很长时间的三维,但是对于坐标变换和变换矩阵这里,总是不是很明白~
看了这篇文章才感觉收获很大:http://blog.csdn.net/zhuyingqingfen/article/details/8657190
现在转载过来,供大家参考学习~~
下面这篇文章详细讲述了OpenGL里的坐标转换,清晰,明了。但是其所谓的渲染管线只包括modelview 转换 和 投影变换,我觉得不是这样的。这只是从坐标角度吧。比如什么顶点着色、光栅化、送至帧缓存都没有涉及到。
原文地址:
1.&OpenGL&渲染管线
OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)和投影变换(Projection Transformation)。做个比喻,计算机图形开发就像我们照相一样,目的就是把真实的场景在一张照相纸上表现出来。那么观测变换的过程就像是我们摆设相机的位置,选择好要照的物体,摆好物体的造型。而投影变换就像相机把真实的三维场景显示在相纸上一样。下面就分别详细的讲一下这两个过程。
1.1模型观测变换
让我们先来弄清楚OpenGL中的渲染管线。管线是一个抽象的概念,之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像水从一根管子的一端流到另一端,这个顺序是不能打破的。先来看看下面的图1:
&&&&&&&&&&&&&&&&&&&&
图1 OPENGL渲染管线&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
图中显示了OpenGL图形管线的主要部分,也是我们在进行图形编程的时候常常要用到的部分。一个顶点数据从图的左上角(MC)进入管线,最后从图的右下角(DC)输出。MC是Model Coordinate的简写,表示模型坐标。DC是Device Coordinate的简写,表示设备坐标。当然DC有很多了,什么显示器,打印机等等。这里DC我们就理解成常说的屏幕坐标好了。MC当然就是3D坐标了(注意:我说的3D坐标,而不是世界坐标),这个3D坐标就是模型坐标,也说成本地坐标(相对于世界坐标)。MC要经过模型变换(Modeling
Transformation)才变换到世界坐标,图2:
世界坐标系和模型坐标系
变换到世界坐标WC(World Coordinate)说简单点就是如何用世界坐标系来表示本地坐标系中的坐标。为了讲得更清楚一些,这里举个2D的例子。如图3:
世界坐标系和模型坐标系的计算
图中红色坐标系是世界坐标系WC,绿色的是模型坐标系MC。现在有一个顶点,在模型坐标系中的坐标为(1,1),现在要把这个模型坐标转换到世界坐标中来表示。从图中可以看出,点(1,1)在世界坐标系中的坐标为(3,4),现在我们来通过计算得到我们希望的结果。首先我们要把模型坐标系MC在世界坐标系中表示出来,使用齐次坐标(Homogeneous Coordinate )可以表示为矩阵(注意,本教程中使用的矩阵都是以列向量组成):其中,矩阵的第一列为MC中x轴在WC中的向量表示,第二列为MC中y轴WC中的向量表示,第三列为MC中的原点在WC中的坐标。对齐次坐标系不了解的同学,请先学习游戏数学方面的知识。有了这个模型变换矩阵后,用这个矩阵乘以在MC中表示的坐标就可以得到该坐标在世界坐标系中的坐标。所以该矩阵和MC中的坐标(1,1)相乘有:
这也正是我们需要的结果。现在让我们把相机坐标也加进去,相机坐标也称为观测坐标(View
Coordinate),如图4和图5。
ModelView变换的三个坐标系
ModelView变换计算
来看看MC坐标中的点(1,1)如何在相机坐标中表示。从图5中可以直接看出MC中的点(1,1)在相机坐标系VC中为(-2,-2)。和上面同样的道理,我们可以写出相机坐标系VC在世界标系WC中可以表示为:
那么世界坐标系中的点转换为相机坐标系中的点我们就需求VC的逆矩阵:
那么世界坐标系WC中的点(3,4)在相机坐标系VC中坐标为:
上面的变换过程,就是可以把模型坐标变换为相机坐标。在OpenGL中,当我们申明顶点的时候,有时候说的是世界坐标,这是因为初始化的时候世界坐标系、模型坐标系和相机坐标系是一样的,重合在一起的。所以OpenGL中提供了模型观测变换,它是把模型坐标系直接转换为相机坐标系,如图4。现在我们已经计算得到了VC-1和MC,如果把VC-1和MC相乘,就可以得到模型坐标在相机坐标中的表示。为了得到模型坐标系中的坐标在相机坐标系中的表示,这就是OpenGL中的ModelView变换矩阵。这也是ModelView变换的名字的由来,它是通过了上面两个步骤得到的。那么这里,ModelView变换矩阵M为:
现在只要用上面的模型观测矩阵M乘以模型坐标系MC中的坐标就可以得到相机坐标系中的坐标了。模型观测变换的关键就是要得到相机坐标系中的坐标,因为光照等计算都是在这个这个坐标系中完成的。下面我们实际OpenGL程序中检查一下。在程序中,为了计算方便,我们使用图6中的模型。
ModelView变换计算模型
根据图中的数据,我们分别可以写出对应MC和VC-1,从而求得观测变换矩阵M。
现在程序中用glGetFloatv()这个函数来获得当前矩阵数据来检查一下。
如果在上面程序段中最后一个glGetFloatv(GL_MODELVIEW_MATRIX, m)处设定断点的话,就可以看到图7所显示的数据。
ModelView变换矩阵数据
到这里,整个ModelView变换就完成了。通过ModelView变换后得到是相机坐标系内的坐标。在这个坐标系内典型的计算就是法线了。现在再来看看后面一个阶段。
//////////////////////////////////////////////////////////////
我的理解:ModelView 变换矩阵,就是完成从模型坐标到View坐标的转换,是坐标系之间的大变换。注意,modelview 既有model,也有view。不只是一个model的矩阵。
只是对model的进行平移或旋转的函数为 &glTranslatef等函数,称作模型变换!它的坐标是基于模型本身的,即位于模型坐标系类,比如glTranslatef(0,&0,&-3);的3个坐标值。
只是针对view进行设置的函数为 &gluLookAt,它的坐标系是view坐标系,比如
它里面的坐标的原点位于相机坐标系的原点。参看下面的投影变换。
/////////////////////////////////////////////////////////////////
1.2投影变换
先还是复习一下OpenGL的渲染管线。图1中可以看到,在投影变换(Projection Transformation)中也分为两个部分,第一个部分是将上个阶段得到的坐标转换为平面坐标,第二个部分是将转换后的平面坐标在进行归一化并进行剪裁。一般地,将三维坐标转换为平面坐标有两种投影方式:正交投影(Orthogonal Projection)和透视投影(Perspective Projection)。
1.2.1&正交投影
正交投影很简单,如图8,对于三维空间中的坐标点和一个二维平面,要在对应的平面上投影,只需将非该平面上的点的坐标分量改为该平面上的坐标值,其余坐标不变。
比如将点(1,1,5)正交投影到z=0的平面上,那么投影后的坐标为(1,1,0)。在openGL中,设置正交投影可以使用函数:
该函数可以设置正交投影的投影空间,在该空间以外的坐标点就不会被投影到投影平面上。函数中的六个参数分是投影空间六个平面,如图9:
图9 OpenGL正交投影空间和投影变换
在图9中,大的投影空间是根据这六个参数设置的投影空间,OpenGL会自动将该空间归一化,也就是将该空间或立方体转化为变长为1的正六面体投影空间,并且该证六面体的中心在相机坐标系的原点。一旦设置使用glortho函数设置投影空间,OpenGL会生成投影矩阵。这个矩阵的作用就是将坐标进行正交投影并且将投影后的坐标正规化(转换到-1到1之间)。要注意的是,生成该矩阵的时候,OpenGL会把右手坐标系转换为左手坐标系。原因很简单,右手坐标系的Z轴向平面外的,这样不符合我们的习惯。该矩阵的矩阵推导这里就不详细说明了,不了解的同学可以参考游戏数学方面资料,这里只给出正交投影矩阵。
这个矩阵看来很复杂,其实计算很简单。举个例子,现在设置了这样的正交投影空间glOrtho(-10,10,-10,10,-10,10),这是个正六面体空间,变长为10。把这些参数带入上面的矩阵可以得到
现在还是在OpenGL程序中来检查一下。在OpenGL程序中添加下面代码段:
在glGetFloatv(GL_PROJECTION_MATRIX,m)处设定断点就可以看到图10中所显示的信息。
正交变换矩阵数据&
1.2.2透视投影
透视投影和正交投影最大的区别就是透视投影具有远近感。
图11 透视投影
透视投影采用了图11中的模型,这样的模型就是保证远的物体看起来小,近的物体看起来大。 在OpenGL中设置透视投影可以使用函数:
该函数也会根据给定的参数生成一个投影空间。如图11中,该投影空间是一个截头体。同样地,OpenGL会自动生成透视投影矩阵,该矩阵也会让3D坐标投影在投影平面上,并且将投影后的坐标也进行正规化。下面也直接给出OpenGL中使用的透视投影矩阵。
下面在OpenGL中添加下面代码段:
设置断点后,我们可以看到图12中显示的数据。
透视变换矩阵数据
到此为止,整个投影变换就完成了。透过投影变换后得到的是正规化的投影平面坐标。这为下一个阶段的视口变换(View port Transformation)做好了准备。
1.3视口变换
现在到了最后一个阶段了。这个阶段叫做视口变换,它把上个阶段得到的正规化的投影坐标转化为windows 窗口坐标。视口变换会将投影平面上的画面映射到窗口上。在OpenGL中可以使用函数
来进行对窗口的映射,如图13。
图13 视口变换glViewport(width/2, 0, width/2, height/2)
举个例子说明,比如上个阶段中得到了一个顶点的坐标为(0,0,0.5,1),根据这个坐标,该顶点位于投影平面的正中间。如果将该点映射到大小为50*50的窗口上时,那么它应该位于屏幕的中间,坐标为(25,25, 0.5,1)。当然这里深度&#是不会改变的。有的同学肯定有疑问了,既然投影到了窗口上,那么还要深度&#干什么?这里要注意的是,虽然在窗口上显示时只需要x,y坐标就够了,但是要在2D窗口上显示3D图形时深度值是不可少的。这里的深度值不是用于显示,而是用于在光栅化的时候进行深度测试。
OpenGL也会根据glViewport函数提供的参数值生成一个视口变换矩阵
该矩阵把上个阶段得到的正规化坐标映射到窗口上,并且将正规化坐标中的深度值在转换到0到1之间。所以在深度缓冲中最大值为1,最小值为0。视口变换结束后,OpenGL中主要的图形管线阶段就算完成了,后面就是光栅化等等操作。再来回顾一下图1,现在相信大家对这个渲染管线有了一定的认识了,也明白了每一个阶段对应的变换矩阵以及如何进行坐标之间的转换的。
2.&屏幕坐标转换为世界坐标
通过前面的教程,以及现在大家对OpenGL整个渲染管线理解后,现在要将屏幕上一点坐标转换为世界坐标就比较容易了。从图形管线的开始到结束,一个模型坐标系中的坐标被转化为了屏幕坐标,那么现在把整个过程倒过来的话,屏幕上一点坐标也可以转为为世界坐标。只要在对应的阶段求得对应变换矩阵的逆矩阵,就可以得到前一个阶段的坐标。这整个过程可以用图14表示。
图14屏幕坐标转换为世界坐标
图中显示的过程完全就是OpenGL渲染管线的逆过程,通过这个过程,屏幕上的点就可以转化为世界坐标系中的点了。可能又有的同学要问,当鼠标点击屏幕上一点的时候并没有深度信息,转换的时候要怎么办呢?这个时候可以使用OpenGL函数
该函数能够获得屏幕上一点对应像素的深度信息。有了这个深度信息,就可以利用上面过程把屏幕上一点转换为世界坐标了。在OpenGL中,上面的过程其实已经有现成的函数可以使用,那就是
该函数直接将屏幕上一点转换对应的世界坐标,该函数的内部实现其实还是上面的那么逆过程。下面给出利用该函数获取世界坐标的代码段。
代码中函数返回类型GVector是用户定义的向量类,返回的是齐次坐标。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:14906次
排名:千里之外
转载:24篇
(1)(5)(6)(1)(8)(2)(3)(1)背景 当前3D图形界主要有两个:微软的Direct 3D以及某组织的OpenGL。曾经一度OpenGL几乎占据所有3D图形领域,这在巨人微软面前简直就是屌丝逆袭。曾几何时微软搞IDE borland公式倒闭了,后来微软搞浏览器,网景公司解散,员工卷铺盖走人了,也就是说微软搞谁,谁倒霉。直到OpenGL的出现,打破了这一魔咒,在与微软竞争的前期,OpenGL几乎甩了微软几条街,并成为事实上的工业标准。后来在微软的大力绞杀下,OpenGL几乎被完全赶出了游戏领域,退居高端图形领域。基本上现在是微软的Direct 3D统治游戏领域,而OpenGL则在高端专业图形领域占绝对统治地位。微软还是微软,OpenGL已经不是以前的OpenGL了,等会。。等会。。这句话咋这么熟悉?想起来了赵本山的小品里说过:你大爷还是你大爷,你大妈已经不是你5年前的大妈了,为什么这么说呢?话说搞OpenGL的那家公司被微软逼疯了,没错。。解散了。。但是OpenGL并没有消失,而是交给某开源组织托管、开发与维护了。哎。。。不说了,都是泪啊。。凡是牵扯到微软,那就是一部血泪史啊。。逼疯了无数企业。。但是话有说回来,商场上比尔盖茨是个侩子手而慈善上这家伙也不小气。。大把大把的捐钱。。
坐标系空间
在OpenGL里面,3D坐标系的X轴自左向右增大,y轴自下向上增大,z轴正方向从屏幕中心指向观察者。 坐标系有以下几种:局部(模型)坐标系、世界坐标系、相机坐标系、屏幕坐标系;对应的矩阵变换则有模型变换、视图(相机)变换、投影变换,其中投影变换分为正视投影、透视投影。而坐标系之间的转换要用到矩阵。世界坐标系相当于是虚拟宇宙,位置固定不变,而局部(模型)坐标系是绘图的一个局部空间,是相对的,相机坐标系是以相机的镜头(或者人的眼睛)来观察物体的视觉空间。在3D中画图是现在局部坐标空间绘制,然后通过矩阵变换转移到世界坐标空间,接着转换到相机空间,然后在投影,最终会在光栅化的二维屏幕上渲染图形。
局部坐标空间又叫模型空间,绘制图形是在模型空间绘制,绘制完成后经过模型变换转换到世界坐标系空间。在OpenGL中渲染三维模型是以图元为最小单位进行渲染的,图元有三角形,四边形等,绝大多数情况下都是以三角形图元渲染。图元如三角形是有3个顶点组成的,那么为什么最小图元不是顶点而是诸如三角形呢?这就好比提到一个化学物质人们会说这个物质是由很多原子组成的,而不会说是由电子、中子、原子核组成,因为电子、中子及原子核是一个有机整体;同样三角形图形的三个顶点也是一个有机整体。
上面图片一是层次细节LOD地形网格,513像素X513像素,不过并没有达到完全的分辨率,而且不同的地方分辨率不同,此是后话。图片二是用OpenGL加载的一个ms3d格式的三维小汽车,这个小汽车是用3d max制作的3ds文件经过MilkShape 3D转换后得到的ms3d文件。这两个三维模型都是以三角形网格渲染而成的,不同的是图一没有进行文理贴图,而是以线框的模式渲染的,这样做是为了更好的看到3D渲染的细节;对应的图二则是以平滑模式渲染并且贴有文理。从本质上来说三维图像的最小单位是顶点,这些顶点以特定的方式送往3D API(典型的如OpenGL 3d api或者D3D api)并以三角形网格的形式进行渲染。当然也可以选择其他多边形如四边形进行渲染,但是三角形渲染最为方便,几乎所有的3D图形都是以三角形为图元进行渲染。从图一可以看出这个地形是有很多小的三角形网格组成的,事实上这个地形网格是以三角形扇的方式组织渲染的。现在我们看到的这两个图形是在屏幕坐标空间观察的,那么他们的第一站其实就是局部(模型)坐标空间,经过一系列3D流水线最终送往二维屏幕进行光栅化处理和渲染。在局部坐标系的物体有N个顶点组成,如果变换到世界坐标系的话需要对所有顶点做变换,共计N次变换。设物体的任一顶点为(loc_x ,loc_y , loc_z)。这个坐标是在局部坐标系下,要想变换到世界坐标空间需要将局部坐标系的原点移动到对应的位置(world_x,world_y,world_z)处,并且同时移动三维模型的所有顶点坐标。很容易得到最终的坐标:(loc_x+world_x,loc_y+world_y,loc_z+world_z)。现在我们先来看一幅图片:
我们惊奇的发现这个计算结果正是我们想要的,没错,你猜对了,在3D图形里面顶点的变换都是通过矩阵完成的,而这个平移变换是最简单的一种矩阵变换,其他的还有旋转、缩放等等。上面的图一、二就是在3D流水线里经过一些列像上述那样的矩阵变换最终才从幕后走向台前展现在大家眼前。由上可以看出每一个顶点经历了16次乘法运算、12次加法运算,共计28N次计算,当然在矩阵变换之间可能还有进行了诸如光照、纹理等操作。假如一个三维场景共有100万个顶点,那么就要做2800万次计算,这还没有加上后面的相机变换、投影变换所做的矩阵运算以及光栅化、渲染等操作。由此可以看出来运算量是非常的大。那么显卡能承受如此巨大的运算量吗?后面会提到,3D 图形库(如OpenGL)会在图形进行渲染前将不必要的顶点裁剪掉,这样就不用渲染他们了,从而节省了GPU的运算量。但是光是依靠3D 图形库的裁剪功能还是不够,虽然裁剪掉了不需进行渲染的顶点,然而这些顶点已经消耗掉了大量的矩阵运算,尤其是当场景非常细腻的时候也就意味着顶点数目非常巨大。那么有没有方法可以在坐标系统进行矩阵变换前就被提前裁剪掉呢?答案是肯定的。举个例子,对于图一中的地形网格来说,他被渲染到屏幕是有条件的,其一:不在照相机视景体空间内的物体将被忽略不予处理;其二:对于远处的地面以低分辨率渲染,近处则以高分辨率渲染;其三:粗糙的部分以高分辨率渲染,平坦的部分以低分辨率渲染。假如有一个600X600的网格经过测试不在照相机的视景体内,那么这36万个顶点就不用进行后续的大量矩阵运算,而测试所消耗是由8个顶点组成的六面体,这个运算量的消耗无疑是值得的。至于如何进行相机裁剪,在层次细节算法里面将会详细说明。在OpenGL以及D3D里面用户一般不会直接操作矩阵,OpenGL会将用户的函数调用解释为矩阵,比如用户调用gluPerspective(参数。。)时,OpenGL会根据函数的参数设置视图矩阵并与当前的视图矩阵相乘。
世界坐标系空间经过上面的平移操作物体就被移动到世界坐标系,这个坐标系是固定不变的,相当于虚拟宇宙中心。此时物体还不能呈现于屏幕,还要经历九九八十一难才能与观众见面。进入世界坐标系以后,照相机的视点可能不在原点,并且视点可能还不是朝向z轴负方向。此时就要将照相机平移到世界坐标系原点,并且调整方向使相机视点朝向z轴负方向。之所以要这样调整是因为如果相机位于原点并朝向z轴负方向的话会给处理带来极大的方便,至于是什么方便呢,我也不知道,反正是专家说的,至于你信不信,反正我是信了。在说明如何变换到相机空间我们先来看一下几个矩阵操作。设有世界坐标系空间的某一个点A(world_x,world_y,world_z)分别绕x轴 y轴 z轴旋转angle_x、angle_y、angle_z度到达B点。那么求其旋转后的坐标。
这里为了更好推导将坐标系进行了旋转。
如图三A点绕x轴 旋转angle_x度求其旋转后的坐标。A点绕X轴旋转所形成的平面必定与y轴 z轴所构成的平面平行,因此将A点 B点投射到y_z平面上得到A撇点 B撇点,A撇&&B撇点在z y平面上的坐标显而易见就是A点 B点对应的 y z坐标。如图可知C角的大小就是angle_x 即C=angle_x;D点对应的那个角是OA与y轴的夹角。
对于B撇点:
对于A撇点:
将第一个方程式展开得到:
将第二个方程代入第三个方程式得到:
显而易见绕x轴旋转x坐标值自然是不变的,也就是说旋转后顶点坐标为:
(world_x, world_y*cos(angle_x)+world_z*sin(angle_x),-world_y*sin(angle_x)+world_z*cos(angle_x)); 下面我们再看一个矩阵运算:
我们再一次吃惊的发现这个正是我们所想要的结果,难道冥冥之中矩阵与3D图形变换有着不解之缘?OpenGL里面矩阵是按照列优先的原则存储于一个一维数组里面,三维顶点不是以三维向量二是以四维向量来表示比如(x,y,z,w)来表示的,w初始默认情况下为1,在变换过程中w的值会跟着发生变化,并且w也有它的用处,此是后话。我们来看一个更加一般的矩阵:
在用有特殊含义的字符串来填充相应位置后,我们会发现。。。没错。。前三行三列恰好表示x y z坐标轴的方向向量,每一列(除了第四列) 的最后一个值默认是0,第四列最后一个值是1,同样在变换过程中它可能发生变化,它的一个用处是齐次化坐标。那么一开始的矩阵前三列分别是(1,0,0,0) (0,1,0,0) (0,0,1,0)很显然这三个坐标分别表示x y z轴的方向向量(此时局部坐标系的原点与世界坐标系原点重合为(0,0,0)),那么旋转后这三列还能表示三个坐标轴方向向量吗?这个矩阵怎么解释呢?我们再来看一个图:
在上述的矩阵变换中我们曾经说过绕X轴旋转物体angle_x度,这相当于物体不动把坐标轴绕X轴向相反的方向旋转angle_x度。如图A表示绕X轴旋转的角度,如果以上述变换为例,那么A=angle_x。显然在新的局部坐标系下对应的B C点在原来的坐标系中的坐标分别是 (0,-1*sin(angle_x),1*cos(angle_x))&&&&&&&&&& (0,1*cos(angle_x),1*sin(angle_x)) 再看矩阵一,发现这个正是新的坐标系的三个方向向量,这个方向向量是以原坐标系为参考系得来的。在3D中我们旋转物体与物体不动旋转坐标系的效果是一样的,只是四维方式的问题而已。在回过头来看矩阵二,我们发现(translate_x ,translate_y,translate_z)是局部矩阵的原点在原坐标系下的坐标,而(x1,y1,z1)是新的局部坐标系的x轴上的一个点在原坐标系下的坐标,其他的以此类推,局部坐标系的原点定了,三个坐标轴上的点也定了,我们吃惊的发现局部坐标系在原坐标系为参考的情况下已经描绘出来了。如果。。如果原坐标系是世界坐标系,没错,如果成真的话我们就经过一系列矩阵变换得到了局部坐标系在世界坐标系中的位置,于是就刻画出来了三维物体的顶点坐标在世界坐标系下的坐标值。 现在还记得我们一开始的问题吗?你可能已经不记得了,问题是如何将世界坐标系变换到相机坐标系。
图片来自3D游戏编程大师。
如上图相机位置(cam_x,cam_y,cam_z)与y轴夹角为angle_y。如果要变换到相机空间的话首先要将相机平移到原点,结合前面所说的也就是设置矩阵的最后一列translate_x=-cam_x, translate_y=-cam_y, translate_z=-cam_z,然后使相机的镜头绕y轴旋转-angle_y度。前面已经讲过绕X轴旋转angle_x度的矩阵方程,那么绕x轴旋转-angle_x度的方法就是将前述矩阵的角度设置为-angle_x就行了,y轴旋转的也可以以此类推。
相机坐标空间经过上述的矩阵变换已经到达了相机坐标空间,现在到了投影的时刻了,前面定义了相机的位置和方向,但是相机的视野不是无限远的,必须为它制定一个视景体,在视景体内的物体将被投影到视平面,不在视景体内的物体将被丢弃不处理
如图便是一个视景体,这个投影是透视投影,所谓透视投影就是给人一中置身于实际场景中的感觉,远处的物体显得小,近处的物体显得大。还有一中投影叫正视投影,这个主要用于CAD程序中。三维图形主要使用透视投影。在OpenGL这个视景体可以用api函数gluPerspective(angle,fov,w_div_h,near,far)来定义。这个函数将产生一个透视投影矩阵并与当前的投影矩阵相乘。
投影空间经过上述矩阵变换就到了投影空间了,接着就是后续的视口变换,渲染等工作了。
裁剪现在我们再来看一个地形网格
这个地形网格和图片一是一个程序生成的,不同的是前者调节系数是8,后者调节系数是25结果导致了后者的分辨率明显大于前者。经过前面坐标系空间变换的介绍我们知道这幅地形网格呈现在我们眼前之前经历了模型变换,视图变换,投影变换等。这个地形网格是513*513尺寸,而且没有达到完全分辨率,那么如果达到了完全分辨率,势必顶点数目大幅增加,再如 如果地形尺寸是呢,顶点数将增加400倍,再如一个实际的场景可能还有大量的树木,房屋,动物,人车等。除了矩阵运算还有纹理贴图,光照,雾化等,处理起来相当的消耗GPU和CPU。如果不控制好的话,系统渲染后运行非常卡。其中有一个可以改善的方法是LOD算法即层次细节算法,这个算法常用来绘制大规模实时地形。这个算法其中需要用到一个叫相机裁剪的算法。接下来就说一下相机裁剪。所谓相机裁剪就是在上图视景体中的物体进行处理,不在里面的物体被丢弃。前面已经说过渲染的时候OpenGL会自动丢弃不在视景体内的物体以避免渲染,而此处的裁剪进一步减少了矩阵运算的次数,也就是说如果物体需要裁剪的话,那么在模型空间就被裁剪了,而没有经过后续的各种矩阵变换。那么必须设计一个算法来检测某个物体是否被相机裁剪。方法之一是将待处理的物体构造一个AABB包围盒,然后用包围盒的顶点与物体顶点所在的平面做相交测试,不想交就被裁剪掉,否则保留。
typedef struc aabb {&&&& &&&&&&someType&&min[3]; &&&&&&someType&&max[3];& } AABB;
这个结构体里面min[3]里面保存的是(min_x,min_y,min_z)对应max[3]里面保存(max_x,max_y,max_z) 这些坐标是采用某种方法找到的物体的最小坐标或最大坐标值,最笨最耗时的办法就是遍历物体的每个顶点找到对应的最小最大坐标值
那么怎么求视景体的六个面的方程呢?在OpenGL中经过透视投影后前文的视景体变为了规则的长方体,这个投影空间中顶点的坐标形式为(pro_x,pro_y,pro_z,w),现在到了w值大显神通的时候了,如果程序员没有在程序中故意操作矩阵的值,那么现在这个w就是每个坐标值得各个分量的绝对值的最大范围,显而易见如果物体的坐标满足-w&x&w;-w&y&w;-w&z&w的话那么物体在这个投影空间内否则被裁减掉。这个投影空间的左平面的方程为x=-w,但是我们把它写作-x-w=0的形式,这样他的法向量就是(-1,0,0)也就是指向投影空间的外部,保证其他六个面的法向量也指向物体外部,这样是为了方便后面的相交测试,当然使所有平面方程的法向量指向空间内部也是可以的,总之保持一致性就可以了。对于一个普通的平面方程A*x+B*y+C*z+D=0和一个顶点(a,b,c)将其代入原方程,如果A*a+B*b+C*c+D=0的话这个顶点就在平面上,若小于零则在平面一侧若大于零则在另一侧,这个方法就可以用于包围盒与相机裁剪测试中。我们的目的是在物体经不经过模型、视图、投影变换直接进行相交测试,那么必然要求得相机视景体在经过相机变换 模型变换前的六面体的6个平面方程。那么从当前的这个投影后的长方体就可以推导出这个所需要的六面体的六个面的方程。设局部坐标系下物体的任意一个顶点是vertex_local=(local_x,local_y,local_z,w)这里w=1;经过投影变换后的坐标vertex_per=(per_x,per_y,per_z);设M是模型变换矩阵,V是相机变换矩阵,P是透视投影变换矩阵。那么将会有如下的坐标变换关系vertex_local*MVP=vertex_per;其中MVP是模型视图投影三个矩阵的乘积,这个是显而易见的。这是目前可以利用的一个等式,我们就是从这个等式推导出面的方程。这其中的矩阵M V P分别是模型 视图 投影矩阵,可以由OpenGL api获得,然后计算其乘积就行了。先来看一个图片:
在这个矩阵乘法当中一个原始坐标乘以一个4*4矩阵得到一个新的坐标。 其中 x_new=X1*x_origin+x2*y_origin+x3*z_origin+x_t*w; &&&&w_new= a*x_origin+b*y_origin+c*z_origin+d*w; 由上述可知投影之后在x轴方向的范围是[-w_new,w_new] 所以右平面上的点的方程式x_new=w_ 结合上两个方程式得到:x_origin*(X1-a)+y_origin*(X2-b) +z_origin*(X3-c)+w*(x_t-d)=0; 由于初始w=1;所以&&&&&x_origin*(X1-a)+y_origin*(X2-b) +z_origin*(X3-c)+(x_t-d)=0; 其中小括号里的数据是可以计算出来的,相当于常量,于是上式的形式就是Ax+By+Cz+D=0。显然这是个平面的方程,而x y z是初始的点,那么这个方程就是初始的点所在的平面的方程。同样道理求得另外5个面的方程,然后做相交测试,便可以进行裁剪了。现在包围盒aabb 六个平面的方程已经了。 那么现在如何进行相交测试呢?
如图一开始最小 最大点是红色的min max点,经过轴分离后最小 最大点变化为绿色的min&&max在知道包围盒aabb和6个平面的方程后就可以使用轴分离方向来测试相交问题。如果aabb最小的点在某个平面外的话,那么其他点一定在平面外。但是现在有一个问题就是怎么定义最大最小点,最大最小分别表示距离平面距离最大最小的点。一开始没有考虑平面的时候直接比较坐标值的大小来确定最大最小点,现在引入平面方程后就要重新调整aabb得到新的包围盒。这里不试图给出严格的数学证明,只举一个简单的例子来说明为何要重新调整aabb。轴分离的话就是分别考虑x y z轴。现在假设考虑x轴,上述的投影后的长方体左平面方程为-x-w=0;假设aabb.max[]={-3,-3,-3} aabb.min[]={-5,-5,-5}但是现在距离-x-w=0(w&1.0) 最小的点显然是{-3,-5,-5) 最大点显然应该是(-5,-3,-3),然后y z轴以此类推,得到更新后的aabb。将新的aabb.min代入上面的平面方程如果大于0的话,算法立刻return,这个aabb不在视镜体内,如果小于0的话,在将aabb.max代入,如果大于0的话设置标志insect=然后循环处理另外几个平面方程。最终若aabb不在包围盒内将会在循环的过程中return,如果循环顺利结束,并且insect=true,说明aabb与视景体相交,否则aabb完全在视景体中。
自此,本文就结束了,这只是任务中的一个小部分。。还有很多其他任务需要做。。
我的CSDN博客:
邮箱:。欢迎指出文中的错误之处,本人也是初学者,因为毕设做这个,于是就开始看这方面的东西。
& 开源中国(OSChina.NET) |
开源中国社区(OSChina.net)是工信部
指定的官方社区

我要回帖

更多关于 用opengl绘制曲线 的文章

 

随机推荐