本节是OpenGL学习的第四个课时,下面介绍OpenGL点的相关知识:
(1)点的概念:
数学上的点,只有位置,没有大小。但在计算机中,无论计算精度如何提高,始终不能表示一个无穷小的点。一般情况下,OpenGL中的点将被画成单个的像素,虽然它可能足够小,但并不会是无穷小。同一像素上,OpenGL可以绘制许多坐标只有稍微不同的点,但该像素的具体颜色将取决于OpenGL的实现。
点是OpenGL中绘制一切的基础。
(2)如何绘制点:
1)OpenGL为点的绘制提供了一系列函数:
glVertex2d glVertex2f glVertex3f glVertex3fv //数字表示参数的个数,字母表示参数的类型: s:表示16位整数(OpenGL中将这个类型定义为GLshort), i:表示32位整数(OpenGL中将这个类型定义为GLint和GLsizei), f:表示32位浮点数(OpenGL中将这个类型定义为GLfloat和GLclampf), d:表示64位浮点数(OpenGL中将这个类型定义为GLdouble和GLclampd)。 v:表示传递的几个参数将使用指针的方式。//OpenGL的很多函数都是采用这样的形式,一个相同的前缀再加上参数说明标记。同时用glVertex*来表示这一系列函数。
上面这些函数虽然函数名和参数都不相同,但功能都类似:
//下面5段代码是等效的: (一)glVertex2i(1, 3); (二)glVertex2f(1.0f, 3.0f); (三)glVertex3f(1.0f, 3.0f, 0.0f); (四)glVertex4f(1.0f, 3.0f, 0.0f, 1.0f); (五)GLfloat VertexArr3[] = {1.0f, 3.0f, 0.0f};glVertex3fv(VertexArr3);
2)绘制不同大小的点:
点的大小默认为1个像素,也可以用glPointSize()函数改变:
#include <GL/glut.h> #pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") void paint(void) { glClear(GL_COLOR_BUFFER_BIT); glClearColor(1,1,1,1); glViewport(0, 0, 400, 100); glViewport(0, 0, 100, 100); glPointSize(10.0f); glColor3f(1,0,0); glBegin(GL_POINTS); glVertex2f(0.0f,0.0f); glEnd(); glViewport(100, 0, 100, 100); glPointSize(20.0f); glColor3f(0, 1, 0); glBegin(GL_POINTS); glVertex2f(0.0f, 0.0f); glEnd(); glViewport(200, 0, 100, 100); glPointSize(30.0f); glColor3f(0, 0, 1); glBegin(GL_POINTS); glVertex2f(0.0f, 0.0f); glEnd(); glViewport(300, 0, 100, 100); glPointSize(40.0f); glColor3f(1, 1, 0); glBegin(GL_POINTS); glVertex2f(0.0f, 0.0f); glEnd(); glFlush(); } int main(int argc,char ** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); glutInitWindowSize(400,100); glutInitWindowPosition(500,300); glutCreateWindow("绘制不同大小的点"); glutDisplayFunc(paint); glutMainLoop(); return 0; }
需要注意的点:
1:给所画图形指定颜色,用glColor3b()不能达到效果,要用glColor3f();
2.glVertex(float x,float y)中x,y为百分比。范围均为(-1,1)表示当前区域位置。(0,0)表示中点,(1,1)表示右上角。
3.glViewport()函数的后两个参数表示区域的宽度和高度,不代表坐标值。
4.在画图完毕不要忘了glFlush();
(3)3D空间中点的动态绘制及动态查看:
#include <GL/glut.h> #include <math.h> #pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") #define GL_PI 3.1416f // 定义旋转角度,在按键控制中使用 static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; // 设置渲染状态 void SetupRC() { glClearColor(0.4f, 0.4f, 0.4f, 1.0f); glColor3f(0.0f, 1.0f, 1.0f); } // 绘制场景(显示回调函数) void RenderScene() { //存储坐标和角度 GLfloat x, y, z, angle; glClear(GL_COLOR_BUFFER_BIT); // 保存矩阵状态并旋转 glPushMatrix(); glRotatef(xRot, 1.0f, 0.0f, 0.0f);//绕x轴旋转 glRotatef(yRot, 0.0f, 1.0f, 0.0f);//绕y轴旋转 // 开始绘制顶点图元 glBegin(GL_POINTS); // 设置Z轴的起始位置 z = -50.0f; for (angle = 0.0f; angle <= (2.0f * GL_PI * 3.0f); angle += 0.1f) { //计算X、Y坐标 x = 50.0f * sin(angle);//相当于在xy平面画正弦图像,并且在z轴上有纵深变化。 y = 50.0f * cos(angle); //指定顶点位置 glVertex3f(x, y, z); //对Z坐标值稍作修改,在下一个顶点使用 z += 0.5f; } glEnd(); // 恢复矩阵状态 glPopMatrix(); // 刷新绘图命令,此时所有未执行的OpenGL命令被执行 glutSwapBuffers(); } // 当窗口大小改变时由GLUT函数库调用 void ChangeSize(GLsizei w, GLsizei h) { // 范围 GLfloat nRange = 100.0f; // 纵横比 GLfloat aspectRatio; // 防止被0所除 if (0 == h){ h = 1; } // 根据窗口大小设置视口 glViewport(0, 0, w, h); // 选择投影矩阵,并重置坐标系统 glMatrixMode(GL_PROJECTION); glLoadIdentity(); // 计算窗口的纵横比(像素比) aspectRatio = (GLfloat)w / (GLfloat)h; // 定义裁剪区域(根据窗口的纵横比,并使用正投影) if (w <= h) {//宽 < 高 glOrtho(-nRange, nRange, -nRange / aspectRatio, nRange / aspectRatio, -nRange, nRange); } else {//宽 > 高 glOrtho(-nRange * aspectRatio, nRange *aspectRatio, -nRange, nRange, -nRange, nRange); } // 选择模型视图矩阵,并重置坐标系统 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } // 按键响应函数 void SpecialKeys(int key, int x, int y) { if (key == GLUT_KEY_UP) xRot -= 5.0f; if (key == GLUT_KEY_DOWN) xRot += 5.0f; if (key == GLUT_KEY_LEFT) yRot -= 5.0f; if (key == GLUT_KEY_RIGHT) yRot += 5.0f; if (key> 356.0f) xRot = 0.0f; if (key< -1.0f) xRot = 355.0f; if (key> 356.0f) yRot = 0.0f; if (key< -1.0f) yRot = 355.0f; // 使用新的坐标重新绘制场景 glutPostRedisplay(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(480, 320); glutCreateWindow("3D空间中点的绘制与查看"); // 设置显示回调函数 glutDisplayFunc(RenderScene); // 设置当窗口的大小发生变化时的回调函数 glutReshapeFunc(ChangeSize); // 设置按键响应函数(特殊键) glutSpecialFunc(SpecialKeys); // 设置渲染状态 SetupRC(); // 启动GLUT框架的运行,一经调用便不再返回,直到程序终止 glutMainLoop(); return 0; }
1)代码解释:
1.void glutSpecialFunc(void (*func)(int key, int x, int y));
glutSpecialFunc sets the special keyboard callback for the current window.The special keyboard callback is triggered when keyboard function or directional keys are pressed. The key callback parameter is a GLUT_KEY_* constant for the special key pressed. The x and y callback parameters indicate the mouse in window relative coordinates when the key was pressed. When a new window is created, no special callback is initially registered and special key strokes in the window are ignored. Passing NULL to glutSpecialFunc disables the generation of special callbacks.
During a special callback, glutGetModifiers may be called to determine the state of modifier keys when the keystroke generating the callback occurred. An implementation should do its best to provide ways to generate all the GLUT_KEY_* special keys.
The available GLUT_KEY_* values are:
GLUT_KEY_F1:F1 function key. GLUT_KEY_F2:F2 function key. ...... GLUT_KEY_F12:F12 function key. GLUT_KEY_LEFT:Left directional key. GLUT_KEY_UP:Up directional key. GLUT_KEY_RIGHT:Right directional key. GLUT_KEY_DOWN:Down directional key. GLUT_KEY_PAGE_UP:Page up directional key. GLUT_KEY_PAGE_DOWN:Page down directional key. GLUT_KEY_HOME:Home directional key. GLUT_KEY_END:End directional key. GLUT_KEY_INSERT:Inset directional key. Note that the escape, backspace, and delete keys are generated as an ASCII character.
设置当前窗口的特定键的回调函数。x,y:当按下键时鼠标的坐标,相对于窗口左上角,以像素为单位。
注意:ESC,回车和delete键由ASCII码产生,即可以用glutKeyboardFunc()处理. 当在键盘上敲击上述按键时调用该函数.注意与glutKeyboardFunc()的区别。
2.自定义键盘监听函数(注册)
一个经常的用法是当按下ESCAPE键时退出应用程序。
注意,我们提到过,glutMainLoop函数产生的是一个永无止境的循环。唯一的跳出循环的方法就是调用系统exit函数。这就是我们函数要做的,当按下ESCAPE键调用exit函数终止应用程序(同时要记住在源代码包含头文件stdlib.h)。下面就是这个函数的代码:
void processNormalKeys(unsigned char key,int x,int y) { if(key==27) Exit(0); }
我们还可以控制特殊键的按键消息:
//当在绘图中动态指定绘图颜色时: void processSpecialKeys(int key, int x, int y) { switch(key) { case GLUT_KEY_F1 : red = 1.0; green = 0.0; blue = 0.0; break; case GLUT_KEY_F2 : red = 0.0; green = 1.0; blue = 0.0; break; case GLUT_KEY_F3 : red = 0.0; green = 0.0; blue = 1.0; break; } }
3.glPushMatrix()、glPopMatrix()
相当于栈里的入栈和出栈。glPushMatrix其实就是把当前状态做一个副本放入堆栈之中;glPopMatrix()从栈中取出一个副本,恢复成副本的状态。
glLoadIdentity(); glTranslatef(1,0,0);//向右移动(1,0,0) glPushMatrix();//保存当前位置 glTranslatef(0,1,0);//现在是(1,1,0)了 glPopMatrix();//这样,现在又回到(1,0,0)了
4.void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
模型变换函数中的旋转。angle表示旋转的角度(注意单位不是弧度),(x,y,z)表示转轴。
glRotatef(45.0, 0.0, 0.0, 1.0);//表示模型沿着(0,0,1)这个轴旋转45°。
5.glFlush和glutSwapBuffers[void glFlush(void void);void glutSwapBuffers(void)]
glFlush 是强制马上输出命令执行的结果,而不是存储在缓冲区中,继续等待其他OpenGL命令。
当执行双缓冲交换的时候,使用glutSwapBuffers。但是在有 glutSwapBuffers 的情况下,不需要 glFlush 就可以达到同样的效果,因为我们执行双缓冲交换的时候,就隐形的执行了一次刷新操作。
6.void glutPostRedisplay(void)与Void glutIdleFunc(void(*f)(void))
glutIdleFunc()空闲回调函数标识了这样一个函数:当事件队列中没有事件需要处理时(即事件队列为空时),它才有机会得到执行。
这两个函数经常这样用:
glutIdleFunc(myidle); void myidle() { glutPostRedisplay(); }
glutPostRedisplay标记当前窗口需要重新绘制。通过glutMainLoop下一次循环时,窗口显示将被回调以重新显示窗口的正常面板。多次调用glutPostRedisplay,在下一个显示回调只产生单一的重新显示回调。
2)相关知识:
1.双缓冲技术:
glutSwapBuffers函数是OpenGL中GLUT工具包中用于实现双缓冲技术的一个重要函数。该函数的功能是交换两个缓冲区指针。当我们进行复杂的绘图操作时,画面便可能有明显的闪烁。解决这个问题的关键在于使绘制的东西同时出现在屏幕上。
双缓冲技术,是指使用两个缓冲区: 前台缓冲和后台缓冲。前台缓冲即我们看到的屏幕,后台缓冲则在内存当中,对我们来说是不可见的。每次的所有绘图操作都在后台缓冲中进行,当绘制完成时, 把绘制的最终结果复制到屏幕上,这样,我们看到所有GDI元素同时出现在屏幕上,从而解决了频繁刷新导致的画面闪烁问题。
在OpenGL中实现双缓冲技术的一种简单方法:
1.在调用glutInitDisplayMode函数时, 开启GLUT_DOUBLE,即glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);。这里将我们惯用的GLUT_SINGLE替换为GLUT_DOUBLE,意为要使用双缓冲而非单缓冲。 2. 调用glutDisplayFunc(display)注册回调函数时, 在回调函数中所有绘制操作完成后调用glutSwapBuffers()交换两个缓冲区指针。 3. 调用glutIdleFunc注册一个空闲时绘制操作函数, 注册的这个函数再调用display函数。
2.单一的重新显示回调:
glutPostRedisplay()所执行的功能类似于直接调用显示回调函数display(),但该函数允许实现在对何时真正需要调用显示回调函数而作出决策时,变得更加“智能化”。
在GLUT遍历整个事件循环时,必然会检索到许多要求窗口重绘的事件。如果每次都去直接调用显示回调函数,窗口必然会被多次绘制。而使用glutPostRedisplay()之后,就使得在遍历消息队列的整个过程中,只对窗口重绘一次。
(4)绘制正弦线上的点:并改变点的大小:
#include <GL/glut.h> #include <math.h> #pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") #define GL_PI 3.1416f const GLfloat factor = 0.1f; void SetupRC(void) { glClearColor(0.5f, 0.5f, 0.5f, 0.5f); } void paint(void) { GLfloat x; glColor3f(1.0f, 1.0f, 1.0f); glPointSize(4.0f);//应在glBegin之前 glBegin(GL_POINTS); for (x = -10; x<10; x += 0.1) { glVertex2f(x*factor, sin(x)*factor*5); } glEnd(); glColor3f(1.0f, 0.0f, 0.0f); glBegin(GL_LINES); glVertex2f(-1.0f, 0.0f); glVertex2f(1.0f, 0.0f); glVertex2f(0.0f, -1.0f); glVertex2f(0.0f, 1.0f); glEnd(); glFlush(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);//当没有键盘监听时不需要用双缓冲模式 glutInitWindowSize(400, 400); glutCreateWindow("正弦点图"); glutDisplayFunc(paint); SetupRC(); glutMainLoop(); return 0; }