加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

OpenGL第三课--输入与交互

(2011-02-20 16:01:40)
标签:

回调函数

事件

标识符

窗口

菜单项

分类: 图像处理

内容大量摘自中国科学技术大学黄章进老师的课件,不知涉不涉及版权的问题啊,先学习了,感谢老师的课件了。

老师主页为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):只有当用户触发了设备后,输入才提供给程序

image

事件模式(Event Mode):每个触发生成一个事件,事件的测量值放到事件队列中,用户程序检查该队列,根据事件的类型采用相应的操作(回调函数)

image

事件类型:

窗口:改变尺寸、重新显示、缩成图标
鼠标:点击一个或多个按钮,移动
键盘:按下或释放某个键
空闲:“没有事件”

回调:回调是事件驱动输入方式的程序界面,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

•在程序执行过程中高度可能发生改变
•需要利用一个全局变量跟踪其变化
•新高度值返回给形状改变回调函数
•也可以用查询函数 glGetIntegervglGetFloatv获取,因为高度是状态的一部分

鼠标结束程序:可以利用简单的鼠标回调函数做到这一点

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)
     // 按下Q、q或ESC键时,终止程序
           if(key == ‘Q’ || key == ‘q’ || key == ‘\27’)
                   exit(0);
}

 

特殊按键: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) &&  (key == ‘c’ || key == ‘C’))
         exit(0);

 

显示回调函数:只要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     // 高小于宽,裁剪矩形高度置为4
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);

image

菜单回调函数

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);

image

 

 

空闲回调函数:当事件队列为空时被执行
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避免这个问题。

0

阅读 收藏 喜欢 打印举报/Report
前一篇:OpenGL第二课
后一篇:OpenGL第四课
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有