OpenGL系列教程之四:OpenGL 变换

相关主题:OpenGL 渲染管线,OpenGL 投影矩阵,OpenGL矩阵类

下载:matrixModelView.zip,matrixProjection.zip

  • 概述
  • OpenGL 变换矩阵
  • 例子:GL_MODELVIEW 矩阵
  • 例子:GL_PROJECTTION 矩阵


概述
在OpenGL渲染管线中几何数据(顶点位置和法向量)在光栅化处理之前会先经过顶点操作和图元装配。

OpenGL 顶点变换


物体坐标系(Object Coordinates)
这是物体的局部坐标系,并且表示物体未经过任何变换前的初始位置和朝向。为了变换物体,使用glRotatef(),glTranslatef(),glScalef()等函数。


人眼坐标系(Eye Coordinates)
这是将GL_MODELVIEW(模型视图矩阵)与物体坐标相乘产生的。在OpenGL中物体从物体自身的坐标变换到人眼坐标使用GL_MODELVIEW矩阵。GL_MODELVIEW矩阵是将模型矩阵和视图矩阵相乘的结果()。模型变换是将物体坐标变换到世界坐标,视图变换是将世界坐标变换到人眼坐标。


注意在OpenGL中没有单独的照相机(视图)矩阵。因此,为了模仿视图变换,场景(三维物体和光照)必须使用视图变换的反转进行变换。换句话说,OpenGL定义的照相机在人眼坐标系中一直在(0,0,0)位置并且指向Z轴的负半轴,并且不能进行变换。模型视图矩阵的更多详细细节。
法向量为了后续的光照计算也会从物体坐标系变换到人眼坐标系。注意法向量和顶点变换的不同方式。它是将GL_MODELVIEW的逆矩阵的转置矩阵乘以法向量。法向量变换的更多细节。



裁剪坐标(Clip Coordinates)
将GL_PROJECTTION与人眼坐标相乘就得到了裁剪坐标。GL_PROJECTION(投影矩阵)定义了视景体;几何数据怎么被投影到屏幕中(透视投影或正投影)。它被称为裁剪坐标是因为变换后的顶点坐标(x,y,z)经过了裁剪(通过与-w和+w的比较)。投影矩阵的更多细节。



归一化的设备坐标(Normalized Device Coordinates(NDC))
将裁剪坐标与w相除就得到归一化的设备坐标。它被称为透视除法。它更像窗口坐标,但是还没有被平移和缩放到匹配窗口。现在3个坐标轴上变量的取值被归一化到-1到1之间。



窗口坐标(Window Coordinates(Screen Coordinates))
这是将归一化的设备坐标应用到视口变换而产生的。归一化的设备坐标经过缩放了平移以匹配窗口。窗口坐标最后被传递给OpenGL渲染管线中光栅化程序生成片元。glViewport()命令被用来定义最后渲染出来的图像将要映射到的长方形区域。glDepthRange()命令被用来定义窗口坐标的z值的范围。窗口坐标是经过下面这两个函数变换得到的
glViewport(x,y,w,h);
glDepthRange(n,f);
OpenGL系列教程之四:OpenGL 变换_第1张图片
视口变换公式是一个从NDC到窗口坐标的简单的线性变换。



OpenGL变换矩阵
OpenGL系列教程之四:OpenGL 变换_第2张图片
OpenGL 变换矩阵
OpenGL使用一个4*4的矩阵来表示变换。注意矩阵中的16个元素是按列的顺序存储在一个一维数组中。如果你需要将他转换成标准型,即按行存储,则需要将这个矩阵转置。
OpenGL有4中不同类型的矩阵:GL_MODELVIEW(模型视图矩阵),GL_PROJCETION(投影矩阵),GL_TEXTURE(纹理矩阵),GL_COLOR(颜色矩阵)。你可以通过使用glMatrixMode()函数改变当前的矩阵类型。例如,为了选中GL_MODELVIEW矩阵,使用glMatrixMode(GL_MODELVIEW).


模型-视图 矩阵(GL_MODELVIEW)
GL_MODEVIEW矩阵联合和视图矩阵和模型矩阵到一个矩阵中。为了变换视图(相机),你需要将整个场景按照视图矩阵的逆进行移动。gluLookAt()函数是用来设置视图变换的。
OpenGL系列教程之四:OpenGL 变换_第3张图片
GL_MODELVIEW矩阵的4列
上图中(m12,m13,m14)表示glTranslatef()函数进行的平移变换。m15是齐次坐标,主要用来进行投影变换。
3个元素集,(m0,m1,m2), (m4,m5,m6)和(m8,m9,m10)是用来表示欧氏变化,如旋转(glRoattef()),和缩放(glScalef())。注意这3个集合实际上表示的是3个正交的轴。
  • (m0,m1,m2):+X轴,指向右边的向量,默认为(1,0,0)
  • (m4,m5,m6)+Y轴,指向上边的向量,默认为(0,1,0)
  • (m8,m9,m10)+Z轴,指向前边的向量,默认为(0,0,1)
