【菜鸟也能玩转OpenGL】OpenGL绘制正方形

本系列文章由 莫问 出品,转载请注明出处。

文章链接:http://blog.csdn.net/mni2005/article/details/27228111

作者莫问(mni2005)   邮箱:mni2005@163.com


Windows GDI环境下,要绘制一个正方形太简单了;但是在3D坏境下,这一切都将变得复杂了,也没有想想的那么容易了。我经常遇到有的3D初学者提到像下面这样的问题:

  1. “如何在OpenGL中绘制一个X像素大小的正方形?”
  2. “为什么在OpenGL绘制了一个正方形,却看不到?”

遇到这样的问题,我真没办法回答,因为这些问题都不是一句话,两句话可以说明白的?好了,当大家仔细看完本片博文后,这两个问题都应该都能大概明白怎么回事了。

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类型的,只是精度有区别而已。函数的参数说明如下:

    • pname:需要返回的参数类型,它可以传GL_MODELVIEW_MATRIX,GL_PROJECTION_MATRIX,GL_VIEWPORT等几十种宏。
    • params:需要返回的数据内存地址,一般为数组首地址。
    因此我们通过下面四行代码,可以获取到两个矩阵的数据。
    	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。

    • GL_MODELVIEW,对模型视图矩阵,应用随后的矩阵操作。
    • GL_PROJECTION,对投影矩阵,应用随后的矩阵操作。
    • GL_TEXTURE,对纹理矩阵,应用随后的矩阵操作。

    该函数看起来简单,但是好多初学者没有真正理解该函数的意义。从函数说明中,我们看到该函数设置当前可操作的目标矩阵,由于OpenGL的状态机机制,你将一个矩阵指定为当前矩阵后,那么后续的所有矩阵相关操作,都是对于该矩阵的操作,除非你再次调用glMatrixMode,修改当前可操作的目标矩阵。到了这里好多人就会问,那OpenGL都有那些函数是可以操作矩阵的?这里我简单列几个常见的:

    glLoadIdentity();
    glFrustum();
    glOrtho();
    glTranslatef();
    glScalef();
    glRotatef();
    glPushMatrix();
    glPopMatrix();
    glLoadMatrixf();
    glMultMatrixf();
    常见的修改当前矩阵的函数就大概上面几个,其它的以后遇到再说,这些函数大家也不必刻意去记,只需要有个印象就可以了,后面我会挨个详细讲解。

    2. 绘制正方形

    通过前面大片预备知识讲解,大家应该有了一个大概思路,在OpenGL中绘图,肯定要先设置两个关键矩阵,只有这样绘制的模型对象才能在屏幕中显示出来。我们在OpenGL中我绘制正方形时,直接将正方形的四个坐标点手工换算到视图坐标系下,这样就可以暂时不用考虑模型视图矩阵了。因此我们绘制正方形时,只需要设置好投影矩阵,然后将正方形的四个坐标点传递给OpenGL,调用绘制接口就大公告成了。

    1. 设置投影矩阵

    前面的概念理解了,这里进行代码实现就简单多了,要修改投影矩阵,先将当前矩阵切换到投影矩阵,然后调用glFrustum()函数设置投影矩阵。glFrustum是opengl类库中一个非常关键的函数,从函数表面看,它定义一个平截头体,也就是一个视景体;从深层次和内部的实现来看,它计算了一个投影矩阵,并且把该投影矩阵与当前投影矩阵(一般为单位矩阵)相乘。它的原型如下:
    void glFrustum(
      GLdouble left,
      GLdouble right,
      GLdouble bottom,
      GLdouble top,
      GLdouble znear,
      GLdouble zfar
    );
    
    • left,right指明相对于垂直平面的左右坐标位置
    • bottom,top指明相对于水平剪切面的下上位置
    • nearVal,farVal指明相对于深度剪切面的远近的距离,两个必须为正数

    各个参数指示的位置如下图所示:

    前面我们提到视景体的概念,它有两个用途。首先视景体决定了视图坐标系中一个物体是如何投影的到屏幕上的。其次它决定了一个那些物体(或者物体的一部分)被裁剪到最终的图像之外,也就是说视景体之外的物体(或物体的一部分部分)在屏幕上看不到。最终完成的代码如下:

    	// 设置当前矩阵为投影矩阵
    	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. 几何图元
    我们已经知道了如何指定顶点,现在还要告诉OpenGL如何使用这些顶点绘制几何图元。为了实现这个目的,需要把一组顶点放到glBegin()和glEnd()之间。传递给glBegin()的参数决定了,这些顶点所构成的几何图元类型。它们的函数原型如下:
    void glBegin(GLenum mode)
    void glEnd(void)
    mode可选择的参数如下:
    • GL_POINTS:把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点
    • GL_LINES:把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段
    • GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段
    • GL_LINE_LOOP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制n条线段
    • GL_TRIANGLES:把每个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形
    • GL_TRIANGLE_STRIP:绘制一组相连的三角形,对于奇数n,顶点n、n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1、n和n+2定义了第n个三角形,总共绘制N-2个三角形
    • GL_TRIANGLE_FAN:绘制一组相连的三角形,三角形是由第一个顶点及其后给定的顶点确定,顶点1、n+1和n+2定义了第n个三角形,总共绘制N-2个三角形
    • GL_QUADS:绘制由四个顶点组成的一组单独的四边形。顶点4n-3、4n-2、4n-1和4n定义了第n个四边形。总共绘制N/4个四边形
    • GL_QUAD_STRIP:绘制一组相连的四边形。每个四边形是由一对顶点及其后给定的一对顶点共同确定的。顶点2n-1、2n、2n+2和2n+1定义了第n个四边形,总共绘制N/2-1个四边形
    • GL_POLYGON:绘制一个凸多边形。顶点1到n定义了这个多边形。
    对这个参数,大家还有什么不明白的,可以看下面的示意图:

    这里我们选择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

