OpenGL.从环境搭建到简单动画的实现

时间:2022-04-23 19:13:09
  • 计算机图形学
  • OpenGL的使用

首先给VS2015配置OpenGL环境就花了很大的力气

然而到最后发现我一直改的都是以前安装遗留的vs2013

使用的缺失vs2015

泪奔。。。

配置OpenGL环境

  • vs2015 + OpenGL
  • 理论上来讲vs的各个版本之间对OpenGL的配置差异不大

所需材料

配置

  • 所有的.h放在vs目录/VC/include/GL文件夹下,一般没有GL则需要自己新建。
  • 所有的.lib放在vs目录/VC/lib文件夹下
  • 所有的.dll在C:\Windows\System32和C:\Windows\syswow64各放一份。已经存在的话跳过。(不过估计替换也没什么问题…不保证)
  • 打开vs在C++下创建一个空项目。右击项目-属性-连接器(Linker)-输入(Input)-附加依赖项(Additional Dependencies)。在后面的下拉框中选择<编辑>。每行一个添加上glaux.lib、glu32.lib、opengl32.lib、glut32.lib、glut.lib。
  • 搞定

示例程序

#include <gl/glut.h>

void display(void)

{
/* clear window */
glClear(GL_COLOR_BUFFER_BIT);

/* draw unit square polygon */
glBegin(GL_POLYGON);
glVertex2f(-0.5, -0.5);
glVertex2f(-0.5, 0.5);
glVertex2f(0.5, 0.5);
glVertex2f(0.5, -0.5);
glEnd();

/* flush GL buffers */
glFlush();
}


void init()
{

/* set clear color to black */

/* glClearColor (0.0, 0.0, 0.0, 0.0); */
/* set fill color to white */

/* glColor3f(1.0, 1.0, 1.0); */

/* set up standard orthogonal view with clipping */
/* box as cube of side 2 centered at origin */
/* This is default view and these statement could be removed */

/* glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); */

}

int main(int argc, char** argv)
{

/* Initialize mode and open a window in upper left corner of screen */
/* Window title is name of program (arg[0]) */

glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(0, 0);
glutCreateWindow("simple");
glutDisplayFunc(display);
init();
glutMainLoop();
}
  • 成功者可看到一个白色的正方形作为奖励

OpenGL的结构

  • OpenGL采用C的语法和风格,不过既然是建立在C++下自然也可以使用面向对象的特点,但OpenGL本身是不具备面向对象的特点。你喜欢也未尝不可。
  • 只需要引入头文件glut.h即可。glut.h中又引入了gl.h和glu.h。
  • 很多函数都是以gl、glu、glut开头的
#include<GL/glut.h>//引入头文件。

void display()//显示回调函数。描述绘制的对象。
{
glClear(GL_COLOR_BUFFER_BIT);//CLear缓存 接受GLbirdield参数来指示。GL_COLOR_BUFFER_BIT指示颜色缓存。

glBegin(GL_POLYGON); //多边形填充
glVertex2f(-0.5, 0.5);
glVertex2f(-0.7, 0.0);
glVertex2f(-0.5, -0.5);
glVertex2f(0.0, -0.7);
glVertex2f(0.5, -0.5);
glVertex2f(0.7, 0.0);
glVertex2f(0.5, 0.5);
glVertex2f(0.0, 0.7);;
glEnd();
glFlush(); //强制执行之前缓存的所有OpenGL命令
//glutSwapBuffers(); //切换双缓存
}

void moveDisplay(int value)
{
glutPostRedisplay();//强制重绘
glutTimerFunc(25, moveDisplay, 1);
}

void init()
{//状态变量的初始化工作
glClearColor(0.0, 0.0, 0.0, 0.0);//设置清屏颜色,模式必须是RGBA
glColor3f(1.0, 1.0, 1.0);//设置绘制颜色
}

void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);//(x,y,width,height)左下角(x,y)宽width高height。规定画布区域。

glMatrixMode(GL_PROJECTION);//指定哪个矩阵会受后续变换函数的影响
glLoadIdentity();//将当前矩阵设置为单位矩阵
gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
//gluOrtho2D(-2.0, 2.0, -2.0, 2.0); //指定一个二维可视裁剪区域(Left,Right,Bottom,Top)左下(Left,Bottom)右上(Right,Top)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void mouseClick(int button, int state, int x, int y)
{//鼠标单击事件
switch (button) //glutIdleFunc当循环队列为空时则触发该事件。
{ //问题:循环过快。
case GLUT_LEFT_BUTTON:
if (state == GLUT_DOWN) {
moveDisplay(1);
}
break;
case GLUT_MIDDLE_BUTTON:
case GLUT_RIGHT_BUTTON:
if (state == GLUT_DOWN)
{
}
break;
default:
break;
}
}

