OpenGl 坐标转换

1. OpenGL 渲染管线

OpenGL渲染管线分为两大部分,模型观測变换(ModelView Transformation)投影变换(Projection Transformation)

做个比喻,计算机图形开发就像我们照相一样。目的就是把真实的场景在一张照相纸上表现出来。

那么观測变换的过程就像是我们摆设相机的位置,选择好要照的物体。摆好物体的造型。而投影变换就像相机把真实的三维场景显示在相纸上一样。以下就分别具体的讲一下这两个过程。

1.1模型观測变换

让我们先来弄清楚OpenGL中的渲染管线。管线是一个抽象的概念,之所以称之为管线是由于显卡在处理数据的时候是依照一个固定的顺序来的。而且严格依照这个顺序

就像水从一根管子的一端流到还有一端。这个顺序是不能打破的。先来看看以下的图1:

                     图1 OPENGL渲染管线

图中显示了OpenGL图形管线的主要部分。也是我们在进行图形编程的时候经常要用到的部分。一个顶点数据从图的左上角(MC)进入管线,最后从图的右下角(DC)输出MC是Model Coordinate的简写,表示模型坐标DCDevice Coordinate的简写。表示设备坐标。当然DC有非常多了。什么显示器,打印机等等。这里DC我们就理解成常说的屏幕坐标好了。

MC当然就是3D坐标了(注意:我说的3D坐标。而不是世界坐标)。这个3D坐标就是模型坐标。也说成本地坐标(相对于世界坐标)。MC要经过模型变换(Modeling Transformation)才变换到世界坐标,图2:

图2 世界坐标系和模型坐标系

变换到世界坐标WC(World Coordinate)说简单点就是怎样用世界坐标系来表示本地坐标系中的坐标。

为了讲得更清楚一些,这里举个2D的样例。如图3:

图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。

图4 ModelView变换的三个坐标系

图5 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中的模型。

图6 ModelView变换计算模型

依据图中的数据,我们分别能够写出相应MC和VC-1,从而求得观測变换矩阵M

如今程序中用glGetFloatv()这个函数来获得当前矩阵数据来检查一下。


[cpp] view plaincopy
  1. float m[16] = {0}; //用来保存当前矩阵数据
  2. glMatrixMode(GL_MODELVIEW);
  3. glLoadIdentity();
  4. glGetFloatv(GL_MODELVIEW_MATRIX, m);
  5. //相机设置,View 变换
  6. gluLookAt(0.0, 0.0, 5.0,
  7. 0.0, 0.0, 0.0,
  8. 0.0, 1.0, 0.0);
  9. glGetFloatv(GL_MODELVIEW_MATRIX, m);
  10. //投影设置
  11. glMatrixMode(GL_PROJECTION);
  12. glLoadIdentity();
  13. glOrtho(-10,10,-10,10,-10,10);
  14. glMatrixMode(GL_MODELVIEW);
  15. //Modeling变换
  16. glTranslatef(0, 0, -3);
  17. glGetFloatv(GL_MODELVIEW_MATRIX, m);
  18. glBegin(GL_POINTS);
  19. glVertex3f(1,1,0);
  20. glEnd();

假设在上面程序段中最后一个glGetFloatv(GL_MODELVIEW_MATRIX, m)处设定断点的话。就能够看到图7所显示的数据。

图7 ModelView变换矩阵数据

到这里,整个ModelView变换就完毕了。

通过ModelView变换后得到是相机坐标系内的坐标。

在这个坐标系内典型的计算就是法线了。

如今再来看看后面一个阶段。

//////////////////////////////////////////////////////////////

我的理解:ModelView 变换矩阵,就是完毕从模型坐标到View坐标的转换,是坐标系之间的大变换。注意,modelview 既有model,也有view。不仅仅是一个model的矩阵。