#include #include "stdafx.h" #define GLUT_DISABLE_ATEXIT_HACK #include //#pragma comment(lib, "glut32.lib") GLfloat AngleX;//旋转向量 GLfloat AngleY; void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); //这个函数其实就是对接下来要做什么进行一下声明 GL_MODELVIEW 模型视图 glLoadIdentity(); glPushMatrix();// 提供了相应的接口 { glRotatef(AngleX, 1.0f, 0.0f, 0.0f); glRotatef(AngleY, 0.0f, 1.0f, 0.0f); glBegin(GL_POLYGON); //前表面 glColor3f(1.0f,1.0f,1.0f);//颜色设置为白色 glVertex3f(50.0f, 50.0f, 50.0f); glColor3f(1.0f,1.0f,0.0f);//颜色设置为黄色 glVertex3f(50.0f, -50.0f, 50.0f); glColor3f(1.0f,0.0f,0.0f);//颜色设置为红色 glVertex3f(-50.0f, -50.0f, 50.0f); glColor3f(1.0f,0.0f,1.0f);//颜色设置为品红色 glVertex3f(-50.0f, 50.0f, 50.0f); glEnd(); glBegin(GL_POLYGON); //后表面 glColor3f(0.0f, 1.0f, 1.0f);//颜色设置为青色 glVertex3f(50.0f, 50.0f, -50.0f); glColor3f(0.0f, 1.0f, 0.0f);//颜色设置为绿色 glVertex3f(50.0f, -50.0f, -50.0f); glColor3f(0.0f, 0.0f, 0.0f);//颜色设置为黑色 glVertex3f(-50.0f, -50.0f, -50.0f); glColor3f(0.0f, 0.0f, 1.0f);//颜色设置为蓝色 glVertex3f(-50.0f, 50.0f, -50.0f); glEnd(); glBegin(GL_POLYGON); //右表面 glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)255);//颜色设置为白色 glVertex3f(50.0f, 50.0f, 50.0f); glColor3f(0.0f, 1.0f, 1.0f);//颜色设置为青色 glVertex3f(50.0f, 50.0f, -50.0f); glColor3f(0.0f, 1.0f, 0.0f);//颜色设置为绿色 glVertex3f(50.0f, -50.0f, -50.0f); glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)0);//颜色设置为黄色 glVertex3f(50.0f, -50.0f, 50.0f); glEnd(); glBegin(GL_POLYGON); //左表面 glColor3d(0.0, 0.0, 1.0);//颜色设置为蓝色 glVertex3f(-50.0f, 50.0f, -50.0f); glColor3f(0.0f, 0.0f, 0.0f);//颜色设置为黑色 glVertex3f(-50.0f, -50.0f, -50.0f); glColor3ub((GLubyte)255, (GLubyte)0, (GLubyte)0);//颜色设置为红色 glVertex3f(-50.0f, -50.0f, 50.0f); glColor3f(1.0f, 0.0f, 1.0f);//颜色设置为品红色 glVertex3f(-50.0f, 50.0f, 50.0f); glEnd(); glBegin(GL_POLYGON); //上表面 glColor3d(0.0, 1.0, 1.0);//颜色设置为青色 glVertex3f(50.0f, 50.0f, -50.0f); glColor3d(1.0, 1.0, 1.0);//颜色设置为白色 glVertex3f(50.0f, 50.0f, 50.0f); glColor3d(1.0, 0.0, 1.0);//颜色设置为品红色 glVertex3f(-50.0f, 50.0f, 50.0f); glColor3d(0.0, 0.0, 1.0);//颜色设置为蓝色 glVertex3f(-50.0f, 50.0f, -50.0f); glEnd(); glBegin(GL_POLYGON); //下表面 glColor3f(0.0f, 1.0f, 0.0f);//颜色设置为绿色 glVertex3f(50.0f, -50.0f, -50.0f); glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)0);//颜色设置为黄色 glVertex3f(50.0f, -50.0f, 50.0f); glColor3f(1.0f, 0.0f, 0.0f);//颜色设置为红色 glVertex3f(-50.0f, -50.0f, 50.0f); glColor3f(0.0f, 0.0f, 0.0f);//颜色设置为黑色 glVertex3f(-50.0f, -50.0f, -50.0f); glEnd(); } glPopMatrix(); glutSwapBuffers(); } void reshape(int w, int h) { GLfloat aspect = (GLfloat)w / (GLfloat)h; GLfloat nRange = 100.0f; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); //将当前矩阵指定为投影模式 glLoadIdentity(); //设置三维投影区 if (w 355.0f) { AngleX = 0.0f; } if (AngleX 355.0f) AngleY = 0.0f; if (AngleY < 0.0f) { AngleY = 355.0f; } glutPostRedisplay(); } void init() { AngleX = 45.0f; AngleY = 315.0f; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); //初始化OpenGL glEnable(GL_DITHER); //抖动是激活的。 glShadeModel(GL_SMOOTH);//两点间颜色有过渡效果 } void main(int argc, char* argv[]) { glutInit(&argc;, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); //使用双缓存 使用深度缓存。 glutInitWindowSize(480, 480); glutCreateWindow("OpenGL颜色立方体"); glutReshapeFunc(reshape); //窗口改变的时候调用的函数 glutDisplayFunc(display); glutSpecialFunc(key_board); //函数注册鼠标响应事件 init(); glutMainLoop(); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫_问

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值