void mouseMotion(int x, int y)
{//鼠标按下后拖动时不断调用函数,基于左上角为原点的坐标系
std::cout << x << " " << y << std::endl;
}

int main(int argc, char **argv)
{
glutInit(&argc, argv);//初始化
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);//设置颜色模式(RGB/RGBA/INDEX)和缓存模式(单/双)
glutInitWindowSize(500, 500);//设置窗口大小
glutInitWindowPosition(400, 180);//设置窗口位置
glutCreateWindow("sample");//创建窗口。默认左上角和300*300。
init();//如上init函数进行设置

glutDisplayFunc(display);//回调函数display。每次窗口重绘时调用的函数。
glutReshapeFunc(reshape);//规定窗口
glutMouseFunc(mouseClick); //鼠标单击事件
glutMotionFunc(mouseMotion);//鼠标移动时间

glutMainLoop();//进入无限事件循环
}
  • 先从main函数开始一个一个介绍

main函数

int main(int argc, char **argv)
  • 以命令行参数的形式给出,可以在调用这个程序的时候传入一些参数。具体有什么用并不知道。但是似乎一定要和下面这个函数相关联。
glutInit(&argc, argv);//初始化函数
  • main中第一个调用的就是这个函数。作用是做一些初始化工作。其实也不一定非第一个执行这个函数,只要它在某些函数(大概也许就是下面这几个glut开头 函数)之前就可以。
    glutInitDisplayMode(GLUT_SINGLE| GLUT_RGB); //设置颜色模式(RGB/RGBA/INDEX)和缓存模式(单/双)
glutInitWindowSize(500, 500);//设置窗口大小
glutInitWindowPosition(400, 180); //设置窗口位置
glutCreateWindow("sample"); //创建窗口。传入名称。```
  • 颜色模式RGB/RGBA/INDEX,即普通的RGB模式、多了个alpha分量的RGBA模式、索引模式。具体解释不明。缓存模式有单缓存和双缓存。双缓存是用来支持动画的显示。
  • 窗口大小。像素值。
  • 窗口位置。窗口左上角相对于屏幕左上角的位置。屏幕坐标系以左上角为原点,x轴向右,y轴向下。
  • 创建窗口。字符串为窗口名。
init();//状态变量初始化函数
  • 函数如下
void init()
{//状态变量的初始化工作
glClearColor(0.0, 0.0, 0.0, 0.0);//设置清屏颜色,模式必须是RGBA
glColor3f(1.0, 1.0, 1.0);//设置绘制颜色
  • 清屏颜色设置,以RGBA设置。
  • 绘制颜色设置,多种方式可选。
  • 颜色都是状态变量,例如绘制颜色设置成了白色,如果不再更改那么绘制图形都是用白色。如果想绘制一个蓝色的图形则需要重新调用glColor3f函数(or其他方式)来设置成蓝色。之后无论绘制什么都是蓝色。想回到白色则需要再次设置。
    glutDisplayFunc(display);//回调函数display。每次窗口重绘时调用的函数。
glutReshapeFunc(reshape);//窗口
glutMouseFunc(mouseClick); //鼠标单击事件
glutMotionFunc(mouseMotion);//鼠标移动时间
  • 回调函数display。负责绘制图形的函数,每次重绘、刷新都会调用一边display
  • ReshapFunc窗口定形函数。打开窗口、改变窗口大小时会调用。规定了画布的大小和窗口显示的范围。
  • 鼠标点击事件函数。传入的函数需要四个整型参数。int button标识鼠标的按键。int state标识按键的状态。int x和int y表示鼠标的坐标。发生鼠标事件的时候(比如点击了窗口中的某个位置)就会调用这个函数。用来可以和用户做一些简单的交互。
  • MotionFunc鼠标移动事件。只需要int x和int y坐标函数。当用户按下鼠标不松手拖动的时候会调用。。
  • 就这些可以干很多事情了。
glutMainLoop();//进入无限事件循环
  • 如果没有这一句,main函数就执行完了,程序就退出了。。。

display函数

void display()//显示回调函数。描述绘制的对象。
{
glClear(GL_COLOR_BUFFER_BIT);//CLear缓存 接受GLbirdield参数来指示。GL_COLOR_BUFFER_BIT指示颜色缓存。

glBegin(GL_POLYGON); //多边形填充
glVertex2f(-0.5, 0.5);
glVertex2f(-0.7, 0.0);
glVertex2f(-0.5, -0.5);
glVertex2f(0.0, -0.7);
glVertex2f(0.5, -0.5);
glVertex2f(0.7, 0.0);
glVertex2f(0.5, 0.5);
glVertex2f(0.0, 0.7);;
glEnd();
glFlush(); //强制执行之前缓存的所有OpenGL命令
//glutSwapBuffers(); //切换双缓存
}
  • glClear清理屏幕。既然每次重绘都要调用,那就需要先把缓冲区清理一下。用的颜色就是init状态初始化里设置的清屏颜色。也可以在glClear之前就主动调用一遍更改颜色。
  • glBegin()开始一个图形的绘制。传入的参数是GL_POLYGON也就是多边形的意思。当然还有其他很多的模式。
  • glVertex2f()以两个float参数的值来指定一个顶点的位置。多个顶点的绘制在多边形模式下会被依次连接起来并对内部进行填充。float函数的值为百分比。由于在ReshapeFunc中对窗口可视的范围设置成了原点在窗口正*,X轴向右,Y轴向上。正向最大为1。所给的八个顶点就构成了一个八边形。
  • glEnd()结束图形的绘制。
  • glFlush()可以把缓存的图形强制绘制到屏幕上。因为之前的所有操作都只是写到了缓冲区里而并没有真正的绘制到屏幕上。(不知道理解对否,红宝书还没怎么怎么开始读。。。)
  • 另一个是双缓存模式下用来代替glFlush()的函数。

reshape函数

void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);//(x,y,width,height)左下角(x,y)宽width高height。规定画布区域。