仅仅是对model的进行平移或旋转的函数为  glTranslatef等函数,称作模型变换!它的坐标是基于模型本身的,即位于模型坐标系类,比方glTranslatef(0, 0, -3);的3个坐标值。

仅仅是针对view进行设置的函数为  gluLookAt,它的坐标系是view坐标系,比方

  1. gluLookAt(0.0, 0.0, 5.0,
  2. 0.0, 0.0, 0.0,
  3. 0.0, 1.0, 0.0);

它里面的坐标的原点位于相机坐标系的原点。參看以下的投影变换。

/////////////////////////////////////////////////////////////////

1.2投影变换

先还是复习一下OpenGL的渲染管线。

图1中能够看到,在投影变换(Projection Transformation)中也分为两个部分,第一个部分是将上个阶段得到的坐标转换为平面坐标,第二个部分是将转换后的平面坐标在进行归一化并进行剪裁。一般地。将三维坐标转换为平面坐标有两种投影方式:正交投影(Orthogonal Projection)和透视投影(Perspective Projection)

1.2.1 正交投影

正交投影非常easy,如图8,对于三维空间中的坐标点和一个二维平面,要在相应的平面上投影。仅仅需将非该平面上的点的坐标分量改为该平面上的坐标值。其余坐标不变。

图8 正交投影

比方将点(1,1,5)正交投影到z=0的平面上。那么投影后的坐标为(1,1,0)。在openGL中,设置正交投影能够使用函数:


[cpp] view plaincopy
  1. glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)

该函数能够设置正交投影的投影空间,在该空间以外的坐标点就不会被投影到投影平面上。函数中的六个參数分是投影空间六个平面,如图9:

图9 OpenGL正交投影空间和投影变换

在图9中。大的投影空间是依据这六个參数设置的投影空间,OpenGL会自己主动将该空间归一化,也就是将该空间或立方体转化为变长为1的正六面体投影空间,而且该证六面体的中心在相机坐标系的原点。一旦设置使用glortho函数设置投影空间。OpenGL会生成投影矩阵。这个矩阵的作用就是将坐标进行正交投影而且将投影后的坐标正规化(转换到-1到1之间)。要注意的是,生成该矩阵的时候,OpenGL会把右手坐标系转换为左手坐标系。原因非常easy,右手坐标系的Z轴向平面外的,这样不符合我们的习惯。该矩阵的矩阵推导这里就不具体说明了,不了解的同学能够參考游戏数学方面资料。这里仅仅给出正交投影矩阵。

这个矩阵看来非常复杂,事实上计算非常easy。举个样例,如今设置了这种正交投影空间glOrtho(-10,10,-10,10,-10,10)。这是个正六面体空间,变长为10。把这些參数带入上面的矩阵能够得到

如今还是在OpenGL程序中来检查一下。在OpenGL程序中加入以下代码段:


[cpp] view plaincopy
  1. //投影设置
  2. glMatrixMode(GL_PROJECTION);
  3. glLoadIdentity();
  4. glOrtho(-10,10,-10,10,-10,10);
  5. glMatrixMode(GL_MODELVIEW);
  6. glGetFloatv(GL_PROJECTION_MATRIX,m)
glGetFloatv(GL_PROJECTION_MATRIX,m)处设定断点就能够看到图10中所显示的信息。

图10 正交变换矩阵数据 

1.2.2透视投影

透视投影和正交投影最大的差别就是透视投影具有远近感。

图11 透视投影

透视投影採用了图11中的模型。这种模型就是保证远的物体看起来小。近的物体看起来大。 在OpenGL中设置透视投影能够使用函数:


[cpp] view plaincopy
  1. void APIENTRY gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

该函数也会依据给定的參数生成一个投影空间。如图11中。该投影空间是一个截头体。相同地,OpenGL会自己主动生成透视投影矩阵,该矩阵也会让3D坐标投影在投影平面上,而且将投影后的坐标也进行正规化。以下也直接给出OpenGL中使用的透视投影矩阵。

