本系列文章由 莫问 出品,转载请注明出处。
文章链接:http://blog.csdn.net/mni2005/article/details/27228111
作者:莫问(mni2005) 邮箱:[email protected]
Windows GDI环境下,要绘制一个正方形太简单了;但是在3D坏境下,这一切都将变得复杂了,也没有想想的那么容易了。我经常遇到有的3D初学者提到像下面这样的问题:
遇到这样的问题,我真没办法回答,因为这些问题都不是一句话,两句话可以说明白的?好了,当大家仔细看完本片博文后,这两个问题都应该都能大概明白怎么回事了。
1. 两个关键坐标系
1. OpenGL的世界坐标系
世界坐标系也叫绝对坐标系,它是一种特殊的坐标系,它建立了描述其它坐标系所需要的参考框架。也就说,要描其它坐标系,必须依赖世界坐标系,而无法用其它坐标系来描述世界坐标系。
OpenGL规定坐标的中心点在屏幕中间,X轴朝向屏幕的右边,Y轴朝向屏幕的的上边,Z轴朝向屏幕的外面,如下图所示。
OpenGL的这种坐标系属于右手坐标系,既然有右手坐标系,有人必然会猜到肯定有左手坐标系,那什么是左手和右手坐标系了?伸出你的左手右手,让拇指指向X轴的正方向,食指指向Y轴的正方向,掌心向前的方向为Z轴的正方向,如果这个坐标系的方向符合右手,便是右手坐标系;如果符合你的左手,便是左手坐标系。
2. 相机坐标系
相机坐标系,也叫观察坐标系,它是指以相机所在的位置为原点,而建立的直角坐标系称为相机坐标系。至于XYZ轴的方向,根据情况而定,不同的规定产生不同的相机模型。在OpenGL中,所有几何对象和模型最终都会换算到相机坐标系下,然后进行投影变换,才能在二维屏幕显示上显示出它们。
2. OpenGL的两个关键矩阵
大多数OpenGL初学者,可能都知道OpenGL中有两个重要的矩阵,它们分别是模型视图矩阵,以及投影矩阵,它们的大小都是4x4。虽然它们很重要,但是我在这里不打算详细讲解,只是简单说明一下它们是干什么的,因为详细讲解的话,涉及的东西太多了。
1. 获取模型视图矩阵和投影矩阵
OpenGL提供了两个函数可以获取OpenGL内部的一些参数信息,这个两个函数的原型如下所示:
void glGetDoublev(
GLenum pname,
GLdouble *params
);
void glGetFloatv(
GLenum pname,
GLfloat * params
);
这个两个函数功能一样,只是参数的类型不一样,一个是double,一个是float。我们一般使用float类型的,你也可以根据需要选择double类型的,只是精度有区别而已。函数的参数说明如下:
float proj[16];
float modeview[16];
glGetFloatv(GL_MODELVIEW_MATRIX, modeview);
glGetFloatv(GL_PROJECTION_MATRIX, proj);
2. 设置当前矩阵
OpenGL提供了一个函数用于设置当前矩阵,它的函数原型很简单,如下:
void glMatrixMode(
GLenum mode
);
参数说明:mode 指定当前可操作的目标矩阵,可选值: GL_MODELVIEW、GL_PROJECTION、GL_TEXTURE。
该函数看起来简单,但是好多初学者没有真正理解该函数的意义。从函数说明中,我们看到该函数设置当前可操作的目标矩阵,由于OpenGL的状态机机制,你将一个矩阵指定为当前矩阵后,那么后续的所有矩阵相关操作,都是对于该矩阵的操作,除非你再次调用glMatrixMode,修改当前可操作的目标矩阵。到了这里好多人就会问,那OpenGL都有那些函数是可以操作矩阵的?这里我简单列几个常见的:
glLoadIdentity();
glFrustum();
glOrtho();
glTranslatef();
glScalef();
glRotatef();
glPushMatrix();
glPopMatrix();
glLoadMatrixf();
glMultMatrixf();
常见的修改当前矩阵的函数就大概上面几个,其它的以后遇到再说,这些函数大家也不必刻意去记,只需要有个印象就可以了,后面我会挨个详细讲解。
2. 绘制正方形
通过前面大片预备知识讲解,大家应该有了一个大概思路,在OpenGL中绘图,肯定要先设置两个关键矩阵,只有这样绘制的模型对象才能在屏幕中显示出来。我们在OpenGL中我绘制正方形时,直接将正方形的四个坐标点手工换算到视图坐标系下,这样就可以暂时不用考虑模型视图矩阵了。因此我们绘制正方形时,只需要设置好投影矩阵,然后将正方形的四个坐标点传递给OpenGL,调用绘制接口就大公告成了。
1. 设置投影矩阵
void glFrustum(
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble znear,
GLdouble zfar
);
各个参数指示的位置如下图所示:
前面我们提到视景体的概念,它有两个用途。首先视景体决定了视图坐标系中一个物体是如何投影的到屏幕上的。其次它决定了一个那些物体(或者物体的一部分)被裁剪到最终的图像之外,也就是说视景体之外的物体(或物体的一部分部分)在屏幕上看不到。最终完成的代码如下:
// 设置当前矩阵为投影矩阵
glMatrixMode(GL_PROJECTION);
// 将当前矩阵置为单位矩阵
glLoadIdentity();
// 通过设置视景体,而修改当前的投影矩阵。
glFrustum(-1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1000.0f);
上面三行代码中,我们遇到了一个新函数:glLoadIdentity(),该函数的功能就是将当前矩阵修改为单位矩阵,代码里的注释很清楚,函数也很简单,这里就不再赘述了。设置投影矩阵的函数实现了,那么这段代码该放在那里了?大家应该很清楚,投影矩阵与OpenGL的窗口尺寸息息相关,那么这段代码必须在窗口大小发生变化时调用。因此必须放置到GLWindow类的reshape函数中,完成后的函数如下:
void GLWindow::reshape (int w, int h)
{
//该段代码仅仅用于查看矩阵的修改效果。
//float proj[16];
//float modeview[16];
//glGetFloatv(GL_MODELVIEW_MATRIX, modeview);
//glGetFloatv(GL_PROJECTION_MATRIX, proj);
glViewport(0, 0, w, h);
// 设置当前矩阵为投影矩阵
glMatrixMode(GL_PROJECTION);
// 将当前矩阵置为单位矩阵
glLoadIdentity();
// 通过设置视景体,而修改当前的投影矩阵。
glFrustum(-1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1000.0f);
}
2. 根据四个点绘制正方形
经过前面的千辛万苦,我们现在终于可以绘制正方形了。但是先别着急,我们先来澄清几个概念,因为我们暂时没有考虑模型矩阵,所以正方形定义的四个坐标点,必须是相机坐标系下的。
现在我们可以定义正方形的四个顶点,注意这四个点都是相机坐标系下。
float vctPnt[][3] = {
{-0.5f, 0.5f, -2.0f},
{ 0.5f, 0.5f, -2.0f},
{ 0.5f, -0.5f, -2.0f},
{-0.5f, -0.5f, -2.0f}
};
1. 指定顶点
在OpenGL中,所有的几何对象最终都描述成一组有序的顶点,glVertex3f()函数可以用来指定顶点,它的原型如下:
void glVertex3f(
GLfloat x,
GLfloat y,
GLfloat z
);
其中xyz,分别表示坐标点在直角坐标系下的X,Y,Z值。
要绘制正方形,我们可以按照顺时针或者逆时针方向,将四个点依次传递给OpenGL。但是这个四个坐标点都是相机坐标系下的,而glVertex3f函数传递给OpenGL的点必须是模型坐标或世界坐标系下的。那我们该怎么才能将相机坐标系下的四个顶点,换算到世界坐标系下了?默认情况下OpenGL的世界坐标系和相机坐标系是重合的,仅仅只是Z轴方向相反,也就是说相机朝向世界坐标系的Z轴负方向。由于四个顶点必须在视景体内,因此顶点在世界坐标系的z坐标必须大于-1,小于-1000,即必须在进裁剪平面与远裁剪平面之间。而我们定义的四个顶点位于世界坐标系的Z轴负方向,刚好在相机的视景体内,因此可以直接传递给OpenGL,无需进行任何换算。 根据以上的分析,我们按照顺时针方向,将四个点依次传递给OpenGL,最终实现的代码如下:
for(int i=0; i<4; i++)
{
glVertex3f(vctPnt[i][0], vctPnt[i][1], vctPnt[i][2]);
}
2. 几何图元
void glBegin(GLenum mode)
void glEnd(void)
mode可选择的参数如下:
这里我们选择GL_LINE_LOOP作为glBegin的参数,它会将四个顶点依次连接,形成一个闭合的几何图型。这里依次连接后形成正方形,代码如下:
glBegin(GL_LINE_LOOP); // 绘制四边形
for(int i=0; i<4; i++)
{
glVertex3f(vctPnt[i][0], vctPnt[i][1], vctPnt[i][2]);
}
glEnd(); // 四边形绘制结束
到这里我们终于完成了正方形的绘制,本片博文代码不到20行,仅仅只修改了两个函数,但讲了不少内容,就是希望各位大家能深入理解代码背后的OpenGL相关知识。最后,我这里在附上完整的display函数实现:
void GLWindow::display()
{
glClear(GL_COLOR_BUFFER_BIT);
float vctPnt[][3] = {
{-0.5f, 0.5f, -2.0f},
{ 0.5f, 0.5f, -2.0f},
{ 0.5f, -0.5f, -2.0f},
{-0.5f, -0.5f, -2.0f}
};
glBegin(GL_LINE_LOOP); // 绘制四边形
for(int i=0; i<4; i++)
{
glVertex3f(vctPnt[i][0], vctPnt[i][1], vctPnt[i][2]);
}
glEnd(); // 四边形绘制结束
}
3. 编译运行
最后了,大家欣赏下自己的成果,编译链接,如果无错误的话,运行效果如下图:
至此,恭喜大家正式迈入OpenGL的学习的大门,源码下载地址:http://write.blog.csdn.net/postedit/30250401