glMatrixMode(GL_PROJECTION);//指定哪个矩阵会受后续变换函数的影响
glLoadIdentity();//将当前矩阵设置为单位矩阵
gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
//gluOrtho2D(-2.0, 2.0, -2.0, 2.0); //指定一个二维可视裁剪区域(Left,Right,Bottom,Top)左下(Left,Bottom)右上(Right,Top)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
  • 传入的参数是系统调用的传入的窗口大小。
  • glViewport()规定了画布的大小。以左下角为原点,x轴右,y轴上,像素值。
  • glMatrixMode和glLoadIdentify不知道有什么用。
  • gluOrtho2D规定了窗口显示的范围。float为比例,1.0也就是对应设置窗口大小时候的500px。如果改成(0.0,1.0,0.0,1.0)也就是只显示第一象限的内容。那么对应的在原点绘制的那个八边形就只显示在第一象限的四分之一的图形了。
  • 同理后面两个也不知道有什么用55555

mouseClick

void mouseClick(int button, int state, int x, int y)
{//鼠标单击事件
switch (button) //glutIdleFunc当循环队列为空时则触发该事件。
{ //问题:循环过快。
case GLUT_LEFT_BUTTON:
if (state == GLUT_DOWN) {
moveDisplay(1);
}
break;
case GLUT_MIDDLE_BUTTON:
case GLUT_RIGHT_BUTTON:
if (state == GLUT_DOWN)
{
}
break;
default:
break;
}
}
  • button有三个预定义的值。GLUT_LEFT_BUTTON表示左键。同理其他
  • state有一些状态的预定义。GLUT_DOWN表示按键按下。同理其他。
  • x和y就是坐标了。
  • 函数内部就可以处理一些事。比如这个函数就是在按下左键后就调用moveDisplay()函数。

moveDisplay函数

void moveDisplay(int value)
{
/*动画实现*/
glutPostRedisplay();//强制重绘
glutTimerFunc(t0, moveDisplay, 1);
}
  • 当然我写的这个函数啥都没有干。。
  • 然而这个函数是动画的关键。比如你可以在动画实现部分修改一些绘制图形时依赖的参数。比如可以把八边形的八个顶点的值预先存在一个数组里,绘制的时候调用。而函数的作用就是改变数组的值。由于数组是全局变量,那么再次绘制的时候就会是一个不一样的八边形。比如移动一段距离、放大缩小等等。
  • 修改完后glutPostRedisplay()可以强制的进行重绘。记得重绘的时候调用的啥函数吗?就是display啊。display使用的数组的值被改变了。还记得display干了什么吗?清屏->绘制->显示。当当当,一个崭新的不一样的八边形。对比之前的八边形就产生了动画效果。
  • glutTimerFunc也是一个关键的函数。第一个参数是毫秒,第二个参数是一个接受一个int且没有返回值的函数,第三个参数是区别值,用来区别第二个参数(大概是这个意思吧)。作用就是在XX毫秒之后调用传入的函数。这里的关键就是在moveDisplay里用glutTimerFunc来调用了moveDisplay自己,就产生了循环。
  • 总结一下moveDisplay。修改参数->强制重绘->XX毫秒之后调用自己。这不就是动画嘛。XX毫秒就是刷新的频率。
  • PS:别忘了把模式设置成双缓存,在display后面用双缓存的切换函数。要不然不一定会产生动画效果。。。具体原因和绘制的速度、刷新的频率有关。想了解的就自己搜索。