我们可以使用角度或观察点直接构造GL_MODELVIEW矩阵而不使用OpenGL的变换函数。下面是一些构造GL_MODELVIEW矩阵的有用的代码:
  • 角度到坐标轴
  • 观察点到坐标轴
  • 矩阵类
注意如果多个变换应用到顶点中时OpenGL是按照相反的顺序执行的。例如,如果一个顶点首先经过矩阵MA变换,然后经过矩阵MB变换,然后OpenGL在将矩阵与顶点相乘之前会首先执行MBxMA操作。因此,最后的变换会首先被应用,最早的变换会最后被应用。

// 注意对象首先被平移,然后被旋转
glRotatef(angle, 1, 0, 0);   // 将对象绕x轴旋转angle角度
glTranslatef(x, y, z);       // 移动对象到(x,y,z)处
drawObject();


投影矩阵(GL_PROJECTION)
GL_PROJECTION矩阵被用来定义截头锥。这个截头锥决定了哪些物体或物体的哪些部分会被裁剪掉。它也定义了三维场景将会如何被映射到屏幕上。(更多细节参考如果构造投影矩阵)
OpenGL为GL_PROJCETION变换提供了两个函数。glFrustum()用来产生一个透视投影,glOrtho()用来产生一个正投影。两个函数都需要6个参数来指定6个裁剪面:左面,右面,上面,近面,远面。截头锥的8个顶点如下图:
OpenGL系列教程之四:OpenGL 变换_第4张图片
OpenGL透视投影体
近(远)面的坐标计算可以通过相似三角形计算得到,如左面的计算方法:

对正投影而言,相似三角形的比例是1,因此远平面的左,右,上,下点的坐标和近平面是一样的。如下图:
OpenGL系列教程之四:OpenGL 变换_第5张图片
OpenGL正投影体
也可以传递更少的参数给gluPerspective()函数和gluOrtho2D()函数。gluPerspective()函数只需要4个参数;视线的垂直夹角,面的宽高比,视点距近裁剪面和远裁剪面的距离。4个参数与6个参数之间的相互转换的代码如下:
// 这创建了一个对称的截头锥
// 它将给定的4个参数(fovy, aspect, near, far)转换
// 成glFrustum()所需的6个参数:(l, r, b, t, n, f) 
void makeFrustum(double fovY, double aspectRatio, double front, double back)
{
    const double DEG2RAD = 3.14159265 / 180;

    double tangent = tan(fovY/2 * DEG2RAD);   // fovY的一半的正切,fovY是视线的夹角
    double height = front * tangent;          // 近裁剪面的高度的一半
    double width = height * aspectRatio;      // 近裁剪面的宽度的一半

    // params: left, right, bottom, top, near, far
    glFrustum(-width, width, -height, height, front, back);
}
然而,如果你需要创建一个不对称的视景体那么必须直接使用glFrustum()函数。例如,如果你需要渲染一个宽屏场景到两个邻接的屏幕中,你可以将视景体划分成两个不对称的视景体(左边和右边),然后将场景分别渲染到两个视景体中,如下图:
非对称视景体的例子


纹理矩阵(GL_TEXTURE)
纹理坐标(s,t,r,q)在纹理映射之前会和GL_TEXTURE(纹理矩阵)相乘。默认情况下它是归一化的,所以纹理将会准确映射物体上你指定的纹理坐标处。通过修改GL_TEXTURE,你可以移动,旋转,拉伸,收缩纹理。
// 将纹理绕x轴旋转
glMatrixMode(GL_TEXTURE);
glRotatef(angle, 1, 0, 0);

颜色矩阵(GL_COLOR)
颜色值(r,g,b,a)会和矩阵GL_COLOR相乘。它可以被用来交换颜色空间和颜色值。GL_COLOR矩阵不会被普遍使用因为它需要GL_ARB_imaging扩展支持。



其他一些矩阵操作
glPushMatrix():将当前矩阵压入当然矩阵栈中
glPopMatrix():从当前矩阵栈中弹出一个元素
glLoadIdentify():将当前矩阵设置为单位矩阵(归一化)
glLoadMatrix{fd}(m):将当前矩阵替换成矩阵m
glLoadTransposeMatrix{fd}(m):将当前矩阵替换成以行为主的矩阵m
glMultMatrix{fd}(m):m乘以当前矩阵,并将结果设置为当前矩阵
glMultTransposeMatrix{fd}(m):将以行为主的矩阵乘以当前矩阵,并将结果设置为当前矩阵
glGetFloatv(GL_MODELVIEW_MATRIX,m):返回模型视图矩阵到矩阵m



例子:模型视图矩阵