以下在OpenGL中加入以下代码段:


[cpp] view plaincopy
  1. //投影设置
  2. glMatrixMode(GL_PROJECTION);
  3. glLoadIdentity();
  4. gluPerspective(45, 1.0, 1.0, 100);
  5. glMatrixMode(GL_MODELVIEW);
  6. glGetFloatv(GL_PROJECTION_MATRIX,m)

设置断点后。我们能够看到图12中显示的数据。

图12 透视变换矩阵数据

到此为止,整个投影变换就完毕了。

透过投影变换后得到的是正规化的投影平面坐标。这为下一个阶段的视口变换(View port Transformation)做好了准备。

1.3视口变换

如今到了最后一个阶段了。

这个阶段叫做视口变换,它把上个阶段得到的正规化的投影坐标转化为windows 窗体坐标。

视口变换会将投影平面上的画面映射到窗体上。在OpenGL中能够使用函数


[cpp] view plaincopy
  1. GLAPI void GLAPIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);

来进行对窗体的映射,如图13。


图13 视口变换glViewport(width/2, 0, width/2, height/2)

举个样例说明。比方上个阶段中得到了一个顶点的坐标为(0,0,0.5,1)。依据这个坐标,该顶点位于投影平面的正中间。假设将该点映射到大小为50*50的窗体上时,那么它应该位于屏幕的中间,坐标为(25,25, 0.5,1)。当然这里深度值0.5是不会改变的。有的同学肯定有疑问了,既然投影到了窗体上,那么还要深度值0.5干什么?这里要注意的是。尽管在窗体上显示时仅仅须要x,y坐标就够了。可是要在2D窗体上显示3D图形时深度值是不可少的。

这里的深度值不是用于显示。而是用于在光栅化的时候进行深度測试。

OpenGL也会依据glViewport函数提供的參数值生成一个视口变换矩阵

该矩阵把上个阶段得到的正规化坐标映射到窗体上,而且将正规化坐标中的深度值在转换到0到1之间。所以在深度缓冲中最大值为1,最小值为0。视口变换结束后,OpenGL中基本的图形管线阶段就算完毕了,后面就是光栅化等等操作。再来回想一下图1,如今相信大家对这个渲染管线有了一定的认识了,也明确了每个阶段相应的变换矩阵以及怎样进行坐标之间的转换的。

2. 屏幕坐标转换为世界坐标

通过前面的教程,以及如今大家对OpenGL整个渲染管线理解后,如今要将屏幕上一点坐标转换为世界坐标就比較easy了。从图形管线的開始到结束,一个模型坐标系中的坐标被转化为了屏幕坐标。那么如今把整个过程倒过来的话,屏幕上一点坐标也能够转为为世界坐标。仅仅要在相应的阶段求得相应变换矩阵的逆矩阵,就能够得到前一个阶段的坐标。这整个过程能够用图14表示。

图14屏幕坐标转换为世界坐标

图中显示的过程全然就是OpenGL渲染管线的逆过程,通过这个过程。屏幕上的点就能够转化为世界坐标系中的点了。可能又有的同学要问,当鼠标点击屏幕上一点的时候并没有深度信息,转换的时候要怎么办呢?这个时候能够使用OpenGL函数


[cpp] view plaincopy
  1. void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);

该函数能够获得屏幕上一点相应像素的深度信息。

有了这个深度信息,就能够利用上面过程把屏幕上一点转换为世界坐标了。

在OpenGL中。上面的过程事实上已经有现成的函数能够使用,那就是


[cpp] view plaincopy
  1. int APIENTRY gluUnProject (
  2. GLdouble  winx, GLdouble  winy,
  3. GLdouble  winz,
  4. const GLdouble modelMatrix[16],
  5. const GLdouble projMatrix[16],
  6. const GLint    viewport[4],
  7. GLdouble  *objx,  GLdouble  *objy,
  8. GLdouble       *objz);

