OpenGL坐标系与几何变换
扫描二维码
随时随地手机看文章
坐标系统
想要弄懂几何变换,一定要搞清楚OpenGL中的坐标系统。
从我们构造模型的局部坐标系(Local/Object Space)经过一系列处理最终渲染到屏幕坐标(Screen Space)下,这过程中有6种坐标系。
World Coordinates(世界坐标系)Object Coordinates(对象坐标系、模型坐标系、局部坐标系或当前绘图坐标系)Eye Coordinates(眼坐标系或照相机坐标系)Clip Coordinates(裁剪坐标系)Normalized Device Coordinates (NDC) (归一化设备坐标系)Window Coordinates (Screen Coordinates)(屏幕坐标)
实际上,并不存在单独的模型变换(Model)和视点变换(View),通常将这两种变换合称为ModelView变换。则OpenGL的顶点变换过程如图所示:
世界坐标系
世界坐标系始终是固定不变的。OpenGL使用右手坐标,这里有一个形象的方法:使用右手定则
X 是你的拇指
Y 是你的食指
Z 是你的中指
如果你把你的拇指指向右边,食指指向天空,那么中指将指向你的背后。我们的观察方向是Z轴负半轴的方向。
进行旋转操作时需要指定的角度θ的方向则由右手法则来决定,即右手握拳,大拇指直向某个坐标轴的正方向,那么其余四指指向的方向即为该坐标轴上的θ角的正方向(即θ角增加的方向),图中用圆弧形箭头标出:
对象坐标系
这是对象在被应用任何变换之前的初始位置和方向所在的坐标系,也就是当前绘图坐标系。该坐标系不是固定的,且仅对该对象适用。在默认情况下,该坐标系与世界坐标系重合。这里能用到的函数有glTranslatef(),glScalef(), glRotatef(),当用这些函数对当前绘图坐标系进行平移、伸缩、旋转变换之后, 世界坐标系和当前绘图坐标系不再重合。改变以后,再用glVertex3f()等绘图函数绘图时,都是在当前绘图坐标系进行绘图,所有的函数参数也都是相对当前绘图坐标系来讲的。如图则是对物体进行变换后,对象坐标系与世界坐标系的相对位置。对象坐标系也叫本地(局部)坐标系。
眼坐标系
模型变换:对象坐标系->世界坐标系
视变换:世界坐标系->眼坐标系
GL_MODELVIEW矩阵是模型变换矩阵和视点变换矩阵的组合(Mview*Mmodel),前面已经说了,并不存在单独的模型变换(Model)和视点变换(View)。所以使用GL_MODELVIEW矩阵就可以使对象从对象坐标系转换到眼坐标系。
为啥要转换到眼坐标系呢?
可以这样理解,通过前面的MODEVIEW变换,这个世界坐标系中的场景已经绘制好了。这时候我们还不能看到场景哦,因为我们的观察位置还没定呢,而且如果我们眼睛(照相机)的位置不同,那么观察物体的角度则不同,那看到的场景的样子肯定也不同,所以要有这一步,把场景与我们的观察位置对应起来。
默认情况下,眼坐标系与世界坐标系也是重合的。使用函数 gluLookAt()则可以指定眼睛(相机)的位置和眼睛看向的方向。该函数的原型如下:
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz)
函数参数中:点(eyex, eyey, eyez)代表眼睛所在位置;
点(centerx, centery,centerz)代表眼睛看向的位置;
向量(upx, upy, upz)代表视线向上方向,其中视点和参考点的连线与视线向上方向要保持垂直关系。
只需控制这三个量,便可定义新的视点。
tips
使用glTranslatef(),glScalef(), glRotatef()这些函数是对对象坐标系进行变动;使用void gluLookAt()是对眼坐标系进行变动,两者可以达到相同的变换效果。相当于对象不动移动相机,和相机不动移动对象。比如场景向x轴正方向移动1个单位(相机不动),相当于相机向x轴负方向移动一个单位(对象不动),glTranslatef(1.0, 0.0, 0.0)
裁剪坐标系
眼坐标到裁剪坐标是通过投影完成的。眼坐标通过乘以GL_PROJECTION矩阵变成了裁剪坐标。
这个GL_PROJECTION矩阵定义了视景体( viewing volume),即确定哪些物体位于视野之内,位于视景体外的对象会被剪裁掉。除了视景体,投影变换还定义了顶点是如何投影到屏幕上的,是透视投影(perspective projection)还是正交投影(orthographic projection)。透视投影似于日常生活看到的场景,远处物体看起来小,近处看起来大。使用透视投影函数glFrustum()和gluPerspective().
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
far, near是指近裁剪面,远剪裁面离视点的距离(>0);对角坐标,(left, bottom, -near)和(right, top, -near)定义了近裁剪面的左下角和右上角的(x, y, z)坐标。
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far)
fovy视角,aspect = w/h
正交投影把物体直接映射到屏幕上,不影响它们的相对大小。也就是图像反映物体的实际大小。函数glOrtho()创建一个用于正交投影的平行视景体, 将其与当前矩阵相乘。
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
关于透视投影和正交投影详见:OpenGL之glMatrixMode函数的用法
归一化设备坐标系
既然要规范化,那么就得先有一个规范。前面在投影部分也已经看到,每种投影,都有一个剪裁空间,称之为观察体,对正交投影来说是一个立方体,对透视投影来说是一个棱台。如果一个观察体是一个x、y、z坐标范围都是 [-1, 1] 的立方体,则称之为规范化立方体,这个就是所谓的规范。那么,将原来的观察体,映射到规范化立方体的过程,就是规范化。
一个格外需要注意的地方是,由于后面的屏幕坐标系通常是左手坐标系,所以这里的规范化观察体也使用左手坐标系,意味着 x 轴和 y 轴没有改变,但是 z 轴的正方向转了个。这带来的结果是,在这样的坐标系下,z 的坐标值越小,距离观察者(也就是你)越近。实际上,在opengl中,进行规范化之后,近裁剪平面的z轴坐标是 -1,远裁剪平面的z轴坐标是1。
由裁剪坐标系下通过除以W分量得到。这个操作称为透视除法。NDC坐标很像屏幕坐标,但是还没有经过平移和缩放到屏幕像素。现在3个轴上的值范围均为[-1,1]。屏幕坐标系
通常将屏幕上的设备坐标称为屏幕坐标。设备坐标又称为物理坐标,是指输出设备上的坐标。设备坐标用对象距离窗口左上角的水平距离和垂直距离来指定对象的位置,是以像素为单位来表示的,设备坐标的 X 轴向右为正,Y 轴向下为正,坐标原点位于窗口的左上角。
从NDC坐标到屏幕坐标基本上是一个线性映射关系。通过对NDC坐标进行视口变换得到。这时候就要用到函数glViewport(),该函数用来定义渲染区域的矩形,也就是最终图像映射到的区域。