要求:开发一个画图程序,用户可以用鼠标绘制线段、矩形、圆和三角形等。通过菜单让用户选择需要绘制的图元。
框架参考
/* globals */
GLsizei wh = 500, ww = 500; /* initial window size */
float xm, ym, xmm, ymm;
int first = 0;
void mouse(int btn, int state, int x, int y)
{
if(btn==GLUT_LEFT_BUTTON && state==GLUT_DOWN)
{
xm = x;
ym = wh-y;
glColor3f(0.0, 0.0, 1.0);
glLogicOp(GL_XOR);
first = 0;
}
if(btn==GLUT_LEFT_BUTTON && state==GLUT_UP)
{
if (first == 1)
{
glRectf(xm, ym, xmm, ymm);
glFlush();
}
xmm = x;
ymm = wh-y;
glColor3f(0.0, 1.0, 0.0);
glLogicOp(GL_COPY);
glRectf(xm, ym, xmm, ymm);
glFlush();
}
if(btn==GLUT_RIGHT_BUTTON && state==GLUT_DOWN)
exit(0);
}
void move(int x, int y)
{
if(first == 1)
{
glRectf(xm, ym, xmm, ymm);
glFlush();
}
xmm = x;
ymm = wh-y;
glRectf(xm, ym, xmm, ymm);
glFlush();
first = 1;
}
void reshape(GLsizei w, GLsizei h)
{
/* adjust clipping box */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0,(GLdouble)w, 0.0,(GLdouble)h, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* adjust viewport and clear */
glViewport(0,0,w,h);
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/* set global size for use by drawing routine */
ww = w;
wh = h;
}
void init(void)
{
glViewport(0,0,ww,wh);
/* Pick 2D clipping window to match size of screen window
This choice avoids having to scale object coordinates
each time window is resized */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0,(GLdouble) ww , 0.0,(GLdouble) wh , -1.0, 1.0);
/* set clear color to black and clear window */
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
glEnable(GL_COLOR_LOGIC_OP);
}
/* display callback required by GLUT 3.0 */
void display(void)
{}
int main(int argc, char** argv)
{
/***GLUT窗口管理,就当做是固定写法吧***/
glutInit(&argc,argv); //初始化GLUT,在调用其他GLUT函数前调用
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //设置窗口的显示模式
glutInitWindowSize(ww, wh); //设置窗口的初始宽度和高度,单位为像素,缺省300x300
glutInitWindowPosition(500, 500) //窗口左上角相对于屏幕左上角的位置,单位为像素,缺省(0,0)
glutCreateWindow("rubber banding"); //创建窗口,标题为title。调用glutMainLoop()之前,窗口不会被显示
init(); //设置OpenGL状态
/***GLUT事件处理***/
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(move);
glutDisplayFunc(display);
glutMainLoop();
}
OpenGL库
首先你会发现这些函数有些不是以gl开头就是以glut开头,以什么开头就代表是哪个库中的函数。
- OpenGL核心库(OpenGL Core Library)
gl是核心,这部分函数用于常规的、核心的图形处理。 - OpenGL实用库(OpenGL Utility Library, GLU)
glu是对gl的部分封装,glut是OpenGL的跨平台工具库,gl中包含了最基本的3D函数,而glu似乎对gl的辅助,如果算数好,不用glu的情况下,也是可以做出同样的效果。 - OpenGL实用工具库(OpenGL Utility Toolkit Library, GLUT)
glut是基本的窗口界面,是独立于gl和glu的,如果不喜欢用glut可以用MFC和Win32窗口等代替,但是glut是跨平台的,这就保证了我们编出的程序是跨平台的。
OpenGL函数名称格式
GLUT事件处理
我们这里界面都是采用GLUT库来写。GLUT使用回调函数(callback)机制来进行事件处理。
- 啥叫回调函数呢(callback)?
回调就是你自己定义一个函数(比如mouse),然后把这个函数作为参数传入别的(或系统)函数(比如glutMouseFunc)中,由别人(或系统)的函数在运行时来调用的函数。简单来说,就是由别人的函数运行期间回过来调用你已经实现的那个函数。 -
void glutDisplayFunc(void(*func)(void))
每个GLUT程序都必须有一个显式回调函数,啥作用呢?比如窗口首次被打开,或者窗口移动了,在窗口中切换绘图选项时,系统都会自动调用这个函数,继而调用传入其中的函数。 - 函数指针
上面那个函数的参数void(*func)(void)
其实就是个函数指针。这个原理和int a
是一样的,int是参数a的类型, a是参数名;类比一下,这里函数指针类型是void(* )(void)
,这里参数是个函数指针类型的变量,参数名是func;其实每个函数的函数名都可以说是一个函数指针名,我们在用的时候才只要闯入相应的函数名就实现了所谓的回调。所以专业点说,回调函数就是一个通过函数指针调用的函数。 -
void glutMainLoop()
进入GLUT事件处理循,环实际就是个死循环啦。当有事件发生时,调用相应的回调函数;否则,处于等待状态。main()函数是以程序进入事件循环做为结束,而不是我们通常所写的return 0; - init()函数
这个函数中我们定义了一些OpenGL的状态量,个人认为和glutDisplayFunc里面将要传入的函数(比如这里的display)有些雷同,所以上面的display()函数我们虽然写了,但却是个空函数,(必须有这个display函数),因为一些状态我们都在init函数中设置了。里面涉及到了视图等的设置,这些之后会讲到,现在只需要这样跟着写就好了。 - GLUT使用回调函数机制来进行事件处理,我们的程序中主要用到了前4个:
–窗口重绘glutDisplayFunc()
–窗口改变大小glutReshapeFunc()
–鼠标按键glutMouseFunc()
–鼠标移动glutMotionFunc()
–键盘输入glutKeyboardFunc()
–空闲函数glutIdleFunc()
窗口
窗口是显示器上的一块矩形区域。窗口内的位置用窗口坐标来指定,单位是像素。
注意原点的位置
- 光栅显示器由于是按照从上到下,从左到右的顺序进行扫描,所以左上角是原点。窗口系统返回的信息(例如鼠标位置)假定原点在左上角。
- 学科学和工程中,左下角是原点(0,0)。OpenGL命令假定原点在左下角。
所以在画图的时候,我们要把鼠标位置的坐标转换成OpenGL命令中对应的坐标。原点在左上角和左下角相比较一下,不就是x坐标不用变,y坐标是个互补的关系嘛,用窗口的高度(wh)减掉鼠标位置的y坐标就得到OpenGL命令中的坐标啦。
画直线要求的橡皮条功能
这里用到一个函数glLogicOp
- glLogicOp(GL_COPY) (默认值)
缺省的写入模式是复制模式(copy)或替换模式 (replacement),直接用源像素取代目标像素。用这种方法不能绘制一条临时直线,因为我们不能用快速简 单的方法恢复在临时直线下面的内容。 - glLogicOp(GL_XOR)
XOR写入模式。 异或操作(XOR):相同值为0,不同值为1
因此如果应用XOR模式画一条直线,那么只要在原地再画 一遍这条直线就可以删除这条直线。
画圆
OpenGL没有提供直接画圆的函数,实现方式用一个多边形来逼近。大家都知道正多边形的边如越多越接近圆吧~
这里我用到了G_TRIANGLE_FAN这个参数,书上都有讲解。
截取一部分代码:
if (btn == GLUT_LEFT_BUTTON && state == GLUT_UP)
{
xmm = x;
ymm = wh - y;
float r = sqrt(pow((xm - xmm), 2) + pow((ym - ymm), 2));
glBegin(GL_TRIANGLE_FAN);
for (int i = 0; i< 1000; ++i){
glVertex2f(xm + r*cos(2 * PI / 1000 * i), ym + r*sin(2 * PI / 1000 * i));
}
glEnd();
glFlush();
}
画三角形
简单的一个实现的思路:用一个变量将三个点的坐标保存起来,每点击一次鼠标记录一个位置并计数,当点数满三个的时候,就把这个三角形画出来。
截取一部分代码:
void mouse_triangle(int btn, int state, int x, int y)
{
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
triVertex[ver][0] = x;
triVertex[ver][1] = wh - y;
glColor3f(1.0, 0.5, 0.0);
glBegin(GL_POINTS);
glVertex2f(triVertex[ver][0], triVertex[ver][1]);
glEnd();
glFlush();
ver++;
if (ver == 3)
{
//glLogicOp(GL_COPY);//直接用源像素取代目标像素 默认的
glBegin(GL_TRIANGLES);
glVertex2f(triVertex[0][0], triVertex[0][1]);
glVertex2f(triVertex[1][0], triVertex[1][1]);
glVertex2f(triVertex[2][0], triVertex[2][1]);
glEnd();
glFlush();
ver = 0;
}
}
}
添加菜单
用到如下的三个函数,比如:
glutCreateMenu(menu); //创建菜单
glutAddMenuEntry("line", 0); //在菜单中添加选项
glutAddMenuEntry("square", 1);
glutAddMenuEntry("triangle", 2);
glutAddMenuEntry("circle", 3);
glutAddMenuEntry("clear", 4);
glutAttachMenu(GLUT_RIGHT_BUTTON); //将菜单绑定鼠标操作