该函数直接将屏幕上一点转换相应的世界坐标,该函数的内部实现事实上还是上面的那么逆过程。

以下给出利用该函数获取世界坐标的代码段。


[cpp] view plaincopy
  1. GVector screen2world(int x, int y)
  2. {
  3. GLint viewport[4];
  4. GLdouble modelview[16];
  5. GLdouble projection[16];
  6. GLfloat winX, winY, winZ;
  7. GLdouble posX, posY, posZ;
  8. glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
  9. glGetDoublev(GL_PROJECTION_MATRIX, projection);
  10. glGetIntegerv(GL_VIEWPORT, viewport);
  11. winX = (float)x;
  12. winY = (float)viewport[3] - (float)y;
  13. glReadPixels(x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
  14. gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
  15. GVector v(4, posX, posY, posZ, 1.0);
  16. return v;
  17. }
代码中函数返回类型GVector是用户定义的向量类,返回的是齐次坐标。
时间: 2024-10-27 08:30:04

OpenGl 坐标转换的相关文章

OpenGl 坐标转换 (转载)

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

OpenGl从零开始之坐标变换

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

OpenGL学习脚印: 坐标变换过程(vertex transformation)

写在前面 前面几节分别介绍了模型变换,视变换,以及给出了投影矩阵和视口变换矩阵的推导,本节从全局把握一遍OpenGL坐标转换的过程,从整体上认识坐标变换过程.相关矩阵的数学推导过程请参考前面几节对应的内容. 通过本节可以了解到 坐标变换的各个阶段 利用GLM数学库实现坐标变换 坐标变换的全局图 OpenGL中的坐标处理过程包括模型变换.视变换.投影变换.视口变换等过程,如下图所示: 在上面的图中,注意,OpenGL只定义了裁剪坐标系.规范化设备坐标系和屏幕坐标系,而局部坐标系(模型坐标系).世界

Python 爬取CSDN博客频道

初次接触python,写的很简单,开发工具PyCharm,python 3.4很方便 python 部分模块安装时需要其他的附属模块之类的,可以先 pip install wheel 然后可以直接下载whl文件进行安装 pip install lxml-3.5.0-cp34-none-win32.whl 定义一个类,准备保存的类型 class CnblogArticle: def __init__(self): self.num='' self.category='' self.title=''

[cocos2dx]cocos2dx主要概念

Cocos2dx 中的主要概念包括:应用.导演.场景.图层.精灵.动画.动作.层次关系如下: CCDirector(导演) 在cocos2d-x引擎中,CCDirector类是整个游戏的组织和控制核心,游戏的运行规则,游戏内的CCScene(场景).布景(CCLayer).角色(CCSprite)等的运动,均由CCDirector管理,其在游戏中起着指定游戏规则让游戏内的场景.布景和任务有序的运行.在整个游戏里面,一般只有一个导演,游戏开始和结束时,都需要调用CCDirector的方法完成游戏初

Java Chaos Game 噪声游戏两则

Java Chaos Game噪声游戏两则 [简介] 最近一直在读<深奥的简洁>,里面有一章介绍了几种使用噪声产生分形图的方法,感觉很有意思,于是尝试使用计算机模拟了一下,效果还不错(噪声法比传统迭代法在编程上好实现一些,后来发现这类算法还不少,搜索chaos game可以找到更多). 本篇程序源文件及其依赖jar包已经打包,可以到这里GitHub下载. [Sierpinski三角形的噪声产生法] 在这些噪声游戏中,Sierpinski(谢尔宾斯基)三角形的生成规则可谓是最简单的: 1.在平面

[OpenGL]OpenGL坐标系及坐标转换

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

OpenGL中的坐标转换

这篇文章写的很清楚,直接转载:http://blog.csdn.net/zhongjling/article/details/8488844 OpenGL中的坐标转换,布布扣,bubuko.com

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

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