OpenGL学习脚印: OpenGL 坐标变换
写在前面
本节内容翻译和整理自http://www. songho的博客《OpenGL Transformation》内容,以供自己和初学者熟悉OpenGL中坐标变换的整个过程。
通过本节,你可以了解到:
- OpenGL坐标变换过程
- 理解OpenGL矩阵计算
概览
几何数据例如顶点位置和法向量在光栅化操作之前,都要通过Vertex Operation 和 Primitive Assembly OpenGL流水线操作(在OpenGL pipeline节描述)。
OpenGL 顶点变换
Object Coordinates(对象坐标系或模型坐标系)
这是对象的局部坐标系统,是对象在被应用任何变换之前的初始位置和方向所在的坐标系。要对对象实行变换,可以使用glRotatef(), glTranslatef(), glScalef()等函数。
Eye Coordinates(眼坐标系或照相机坐标系)
由GL_MODELVIEW矩阵和模型坐标系中坐标相乘的结果。在OpenGL中使用GL_MODELVIEW矩阵来使对象从模型坐标系转换到眼坐标系。
GL_MODELVIEW矩阵是模型变换和视变换矩阵的组合(Mview*Mmodel)。模型变换从对象坐标系转换到世界坐标系,而视变换从世界坐标系转换到眼坐标系。
注意:OpenGL中并没有单独的视变换矩阵。因此,要想模拟变换照相机或者进行视变换,那么场景(3D对象和光照)必须以视变换矩阵的逆矩阵进行变换。换言之,OpenGL将照相机定义在位于眼坐标系下朝向-Z轴,位于点(0,0,0)的位置,而不能进行变换。
法向量同样从对象坐标系变换到眼坐标系来用于光照计算。
注意:法向量的转换方式和顶点不同。它用法向量乘以GL_MODELVIEW矩阵的逆矩阵的转置矩阵。请参考Normal
Vector Transformation获取更多细节。
Clip Coordinates(裁剪坐标系)
眼坐标通过乘以GL_PROJECTION变成了裁剪坐标。这个GL_PROJECTION矩阵定义了视见体( viewing volume,frustum),顶点式如何投影到屏幕上的(透视投影还是正交投影perspective or orthogonal)。称作裁剪坐标系的是因为,经过变换后的顶点(x,y,z)将与 ±w相比较来进行裁剪。请参考下面Projection
Matrix部分获取更多细节。
Normalized Device Coordinates (NDC) (归一化设备坐标系)
由裁剪坐标系下通过除以W分量得到。这个操作称为透视除法。NDC坐标很像屏幕坐标,但是还没有经过平移和缩放到屏幕像素。现在3个轴上的值范围均为[-1,1]。
Window Coordinates (Screen Coordinates)(屏幕坐标)
通过对NDC坐标进行视口变换得到。NDC坐标通过平移和缩放来适应渲染的屏幕。屏幕坐标最终传递给绘制流水线中的光栅化处理部分来变成片元。glViewport() 用来定义渲染区域的矩形,这是最终图像映射到的区域。另外,glDepthRange()用来确定屏幕坐标中的Z值。屏幕坐标通过上面两个函数的给定参数来进行计算:
glViewport(x, y, w, h);
glDepthRange(n, f);
这个公式由NDC坐标和屏幕坐标之间的线性关系来获得的:
OpenGL变换矩阵
OpenGL使用4x4矩阵来进行变换。注意,这16个元素的矩阵,实际上按列主序的方式以1D形式存储。如下图所示:
如果想当做通常的行主序格式使用,你需要将其转置。
OpenGL当中,有四类型的矩阵: GL_MODELVIEW,GL_PROJECTION,
GL_TEXTURE, 和 GL_COLOR
可以通过glMatrixMode() 函数来指定当前矩阵类型,例如使用模视矩阵,则可以选择GL_MODELVIEW,调用 glMatrixMode(GL_MODELVIEW)。
Model-View Matrix (GL_MODELVIEW) 模视矩阵
GL_MODELVIEW矩阵将模型变换矩阵和视变换矩阵组合成一个矩阵。为了转换相机,你需要对整个场景执行相反的变换。gluLookAt()函数专门用来设定视变换。矩阵最后一列元素(m12,m13,
m14) 用于执行平移变换,glTranslatef()。
元素m15是齐次坐标系下坐标,特别用于投影变换。
(m0, m1,
m2), (m4, m5, m6) and (m8,m9,
m10)这3个元素集,用于欧几里得和仿射变换,例如glRotatef()用于旋转,glScalef()用于缩放。注意: 这三个元素集实际上代表3个正交坐标轴:
- (m0, m1,m2) : +X axis,
left vector, (1, 0, 0) by default
- (m4, m5,m6) : +Y axis,
up vector, (0, 1, 0) by default
- (m8, m9,m10) : +Z axis,
forward vector, (0, 0, 1) by default
如下图所示:
我们可以从角度和lookat向量来直接构造GL_MODELVIEW,而不是用OpenGL来操作4列的GL_MODELVIEW矩阵。这里有一些有用的代码来构造GL_MODELVIEW矩阵:
注意:如果多个变换应用到一个顶点时,OpenGL以逆序的方式执行多个相乘操作。举例来说,如果顶点先由MA 变换,再由MB变换,那么OpenGL先执行MB
x MA 操作,再乘以顶点。因此,在代码中,后执行的变换先出现,而先执行的变换后出现:
- // Note that the object will be translated first then rotated
- glRotatef(angle, 1, 0, 0); // rotate object angle degree around X-axis
- glTranslatef(x, y, z); // move object to (x, y, z)
- drawObject();
- <span style="font-size:14px;"><code class="codeblock" style="margin-left:30px;"></code></span>
Projection Matrix (GL_PROJECTION) 投影矩阵
GL_PROJECTION用于定义视锥。视锥决定了哪些对象以及对象的哪些部分会被裁减掉。 同时,它也决定了3D场景是如何被投影到屏幕上的。
OpenGL使用两个函数来进行投影变换。glFrustum()用于进行透视投影,glOrtho() 用于进行正交(平行)投影。这两个函数都需要6各参数来指定裁剪平面的:left,right,
bottom, top, near and far planes。两个函数原型如下:
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal);
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal);
8个顶点如下图所示:
远裁剪面(far)的顶点可以通过相似三角形的比例计算出来(补充: 实际上通过函数gluFrustum指定时,参数都是指的近裁面的l,r,t,b,远裁剪面可以计算出来),例如,远裁剪面的left参数可以如下计算:
对于正交投影,这个比例是1,因此远裁剪面的 left,
right, bottom和top 和近裁剪面的一样,如下图所示:
gluPerspective() and gluOrtho2D()函数使用时需要更少的参数。gluPerspective()只需要四个参数,FOV视角,宽高比,以及远近裁剪面的距离。函数原型为:
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
从gluPerspective()转换为同等的glFrustum()的代码如下:
- // This creates a symmetric frustum.
- // It converts to 6 params (l, r, b, t, n, f) for glFrustum()
- // from given 4 params (fovy, aspect, near, far)
- void makeFrustum(double fovY, double aspectRatio, double front, double back)
- {
- const double DEG2RAD = 3.14159265 / 180;
-
- double tangent = tan(fovY/2 * DEG2RAD); // tangent of half fovY
- double height = front * tangent; // half height of near plane
- double width = height * aspectRatio; // half width of near plane
-
- // params: left, right, bottom, top, near, far
- glFrustum(-width, width, -height, height, front, back);
- }
补充: 关于gluPerspective函数计算视锥
利用gluPerspective函数指定视锥如下图所示:
注意到视锥的对称性,利用三角形的相似性,可以推算如下图所示:
其中 top : n = tan(fov/2) ,
bottom= -top, left = -right
===> top= height/2 = = n*tan(fov/2) height为近裁剪面高度
===> right=width/2 = aspect*height/2=aspect*n*tan(fov/2) width为近裁剪面宽度
注意上述代码中width,height均为实际值的一半。
注意:这里使用gluPerspective函数构造的是一个对称的视锥,如果你想要构造非对称视锥必须直接使用
glFrustum()函数。例如,你想要把一个宽场景绘制到两个相连的屏幕上,你可以将视锥分割为左右两个非对称的视锥,然后在每个视锥中渲染场景,如下图所示:
Texture Matrix (GL_TEXTURE)纹理矩阵
纹理坐标(s,t,r,q)在进行任何纹理映射前乘以GL_TEXTURE。默认情况下,它是一个单位阵,因此纹理会被映射到物体的位置,那个你指定纹理坐标的位置。通过修改GL_TEXTURE,你可以滑动、旋转、拉伸以及收缩纹理。
- // rotate texture around X-axis
- glMatrixMode(GL_TEXTURE);
- glRotatef(angle, 1, 0, 0)
Color Matrix (GL_COLOR) 颜色矩阵
颜色分量(r,g,b,a)乘以GL_COLOR矩阵。可以用于颜色空间转换和颜色分量交换。GL_COLOR矩阵被经常使用,并且需要GL_ARB_imaging拓展。
其他的矩阵操作
glPushMatrix() :
push the current matrix into the current matrix stack.
glPopMatrix() :
pop the current matrix from the current matrix stack.
glLoadIdentity() :
set the current matrix to the identity matrix.
glLoadMatrix{fd}(m) :
replace the current matrix with the matrixm.
glLoadTransposeMatrix{fd}(m) :
replace the current matrix with the row-major ordered matrixm.
glMultMatrix{fd}(m) :
multiply the current matrix by the matrixm, and update the result to the current matrix.
glMultTransposeMatrix{fd}(m) :
multiply the current matrix by the row-major ordered matrixm, and update the result to the current matrix.
glGetFloatv(GL_MODELVIEW_MATRIX,m) :
return 16 values of GL_MODELVIEW matrix tom.
正文到此结束,关于文中提到的两个经典帮助理解的例子,可以通过原文下载,这里不再介绍。
|