学计算机图形学的慕课听的那些概念是云里雾里,自己在百度搜来搜去发现还是自己结合官方的说明和代码去看可能更容易理解他画图的一个具体流程,那些开篇让人头大的概念性问题还是留在敲键盘的过程中去慢慢体会。因为听了好几天课,做周末的作业发现对整个程序的主体部分的理解还不是很清晰,所以写个博客来总结一下(我发现把东西写下来远比在脑子里想半天要来的快)。因为所有OpenGL的程序也就是那么几个步骤,所以主要总结一下他的大致框架以及其中用到的函数的具体含义及用法。
main()
{
设置显示模式;
初始化窗口;
创建窗口;
设置一系列回调函数;
启动主循环;
}
这里面除了第四步,其他除了参数可能有变化,用到的函数是固定的。而我们所有关于绘制的命令全部在第四步设置一系列回调函数实现。
那么什么是回调函数呢?
百度百科上给出的定义是回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
也许只看概念很难懂,不用担心,到了第三个环节就能体会到这个回调函数的具体实现过程了。
不过根据概念可以看出我们用来绘图的回调函数是不需要我们自己调用的,而是作为函数指针在系统响应事件时自动调用。
既然除了第四步,用到的函数是固定的,下面给出的代码就是对1、2、3、5步用到的函数的总结和分析,所以可以作为一个框架使用省的再来回打了。
这里是老师给的用来检查freeglut和glew的配置是否成功的程序
先看一下效果:
//#include "stdafx.h"
//百度了一下,这个头文件说白了就是在C++中起到的作用是头文件预编译,即把C++工程中使用的MFC头文件预先编译,以后该工程编译时,直接使用预编译的结果,以加快编译速度。
//需要包含的头文件
#include
//#include
//#include
//因为freeglut的头文件中已经包含了gl和glu,所以只需要include前者即可。
#include
void display()//用来绘图的回调函数
{
//一系列OpenGL绘图函数;
}
//主函数
int main(int argc, char* argv[])
{
//对FREEGLUT进行初始化,这个函数必须在其它的FREEGLUT使用之前调用一次。
//其格式比较死板,一般照抄这句glutInit(&argc, argv)就可以了。
glutInit(&argc, argv);
//glutInitDisplayMode初始化OPENGL显示方式
//其中GLUT_RGBA表示使用RGBA颜色(A是透明度),与之对应的还有GLUT_INDEX(表示使用索引颜色)。
//GLUT_DOUBLE表示使用双缓冲,与之对应的还有GLUT_SINGLE(使用单缓冲)。
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA);
//glutInitDisplayMode (GLUT_SINGLE | GLUT_RGBA);
//设定OPENGL窗口位置和大小
glutInitWindowPosition (100, 100);
glutInitWindowSize (500, 500);
//根据前面设置的信息创建窗口。参数将被作为窗口的标题。
//注意:窗口被创建后,并不立即显示到屏幕上。需要调用glutMainLoop才能看到窗口。
glutCreateWindow ("创建一个窗口");
//中间还可以有用户自己的初始化函数、与鼠标键盘响应的函数
//glutDisplayFunc,设置一个函数,当需要进行画图时,这个函数就会被调用。
//(这个说法不够准确,但准确的说法要我说就是随时绘制和渲染,暂时这样说吧)。
//在glutDisplayFunc函数中,我们设置了“当需要画图时,请调用display函数”。
//于是display函数就用来画图。display就是上面提到的回调函数。
glutDisplayFunc(display);
//进行一个消息循环,这个函数可以显示窗口,并且等待窗口关闭后才会返回
glutMainLoop();
return 0;
}
运行一下你就会看到一个终端和一个唰白的窗口(也有可能是黑的。。。)。如果你想让你的窗口有颜色,可以把下面这两个函数放到display函数中:
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();
通过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。
注意,除了glClear之外,我们还调用了glClearColor来设置清空屏幕所用的颜色。当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。在这里,我们将屏幕设置为了类似黑板的深蓝绿色。
glutSwapBuffers()用来交换前后缓冲区,保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)。 glFlush 是强制马上输出命令执行的结果,而不是存储在缓冲区中,继续等待其他OpenGL命令。当执行双缓冲交换的时候,使用glutSwapBuffers。 但是在有glutSwapBuffers 的情况下, 不需要 glFlush 就可以达到同样的效果,因为我们执行双缓冲交换的时候,就隐形的执行了一次刷新操作。
这就是一个大致的框架,都是不可或缺的函数,但是说实话程序中的注释也仅能用来理解函数的表面含义,具体是怎么运行的还要自己体会。
不要担心茶壶很难画,因为OpenGL中给了一个这样的函数专门画茶壶。。。
为了让说明更加详细,这里就用老师给的一个用来检查freeglut和glew的配置是否成功的程序——画一个茶壶和一个正方形来举例。下面很多函数都是以后才学到的,这里主要体会OpenGL它程序这样用的一个思想以及回调函数的奥妙。
画出来的茶壶和正方形:
#include
#include
//摄像机离物体的距离
GLfloat G_fDistance = 3.6f;
//物体的旋转角度
GLfloat G_fAngle_horizon = 0.0f;
GLfloat G_fAngle_vertical = 0.0f;
////////////////////////////////////////////////
void myinit(void);//用户初始化函数
void myReshape(GLsizei w, GLsizei h);//窗口大小变化的回调函数
void display(void);
//响应键盘输入, 从而设定物体移近移远以及旋转的回调函数
void processSpecialKeys(int key, int x, int y);
void processNormalKeys(unsigned char key,int x,int y);
////////////////////////////////////////////////
//主函数
int main(int argc, char* argv[])
{
//固定步骤:
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize (500, 500);
glutInitWindowPosition (100, 100);
glutCreateWindow ("实战:画一个茶壶和一个正方形");
//调用初始化函数
myinit();
//设定窗口大小变化的回调函数
glutReshapeFunc(myReshape);
//设定键盘控制的回调函数
glutSpecialFunc(processSpecialKeys);
glutKeyboardFunc(processNormalKeys);
//开始OPENGL的循环
glutDisplayFunc(display);
//Idle的意思是闲逛,很有趣,其实这个函数就是用来对glutDisplayFunc查漏补缺的
//定时调用来防止遗漏信息。
glutIdleFunc(display);
glutMainLoop();
return 0;
}
////////////////////////////////////////////////
//用户初始化函数
void myinit(void)
{
//“glEnable(GL_DEPTH_TEST): 用来开启更新深度缓冲区的功能,也就是,如果通过比较后深度值发生变化了,会进行更新深度缓冲区的操作。
//启动它,OpenGL就可以跟踪在Z轴上的像素,这样,它只会再那个像素前方没有东西时,才会绘画这个像素。”
glEnable(GL_DEPTH_TEST);
}
//窗口大小变化时的回调函数
void myReshape(GLsizei w, GLsizei h)
{
//设定视区
glViewport(0, 0, w, h);
//设定透视方式
glMatrixMode(GL_PROJECTION);
//初始化/重置矩阵为单位矩阵
glLoadIdentity();
gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
// gluPerspective(60.0, 1.0, 1.0, 30.0); //调整窗口比例时物体会变形
}
//每桢OpenGL都会调用这个函数,用户应该把显示代码放在这个函数中
void display(void)
{
//设置清除屏幕的颜色,并清除屏幕和深度缓冲
glClearColor(0.0f,0.0f,0.0f,0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//设置成模型矩阵模式
glMatrixMode(GL_MODELVIEW);
//载入单位化矩阵
glLoadIdentity();
//坐标中心向Z轴平移-G_fDistance (使坐标中心位于摄像机前方)
glTranslatef(0.0, 0.0, -G_fDistance);
//旋转函数(这里用来配合用户调整观察物体的位置)
glRotatef(G_fAngle_horizon, 0.0f, 1.0f, 0.0f);
glRotatef(G_fAngle_vertical, 1.0f, 0.0f, 0.0f);
////////////////////////////////////////////////
//绘制物体
//画一个正方形面
glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
// glColor3ub(255, 0, 255);
glBegin(GL_QUADS);
glVertex3f (-1.0, -1.0f, 0.0f);
glVertex3f (1.0, -1.0f, 0.0f);
glVertex3f (1.0, 1.0f, 0.0f);
glVertex3f (-1.0, 1.0f, 0.0f);
glEnd();
//画一个茶壶
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glutWireTeapot(1.0);
// glutSolidTeapot(1.0);
//交换前后缓冲区保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)。
glutSwapBuffers();
// glFlush();
}
//对于键盘上特殊符号的响应函数
void processSpecialKeys(int key, int x, int y)
{
switch(key) {
case GLUT_KEY_LEFT: //左箭头可以向左旋转
G_fAngle_horizon -= 5.0f;
break;
case GLUT_KEY_RIGHT: //右箭头可以向右旋转
G_fAngle_horizon += 5.0f;
break;
case GLUT_KEY_UP: //上箭头可以向上旋转
G_fAngle_vertical -= 5.0f;
break;
case GLUT_KEY_DOWN: //下箭头可以向下旋转
G_fAngle_vertical += 5.0f;
break;
}
// glutPostRedisplay();
}
//对于键盘上正常符号的响应函数
void processNormalKeys(unsigned char key,int x,int y)
{
switch(key) {
case 97: //"a"图靠近镜头
G_fDistance -= 0.3f;
break;
case 65: //"A"图远离镜头
G_fDistance += 0.3f;
break;
case 27: //"esc"退出绘图窗口
exit(0);
}
// glutPostRedisplay();
}
上面程序执行完之后就会出现一个绿色的茶壶和一个红色的正方形,看最后两个函数就知道怎么变换茶壶的角度和“大小”了。