昨天学习了如何使用codeblocks来编译运行一个opengl的项目。在创建一个新的opengl项目时他默认已经写了一个示例,今天我们就上面的例子进行下代码的剖析,以此来敲开opengl的神秘大门。
先把代码贴上来(在此我为每个函数的作用都写上了详细的注释):
/*
* 该代码是由一位叫Nigel Stewart的写于2003年11月,例子的目的是测试以glut实现球体,圆椎,圆环的纺纱线框和平滑阴影的形状。
* 数量的几何栈和切割可以使用热键“-”或“+”调整。
*/ #ifdef __APPLE__ //如果程序中没有定义了 __APPLE__ 这个符号则加载glut,#ifdef 是预编译命令
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif #include <stdlib.h>//standard library标准库头文件,stdlib 头文件里包含了C、C++语言的最常用的系统函数,该文件包含了的C语言标准库函数的定义 static int slices = ;
static int stacks = ; /* GLUT callback Handlers */ static void resize(int width, int height)
{
const float ar = (float) width / (float) height; glViewport(, , width, height);//占据打开窗口的整个像素矩形
glMatrixMode(GL_PROJECTION);//指定当前矩阵为投影矩阵
glLoadIdentity();//该函数的功能是重置当前指定的矩阵为单位矩阵
glFrustum(-ar, ar, -1.0, 1.0, 2.0, 100.0);//创建一个透视投影的矩阵,并且用这个矩阵乘以当前矩阵 glMatrixMode(GL_MODELVIEW);
glLoadIdentity() ;
}
/*
*绘制
*/
static void display(void)
{
const double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;//返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒
const double a = t*90.0; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色缓冲以及深度缓冲
glColor3d(,,);//设置红色为当前颜色 glPushMatrix();//glPushMatrix(),glPopMatrix()这两个函数是搭配使用的
glTranslated(-2.4,1.2,-);//定义一个平移矩阵,该矩阵与当前矩阵相乘,使后续的图形进行平移变换。
glRotated(,,,);//旋转
glRotated(a,,,);
glutSolidSphere(,slices,stacks);//用于渲染一个丝状球体
glPopMatrix(); glPushMatrix();
glTranslated(,1.2,-);
glRotated(,,,);
glRotated(a,,,);
glutSolidCone(,,slices,stacks);//用于渲染一个丝状圆锥
glPopMatrix(); glPushMatrix();
glTranslated(2.4,1.2,-);
glRotated(,,,);
glRotated(a,,,);
glutSolidTorus(0.2,0.8,slices,stacks);//用于渲染一个丝状圆环
glPopMatrix(); glPushMatrix();
glTranslated(-2.4,-1.2,-);
glRotated(,,,);
glRotated(a,,,);
glutWireSphere(,slices,stacks);//用于渲染一个实体球体
glPopMatrix(); glPushMatrix();
glTranslated(,-1.2,-);
glRotated(,,,);
glRotated(a,,,);
glutWireCone(,,slices,stacks);//用于渲染一个实体圆锥
glPopMatrix(); glPushMatrix();
glTranslated(2.4,-1.2,-);
glRotated(,,,);
glRotated(a,,,);
glutWireTorus(0.2,0.8,slices,stacks);//用于渲染一个实体圆环
glPopMatrix(); glutSwapBuffers();//因为使用的是双缓存GLUT_DOUBLE,所以这里必须要交换缓存才会显示
}
/*
*设置对应按键响应的不同事件
*/
static void key(unsigned char key, int x, int y)
{
switch (key)
{
case :
case 'q':
exit();
break; case '+':
slices++;
stacks++;
break; case '-':
if (slices> && stacks>)
{
slices--;
stacks--;
}
break;
} glutPostRedisplay();//标记当前窗口需要重新绘制。
} static void idle(void)
{
glutPostRedisplay();
} const GLfloat light_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f };
const GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat light_position[] = { 2.0f, 5.0f, 5.0f, 0.0f }; const GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f };
const GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f };
const GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const GLfloat high_shininess[] = { 100.0f }; /* Program entry point */ int main(int argc, char *argv[])
{
glutInit(&argc, argv); //调用glut函数前,要初始化glut,即调用glutInit()
glutInitWindowSize(,);//预定义窗口大小
glutInitWindowPosition(,);//设置窗口左上方位置
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);//指定了是使用RGBA模式还是双缓冲或者是深度缓冲 glutCreateWindow("GLUT Shapes");//创建一个支持opengl渲染环境的窗口 glutReshapeFunc(resize);//注册一个resize函数,表示当窗口发生变化时应采取什么行动,在这个里面resize根据缩放后的窗口重新设置
glutDisplayFunc(display);//注册一个绘图函数display,这样操作系统在必要时刻就会对窗体进行重新绘制操作。
glutKeyboardFunc(key);//注册一个按键消息处理函数,这个函数是告诉窗口系统,哪一个函数将会被调用来处理普通按键消息的。
glutIdleFunc(idle);//注册一个回调函数,如果不存在其他尚未完成的事件(例如:当事件循环处于空闲的时候),就执行这个函数。 glClearColor(,,,);//设置窗口背景色
glEnable(GL_CULL_FACE);//用于启动各种功能其功能由参数决定,GL_CULL_FACE启用隐藏图形材料的面。 开启剔除操作效果。
glCullFace(GL_BACK);//glCullFace()参数包括GL_FRONT和GL_BACK,两个参数分别表示禁用多边形正面或者背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算。 glEnable(GL_DEPTH_TEST);//启用深度测试
glDepthFunc(GL_LESS);//指定深度缓冲比较值,GL_LESS,如果输入的深度值小于参考值,则通过。 glEnable(GL_LIGHT0);//开启0号光源
glEnable(GL_NORMALIZE);//在进行光照计算之前自动单位化法向量。
glEnable(GL_COLOR_MATERIAL);//使用颜色材质
glEnable(GL_LIGHTING);//开启灯光 glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);//设置0号光源的环境强度
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);//光源的漫反射
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);//光源镜面反射
glLightfv(GL_LIGHT0, GL_POSITION, light_position);//指定光源位置 glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);//材质属性中的发射光
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);//材质属性中的散射光
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);//材质属性中的镜面反射光
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);//材质属性中的光强度 glutMainLoop();//这里让整个绘图循环进行,相当于死循环 return EXIT_SUCCESS;
}
结合注释大概浏览下代码,从上面的代码中我们可以看出OpenGL中的函数都是由gl开头的,类似的,opengl还定义了一些以前缀GL_开头的常量,所有单词都使用大写形式,并以下划线分隔。还有基本都是调用glut里面的函数接口,然后传入自己的参数去改变状态,得到自己想要的结果的。这就是因为opengl本身就是一个强大的图形api,先来理清opengl的概念。
什么是opengl
opengl是图形硬件的一种软件接口,这个接口包含的函数超过700多个,这些函数可以用于指定物体和操作,创建交互式的三维应用程序。opengl的设计目标就是作为流线型的、独立于硬件的接口,在许多不同硬件平台上实现。我们学习opengl目的就是去实现绘制自己想要的图形效果,而且他的一大特点就是与平台无关,与语言无关,比如我们基于android使用opengl绘制图形界面,那么绘制出的这些界面不仅可以再android操作系统上引用也完全可以移植到ios上去。
回到上面的例子,看懂上面代码很简单,头文件加载什么的就不多说了,看注释。
我们直接来到main函数整体观看下main函数,没有任何逻辑代码,全是调用glut的接口函数。它的基本结构非常简单就是初始化一些状态(这些状态用于控制opengl的渲染方式),并指定需要渲染的物体。还有需要注意的是,opengl跟常用计算机语言不一样,如果你要自定义写一个函数的话必须先引用glut里的注册函数注册一遍,才能行之有效,因为opengl响应事件的触发是分开的,所以会调用不同函数去初始化一遍,比如上面代码中注册的函数:
opengl是图形api他封装的接口有很多,我们无须去了解每一个函数接口的作用,只要了解常用的接口函数以及调用的逻辑,当我们特殊需要时就去看他的api文档来对应调用。
对于上面每个函数的作用我都标上了注释,所以这里不再叙述,这里着重说下双缓冲技术。
我们知道在现实生活中的动画都是我们把关键帧画完然后再进行播放,但是在计算机中不同,计算机是画完一张用一张,当要用另一张的时候再画,但是当我们要进行十分复杂的画图操作的时候,可能就会有明显的闪烁或者卡顿现象了,解决这个问题就要用到双缓冲技术。
所谓的双缓冲技术,其实就是使用两个缓冲区,前台缓冲和后台缓冲。前台缓冲是我们看到的,后台缓冲则是在内存中的。每次的画图操作都是再后台缓冲中进行,然后复制到屏幕中。这样就不会因为频繁刷新而出现闪烁了。 使用双缓冲技术也会当后台缓冲还没有画好的时候,这时前台会停留在当前画面直到后台绘制完成才进行切换。
在OpenGL中实现双缓冲的一种方式就是调用glutSwapBuffers()。
从代码中还会发现涉及到很多线性代数相关的东西,所以还需学好线性代数,推荐学习书籍《线性代数及其应用》,《opengl编程指南》