这个例子程序显示了怎么使用glTranslatef()函数和glRotatef()函数来操纵模型视图矩阵。
下载源代码和可执行程序:matrixModelView.zip matrixModelView_mac.zip(OS X 10.6+)

注意无论在Mac下还是在Windows下所有的OpenGL函数调用都是在ModelGL.h和ModelGL.cpp中实现的,在两个包中所有文件都是相同的。

这个例子程序使用一个自定义的4*4矩阵类和OpenGL默认的矩阵模式来指定模型变换和视图变换。ModelGL.cpp中定义了3个矩阵对象:matrxModel,matrixView和matrixModelView。每个矩阵存储了前乘的变换并使用glLoadMatrix()函数将矩阵中的元素传递到OpenGL中。实际的绘图模式如下:

...
glPushMatrix();

// 为视图变换设置视图矩阵
glLoadMatrixf(matrixView.getTranspose());

// 在模型变换之前绘制最原始的网格
drawGrid();

// 为模型变换和视图变换设置模型视图矩阵
// 从物体坐标系变换到人眼坐标系
glLoadMatrixf(matrixModelView.getTranspose());

// 模型视图变换之后绘制一个茶壶
drawTeapot();

glPopMatrix();
...


相同的功能使用OpenGL默认的矩阵操作函数如下:

...
glPushMatrix();

// 将模型视图矩阵归一化
glLoadIdentity();

// 首先,将视图从物体坐标系变换到人眼坐标系
// 注意所有的值都是负数,因为我们是使用视图矩阵的逆来移动整个场景
glRotatef(-cameraAngle[2], 0, 0, 1); // roll
glRotatef(-cameraAngle[1], 0, 1, 0); // heading
glRotatef(-cameraAngle[0], 1, 0, 0); // pitch
glTranslatef(-cameraPosition[0], -cameraPosition[1], -cameraPosition[2]);

// 在模型变换之前绘制最原始的网格
drawGrid();

// 模型变换
// GL_MODELVIEW矩阵的结果如下:
// ModelView_M = View_M * Model_M
glTranslatef(modelPosition[0], modelPosition[1], modelPosition[2]);
glRotatef(modelAngle[0], 1, 0, 0);
glRotatef(modelAngle[1], 0, 1, 0);
glRotatef(modelAngle[2], 0, 0, 1);

// 模型视图变换之后绘制一个茶壶
drawTeapot();

glPopMatrix();
...



例子:投影变换


OpenGL系列教程之四:OpenGL 变换_第6张图片

这个例子程序显示了如何使用glFrustum()函数和glOrtho()函数来操作投影变换。

下载源代码和可执行程序:matrixProjection.zipmatrixProjection_mac.zip(OS X 10.6+)

再次说明,ModelGL.h和ModelGL.cpp在Window和Mac下是一样的,并且所有的OpenGL函数调用都在这两个文件中。


ModelGL类有一个自定义的矩阵对象,matrixProjection,和两个成员函数,setFrustum()和setOrthoFrustum(),这两个函数等价于glFrustum()和glOrtho()。

///////////////////////////////////////////////////////////////////////////////
// 使用6个参数像glFrustum()函数一样设置一个透视的视景体
// (left, right, bottom, top, near, far)
// 注意:这是以行为主的标记方式,OpenGL需要先将其转置
///////////////////////////////////////////////////////////////////////////////
void ModelGL::setFrustum(float l, float r, float b, float t, float n, float f)
{
    matrixProjection.identity();
    matrixProjection[0]  = 2 * n / (r - l);
    matrixProjection[2]  = (r + l) / (r - l);
    matrixProjection[5]  = 2 * n / (t - b);
    matrixProjection[6]  = (t + b) / (t - b);
    matrixProjection[10] = -(f + n) / (f - n);
    matrixProjection[11] = -(2 * f * n) / (f - n);
    matrixProjection[14] = -1;
    matrixProjection[15] = 0;
}

///////////////////////////////////////////////////////////////////////////////
// 使用6个参数像glOrtho()函数一样设置一个正投影的视景体
// (left, right, bottom, top, near, far)
// 注意:这是以行为主的标记方式,OpenGL需要先将其转置
///////////////////////////////////////////////////////////////////////////////
void ModelGL::setOrthoFrustum(float l, float r, float b, float t, float n,
                              float f)
{
    matrixProjection.identity();
    matrixProjection[0]  = 2 / (r - l);
    matrixProjection[3]  = -(r + l) / (r - l);
    matrixProjection[5]  = 2 / (t - b);
    matrixProjection[7]  = -(t + b) / (t - b);
    matrixProjection[10] = -2 / (f - n);
    matrixProjection[11] = -(f + n) / (f - n);
}
...

// 将投影矩阵传递给OpenGL
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(matrixProjection.getTranspose());
...
构造一个16个元素的GL_PROJECTION矩阵可以参考这儿。






你可能感兴趣的:(OpenGL系列教程之四:OpenGL 变换)