标签:
回调函数事件标识符窗口菜单项 |
分类: 图像处理 |
内容大量摘自中国科学技术大学黄章进老师的课件,不知涉不涉及版权的问题啊,先学习了,感谢老师的课件了。
老师主页为http://staff.ustc.edu.cn/~zhuang/acg/
在GKS和PHIGS这两个老的API中,定义了六种类型的逻辑输入:
• 定位(Locator):返回一个位置
• 拾取(Pick):返回对象的标识ID
• 键盘(Keyboard):返回字符串
• 笔划(Stroke):返回一组位置数据
• 定值(Valuator):返回模拟量输入(浮点数)
• 选择(Choice):返回n项中的一项
在X Window系统中引入了适用于工作站网络的客户-服务器模型(C-S模型)
• 客户(Client): OpenGL程序
• 图形服务器(Graphics Server):具有指向设备、键盘和位图(即光栅)显示设备
输入模式:请求模式和事件模式
请求模式(Request Mode):只有当用户触发了设备后,输入才提供给程序
事件模式(Event Mode):每个触发生成一个事件,事件的测量值放到事件队列中,用户程序检查该队列,根据事件的类型采用相应的操作(回调函数)
事件类型:
窗口:改变尺寸、重新显示、缩成图标
鼠标:点击一个或多个按钮,移动
键盘:按下或释放某个键
空闲:“没有事件”
回调:回调是事件驱动输入方式的程序界面,GLUT中的回调函数有
•glutDisplayFunc
•glutMouseFunc
•glutReshapeFunc
•glutKeyboardFunc
•glutIdleFunc
•glutMotionFunc
•glutPassiveMotionFunc
GLUT中的事件循环:每经过事件循环一次,GLUT进行下述操作
• 查看事件队列中的事件
• 对于在队列中的每个事件,如果定义了相应的回调函数,GLUT就执行这个回调函数
• 如果对该事件没有定义回调函数,那么就忽略该事件
鼠标回调函数:
glutMouseFunc(mouse)
void mouse(int button, int state, int x, int y)
•其中button的值可能是GLUT_LEFT_BUTTON,GLUT_MIDDLE_BUTTON,GLUT_RIGHT_BUTTON表示哪个按钮导致了事件发生
•state表示相应按钮的状态:GL_UP,
GL_DOWN
•x, y表示在窗口中的位置
定位:
在屏幕上的位置通常是以像素为单位的,原点在左上角;在OpenGL中使用的世界坐标系,其原点在左下角
在这个坐标系中的y坐标需要从窗口高度中减去回调函数返回的y值:y := h-y
获取窗口尺寸:为了完成y坐标的转换,需要知道窗口的高度h
•在程序执行过程中高度可能发生改变
•需要利用一个全局变量跟踪其变化
•新高度值返回给形状改变回调函数
•也可以用查询函数 glGetIntegerv和glGetFloatv获取,因为高度是状态的一部分
鼠标结束程序:可以利用简单的鼠标回调函数做到这一点
void mouse(int btn, int state, int x, int y)
{
if(btn==GLUT_RIGHT_BUTTON && state
== GLUT_DOWN)
exit(0);
}
鼠标移动回调函数:
•glutMotionFunc(drawSquare)
被动移动回调函数,可以不用按鼠标按钮就可以连续画方框
•glutPassiveMotionFunc(drawSquare)
具体形式为
void glutMotionFunc(void
(*f)(int x,int y))
void glutPassiveMotionFunc(void (*f)(int x,int y))
键盘的使用:
glutKeyboardFunc(keyboard)
void keyboard(unsigned char key, int x, int y)
•返回键盘上被按下键的ASCII码和鼠标位置
•注意在GLUT中并不把释放按键做为一个事件
例如:
void keyboard(unsigned char key, int x, int y)
{
}
特殊按键:GLUT在glut.h中定义了特殊按键:
• 功能键1: GLUT_KEY_F1
• 向上方向键:GLUT_KEY_UP
void glutSpecialFunc(void (*func)(int key, int x, int y))
回调函数内
if(key == GLUT_KEY_F1) ……
if(key == GLUT_KEY_UP) ……
修饰键 – Ctrl/Alt/Shift:
int glutGetModifiers()
如果在鼠标或键盘事件产生时修饰键被按下,该函数返回GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL,GLUT_ACTIVE_ALT的逻辑与结果
例如:让Ctrl+c或Ctrl+C终止程序,在键盘回调函数中
if (glutGetModifiers() == GLUT_ACTIVE_CTRL)
&&
显示回调函数:只要GLUT确定需要刷新窗口,那么就会执行显示回调函数
• 当第一次打开窗口的时候
• 当改变了窗口形状的时候
• 当重新露出了窗口的时候
• 当用户程序决定需要改变显示内容的时候
glutDisplayFunc(display)注册要执行的显示回调函数display
许多事件都会导致调用显示回调函数,这会导致遍历一次事件循环的过程中多次执行显示回调函数
可以用下列方法避免这个问题
glutPostRedisplay();这条语句设置一个标志
当事件循环结束时,GLUT会检查是否设置了上述标志,如果设置了标志,那么就会执行显示回调函数
重画回调函数:
glutReshapeFunc(reshape)
void reshape(int w, int h)
•返回新窗口的宽度与高度(单位:像素)
•回调函数执行后自动发送刷新显示事件,触发显示回调
•GLUT有一个缺省的形状改变的回调函数,调用glViewport(0,0,w,h)把视口设置为新窗口
重画回调函数是放置照相机函数的恰当地方
例子,通过保持视口和世界窗口的长宽比一样,使得物体不变形
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); // 调整裁剪窗口
glLoadIdentity();
if(w<=h) // 宽小于高,裁剪矩形宽度置为4
gluOrtho2D(-2.0, 2.0,
-2.0*(GLfloat)h/(GLfloat)w,2.0*(GLfloat)h/(GLfloat)w);
else
gluOrtho2D(-2.0*(GLfloat)w/(GLfloat)h,2.0*(GLfloat)w/(GLfloat)h,
-2.0, 2.0);
glMatrixMode(GL_MODELVIEW);
}
更复杂的交互程序:
子窗口:
int glutCreateWindow(char
*name)
•创建一个顶层窗口name,并为其返回一个整数标识符
void glutDestroyWindow(int id)
•销毁标识符为id的窗口
void glutSetWindow(int id)
•把当前窗口设为标识符为id的窗口
int glutCreateSubWindow(int parent,
int x, int y, int width, int height)
•为parent窗口创建一个子窗口,返回子窗口的标识符。子窗口原点位于(x,y),宽度为width,高度为height
void glutPostWindowRedisplay(int
id)
•通知标识符为id的窗口重新显示
构件(widgets):
GLUT只提供了包含菜单在内的很少几个构件,构件集中包含下述工具:
• 菜单
• 滑动条
• 对话框
• 文本输入框
菜单:GLUT支持弹出式菜单,而且可以有子菜单
创建弹出式菜单的三个步骤
•定义菜单内各菜单项
•定义每个菜单项的行为,即如果菜单项被选择执行的操作
•把菜单关接到鼠标按钮上
int glutCreateMenu(void (*f)(int
value))
•创建一个使用回调函数f()的顶层菜单,并返回菜单的整数标识符
void glutAddMenuEntry(char *name, int
value)
•为当前菜单增加一个名为name的菜单项;value值在选中时返回给菜单回调函数
void glutAttachMenu(int button)
•将当前菜单关联到鼠标按钮button上(GLUT_RIGHT_BUTTON、GLUT_MIDDLE_BUTTON、GLUT_LEFT_BUTTON)
例子:
在main()中
menu_id = glutCreateMenu(mymenu);
glutAddMenuEntry(“Clear Screen”, 1);
glutAddMenuEntry(“Exit”, 2);
glutAttachMenu(GLUT_RIGHT_BUTTON);
菜单回调函数
void mymenu(int value) {
if(value==1) glClear(GL_COLOR_BUFFER_BIT);
if(value==2) exit(0);
}
添加子菜单:
void glutAddSubMenu(char *submenu_name, int submenu_id)
增加一个子菜单项submenu_name作为当前菜单的一项,子菜单创建时返回的标识符为submenu_id;
必须先创建子菜单
例子:在main()函数中创建一个层级菜单
int sub_menu;
sub_menu = glutCreateMenu(processSizeMenu);
glutAddMenuEntry(“increase square size“,2);
glutAddMenuEntry(“decrease square size“,3);
glutCreateMenu(processTopMenu);
glutAddMenuEntry(“quit“,1);
glutAddSubMenu(“resize",sub_menu);
glutAttachMenu(GLUT_RIGHT_BUTTON);
空闲回调函数:当事件队列为空时被执行
void glutIdleFunc(void (*
f)(void))
glutPostRedisplay();
单缓冲区会出现错位的现象,对此可以使用双缓冲区来解决
前缓冲区:显示它的内容,但不向它写入内容
后缓冲区:写入内容,不显示
•程序在main()中请求使用双缓存
glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE)
•在显示回调函数结束之前,交换两个缓冲区
void display() {
glClear(GL_COLOR_BUFFER_BIT | …);
…
// 绘制图形
…
glutSwapBuffers();
}
拾取(picking):
OpenGL使用三种模式进行绘制
GLint glRenderMode(GLenum mode)
•GL_RENDER 正常绘制模式:正常绘制到帧缓冲区中(缺省模式)
•GL_FEEDBACK 反馈模式:提供已绘制的图元列表,但并不输出到帧缓冲区中
•GL_SELECTION
选择模式:视景体中的每个图元产生一个命中记录,这个记录放到名称堆栈中,稍后要被检测
选择模式中用到的函数
glSelectBuffer(): 指定名称缓冲区
glInitNames(): 初始化名称堆栈
glPushName(id): 把id压入名称堆栈中
glPopName(): 把名称堆栈顶部的名称弹出
glLoadName(id): 取代在名称堆栈中的栈顶名称
绘制对象前,把对象的名称加载(取代栈顶名称)到名称堆栈。初始化时,将一个未使用的名称压栈:
glInitNames();
glPushName(0);
利用选择模式进行拾取:
初始化名称缓冲区;
进入选择模式 (例如:应用鼠标);
绘制场景,场景中对象有用户指定的标识;
重新进入正常绘制模式,该操作返回命中次数;
检查名称缓冲区的内容(命中记录),命中记录包括id与深度信息。
注意:在选择模式中不能进行选取;
在选择模式中改变视图参数,使得只有靠近鼠标指针的对象位于新的视景体中
利用gluPickMatrix(…)
还有一种拾取方式,就是利用屏幕上的区域进行拾取,这时,相比于应用选择模式进行拾取而言,
通过查看鼠标位置,确定屏幕的区域就更简单了;
另外还可以通过另外的缓冲区和颜色进行拾取,对于很少数目的对象,可以给每个对象赋以唯一的颜色(有时是在颜色索引模式中),然后把场景输入到不是前缓冲区的一个颜色缓冲区中,这样就不会看到显示出来的结果,然后获到鼠标位置,利用 glReadPixels()读取缓冲区中的颜色,根据返回的颜色确定是哪个对象
像素的写入模式:
缺省的写入模式是直接用源像素取代目标像素,即d’=s,用这种方法不能绘制一条临时直线
XOR写入模式下,异或操作(XOR):d’ = d ⊕ s ,因此如果应用XOR模式画一条直线,那么只要在原地再画一遍这条直线就可以删除这条直线,也称橡皮条式绘图
OpenGL中的逻辑操作:
OpenGL支持所有的16种操作
•必须首先启动逻辑操作
glEnable(GL_COLOR_LOGIC_OP)
•然后选择逻辑操作类型
glLogicOp(GL_XOR)
glLogicOp(GL_COPY) (默认值)
即时模式与保留模式:
在标准的OpenGL程序中,一旦某个对象被显示输出,对它没有任何记忆;为了重新显示它,需要再执行一次相应的代码,即即时模式(immediate mode)图形;
另外一种模式就是首先定义对象,然后以某种形式把它记忆下来,从而更容易重新显示,这就是保留模式(retained mode)图形,在OpenGL中是通过显示列表实现的。
显示列表:
从概念上类似于图形文件
•必须首先定义列表的名称并创建它
•向列表中添加内容
•关闭列表
在客户-服务的体系环境中,显示列表是放在服务器(显示服务器)一方
显示列表中用到的函数有
void glNewList(GLuint name, GLenum
mode)
•创建一个新的显示列表,其标识符为name。
• mode: GL_COMPILE 把显示列表放到服务器,但不显示;GL_COMPILE_AND_EXECUTE
创建时即被显示
void glEndList()
•结束显示列表的定义
void glCallList(GLuint name)
•执行显示列表name
void glDeleteLists(GLuint first,
GLsizei number)
•删除自first开始的number个显示列表
•显示列表一经创建便不可修改,只能删除
例子:
创建显示列表
GLuint id;
void init(void) {
id = glGenLists(1);
glNewList(id, GL_COMPILE);
//其它OpenGL绘图子程序或者绘图命令
glEndList();
}
调用已创建的显示列表
void display(void) {
glCallList(id);
}
绝大多数OpenGL函数都可以放在显示列表中,在显示列表中发生的状态改变在列表执行结束后仍然起作用,所以可以通过刚进入显示列表时调用 glPushAttrib和glPushMatrix,执行结束之前调用glPopAttrib和glPopMatrix避免这个问题。