平移 & 旋转 & 双缓冲 & 双缓存 & 空闲调用 & 激活函数(启用功能) & 按键控制

  • 参考: kiya-z OpenGL系列

旋转金字塔

#include<GL/glut.h>
#include<stdlib.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
GLfloat rtri;   //金字塔旋转角度,==这个东西叫金字塔貌似不怎么样。。。只是因为底面没有填充。。
float add = 0.1f;
void init()
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glShadeModel(GL_SMOOTH); //GL_FLAT和GL_SMOOTH在这里的区别很明显哟
    glEnable(GL_DEPTH_TEST);
    /*激活深度测试,也就是,如果通过比较后深度值发生变化了,会进行更新深度缓冲区的操作。 启动它,OpenGL就可以跟踪Z轴上的像素,这样,它只会在那个像素前方没有东西时,才会绘画这个像素。 通俗的说,就是根据坐标的远近自动隐藏被遮住的图形(材料)*/
}
void mydisplay()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f, 0.2f, -4.0f); //平移,三个方向的偏移量,缩小一下
    glRotatef(rtri, 0.0f, 1.0f, 0.0f); //旋转,沿各个不同方向
    //第一个面
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    //第二个面
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);
    glColor3f(1.0f, 0.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    //第三个面
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    //第四个面
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd();
    //rtri += 0.1f;
    rtri += add; //这家伙可以控制旋转角度,也就是速度,单位时间内转过的角度,当然越大越快了
    glutSwapBuffers();
}
void reshape(int width, int height)
{
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
void mouse(int button,int state,int x,int y)
{
    //前面三行是按键,后面是滚轮
    if(button == GLUT_LEFT_BUTTON)  { if(add==0) add = 0.1f; add = add*2;}
    if(button == GLUT_MIDDLE_BUTTON)    add = 0;
    if(button == GLUT_RIGHT_BUTTON) { if(add==0) add = 0.1f;  add = add/2;}
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition(300, 100);
    glutInitWindowSize(650, 500);
    glutCreateWindow("change");
    init();
    glutDisplayFunc(mydisplay);
    glutReshapeFunc(reshape);
    glutIdleFunc(mydisplay); //设置空闲时调用的函数,idle就是空闲的、闲置的意思
    glutMouseFunc(mouse);
    glutMainLoop();
    return 0;
}

双缓冲技术介绍 (废话…)

  • 在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。
  • 计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。
  • 如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。
  • 让我们把计算机想象成一个画图比较快的人,
  • 假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。
  • 而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。
  • 也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。
  • 如何解决这一问题呢?
  • 我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。
  • 这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。
  • 即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,
  • 在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。
  • 注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是 OpenGL 标准中的内容。
  • OpenGL 为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用的 PC 都是支持双缓冲技术的。
  • 要启动双缓冲功能,最简单的办法就是使用 GLUT 工具包。我们以前在 main 函数里面写:glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
  • 其中 GLUT_SINGLE 表示单缓冲,如果改成 GLUT_DOUBLE就是双缓冲了。
  • 当然还有需要更改的地方——每次绘制完成时,我们需要交换两个缓冲区,把绘制好的信息用于屏幕显示(否则无论怎么绘制,还是什么都看不到)。
  • 如果使用 GLUT 工具包,也可以很轻松的完成这一工作,只要在绘制完成时简单的调用 glutSwapBuffers函数就可以了。– 交换双缓存函数
  • 总之一句话,使用双缓存,以避免把计算机作图的过程都表现出来,或者为了平滑地实现动画。

glutInitDisplayMode(GLUT_DEPTH) 和 glEnable(GL_DEPTH_TEST)

  • 在glutInitDisplayMode()参数说明GLUT_DEPTH,表明窗口使用深度缓存
  • 在glEnable里激活GL_DEPTH_TEST,说明启用深度测试,也就是,如果通过比较后深度值发生变化了,会进行更新深度缓冲区的操作。启动它,OpenGL就可以跟踪Z轴上的像素,这样,它只会在那个像素前方没有东西时,才会绘画这个像素。通俗的说,就是根据坐标的远近自动隐藏被遮住的图形(材料)
  • 在glutInitDisplayMode()里还有很多别的参数如下:
对应宏定义 意义
GLUT_RGB 0x0000 指定 RGB 颜色模式的窗口
GLUT_RGBA 0x0000 指定 RGBA 颜色模式的窗口
GLUT_INDEX 0x0001 指定颜色索引模式的窗口
GLUT_SINGLE 0x0000 指定单缓存窗口
GLUT_DOUBLE 0x0002 指定双缓存窗口
GLUT_ACCUM 0x0004 窗口使用累加缓存
GLUT_ALPHA 0x0008 窗口的颜色分量包含 alpha 值
GLUT_DEPTH 0x0010 窗口使用深度缓存
GLUT_STENCIL 0x0020 窗口使用模板缓存
GLUT_MULTISAMPLE 0x0080 指定支持多样本功能的窗口
GLUT_STEREO 0x0100 指定立体窗口
GLUT_LUMINANCE 0x0200 窗口使用亮度颜色模型
  • glEnable用于启用各种功能。具体功能由参数决定。意思就是我让你复活了!
  • 与glDisable相对应。glDisable用以关闭各项功能,我又让你死了
  • 在glEnable()里也有很多可以用,如下所示
类型 说明
GL_ALPHA_TEST 4864 根据函数glAlphaFunc的条件要求来决定图形透明的层度是否显示。具体参见glAlphaFunc
GL_AUTO_NORMAL 3456 执行后,图形能把光反射到各个方向
GL_BLEND 3042 启用颜色混合。例如实现半透明效果
GL_CLIP_PLANE0 ~ GL_CLIP_PLANE5 12288 ~ 12283 根据函数glClipPlane的条件要求 启用图形切割管道。这里指六种缓存管道
GL_COLOR_LOGIC_OP 3058 启用每一像素的色彩为位逻辑运算
GL_COLOR_MATERIAL 2930 执行后,图形(材料)将根据光线的照耀进行反射。 反射要求由函数glColorMaterial进行设定。
GL_CULL_FACE 2884 根据函数glCullFace要求启用隐藏图形材料的面。
GL_DEPTH_TEST 2929 启用深度测试。根据坐标的远近自动隐藏被遮住的图形(材料)
GL_DITHER 3024 启用抖动
GL_FOG 2912 雾化效果 例如距离越远越模糊
GL_INDEX_LOGIC_OP 3057 逻辑操作
GL_LIGHT0 ~ GL_LIGHT7 16384 ~ 16391 启用0号灯到7号灯(光源) 光源要求由函数glLight函数来完成
GL_LIGHTING 2896 启用灯源
GL_LINE_SMOOTH 2848 执行后,过虑线段的锯齿
GL_LINE_STIPPLE 2852 执行后,画虚线
GL_LOGIC_OP 3057 逻辑操作
GL_MAP1_COLOR_4 3472 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成RGBA曲线
GL_MAP1_INDEX 3473 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成颜色索引曲线
GL_MAP1_NORMAL 3474 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成法线
GL_MAP1_TEXTURE_COORD_1 3475 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成文理坐标
GL_MAP1_TEXTURE_COORD_2 3476 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成文理坐标
GL_MAP1_TEXTURE_COORD_3 3477 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成文理坐标
GL_MAP1_TEXTURE_COORD_4 3478 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 生成文理坐标
GL_MAP1_VERTEX_3 3479 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 在三维空间里生成曲线
GL_MAP1_VERTEX_4 3480 根据函数Map1对贝赛尔曲线的设置,启用glEvalCoord1,glEvalMesh1,glEvalPoint1, 在四维空间里生成法线
GL_MAP2_COLOR_4 3504 根据函数Map2对贝赛尔曲线的设置, 启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成RGBA曲线
GL_MAP2_INDEX 3505 根据函数Map2对贝赛尔曲线的设置, 启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成颜色索引
GL_MAP2_NORMAL 3506 根据函数Map2对贝赛尔曲线的设置, 启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成法线
GL_MAP2_TEXTURE_COORD_1 3507 根据函数Map2对贝赛尔曲线的设置, 启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成纹理坐标
GL_MAP2_TEXTURE_COORD_2 3508 根据函数Map2对贝赛尔曲线的设置, 启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成纹理坐标
GL_MAP2_TEXTURE_COORD_3 3509 根据函数Map2对贝赛尔曲线的设置,启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成纹理坐标
GL_MAP2_TEXTURE_COORD_4 3510 根据函数Map2对贝赛尔曲线的设置,启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 生成纹理坐标
GL_MAP2_VERTEX_3 3511 根据函数Map2对贝赛尔曲线的设置,启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 在三维空间里生成曲线
GL_MAP2_VERTEX_4 3512 根据函数Map2对贝赛尔曲线的设置,启用glEvalCoord2,glEvalMesh2,glEvalPoint2, 在三维空间里生成曲线
GL_NORMALIZE 2977 根据函数glNormal的设置条件,启用法向量
GL_POINT_SMOOTH 2832 执行后,过虑线点的锯齿
GL_POLYGON_OFFSET_FILL 32823 根据函数glPolygonOffset的设置,启用面的深度偏移
GL_POLYGON_OFFSET_LINE 10754 根据函数glPolygonOffset的设置,启用线的深度偏移
GL_POLYGON_OFFSET_POINT 10753 根据函数glPolygonOffset的设置,启用点的深度偏移
GL_POLYGON_SMOOTH 2881 过虑图形(多边形)的锯齿
GL_POLYGON_STIPPLE 2882 执行后,多边形为矢量画图
GL_SCISSOR_TEST 3089 根据函数glScissor设置,启用图形剪切
GL_STENCIL_TEST 2960 开启使用模板测试并且更新模版缓存。参见glStencilFunc和glStencilOp.
GL_TEXTURE_1D 3552 启用一维文理
GL_TEXTURE_2D 3553 启用二维文理
GL_TEXTURE_GEN_Q 3171 根据函数glTexGen,启用纹理处理
GL_TEXTURE_GEN_R 3170 根据函数glTexGen,启用纹理处理
GL_TEXTURE_GEN_S 3168 根据函数glTexGen,启用纹理处理
GL_TEXTURE_GEN_T 3169 根据函数glTexGen,启用纹理处理

glClear和glLoadIdentity这两个函数的作用

  • glClear函数的作用是用当前缓冲区清除值,也就是函数所指定的值来清除指定的缓冲区
  • 可以使用 | 运算符组合不同的缓冲标志位,表明需要清除的缓冲,
  • 例如glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)表示要清除颜色缓冲以及深度缓冲,可以使用以下标志位
  • GL_COLOR_BUFFER_BIT: 当前可写的颜色缓冲
  • GL_DEPTH_BUFFER_BIT: 深度缓冲
  • GL_ACCUM_BUFFER_BIT: 累积缓冲
  • GL_STENCIL_BUFFER_BIT: 模板缓冲
  • glLoadIdentity:
  • 当调用glLoadIdentity()之后,实际上将当前的用户坐标系的原点移到了屏幕中心,
  • 类似于一个复位操作,OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。
  • X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
  • 中心左面的坐标值是负值,右面是正值。
  • 移向屏幕顶端是正值,移向屏幕底端是负值。
  • 移入屏幕深处是负值,移出屏幕则是正值。

glTranslatef(x,y,z):使原点沿着 X, Y 和 Z 轴移动。

  • 注意在glTranslatef(x, y, z)中,当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。
  • 其作用就是将你绘点坐标的原点在当前原点的基础上平移一个(x,y,z)向量。

glRotatef(angle, x, y, z)

  • 与glTranslatef(x, y, z)类似,glRotatef(angle, x, y, z)也是对坐标系进行操作。
  • 旋转轴经过原点,方向为(x,y,z),旋转角度为angle,方向满足右手定则。
  • 当然,如果在旋转函数后面再加一句glLoadIdentity()抚慰函数,就等于撤销了旋转操作,图形就不会旋转了

glMatrixMode()及其参数的作用:

  • glMatrixMode,这个函数其实就是对接下来要做什么进行一下声明,也就是在要做下一步之前告诉计算机我要对“什么”进行操作了,这个“什么”在glMatrixMode的“()”里的选项(参数)有,GL_PROJECTION,GL_MODELVIEW和GL_TEXTURE
  • 如果参数是GL_PROJECTION,这个是投影的意思,就是要对投影相关进行操作,也就是把物体投影到一个平面上,就像我们照相一样,把3维物体投到2维的平面上。这样,接下来的语句可以是跟透视相关的函数,比如glFrustum()或gluPerspective();
  • 如果参数是GL_MODELVIEW,这个是对模型视景的操作,接下来的语句描绘一个以模型为基础的适应,这样来设置参数,接下来用到的就是像gluLookAt()这样的函数;
  • 若是GL_TEXTURE,就是对纹理相关进行操作;
  • 顺便说下,OpenGL里面的操作,很多是基于对矩阵的操作的,比如位移,旋转,缩放,所以,这里其实说的规范一点就是glMatrixMode是用来指定哪一个矩阵是当前矩阵,而它的参数代表要操作的目标,GL_PROJECTION是对投影矩阵操作,GL_MODELVIEW是对模型视景矩阵操作,GL_TEXTURE是对纹理矩阵进行随后的操作。

gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar)

  • fovy,这个最难理解,我的理解是,眼睛睁开的角度,即,视角的大小,如果设置为0,相当你闭上眼睛了,
  • 所以什么也看不到,如果为180,那么可以认为你的视界很广阔,
  • aspect,这个好理解,就是实际窗口的纵横比,即x/y
  • zNear,表示你近处,的裁面,眼睛距离近处的距离,假设为10米远,请不要设置为负值,OpenGl就傻了,不知道怎么算了,
  • zFar表示远处的裁面,假设为1000米远,
  • 首先假设我们现在距离物体有50个单位距离远的位置,在眼睛睁开角度设置为45时,我们可以看到,在远处一个球,,
  • 现在我们将眼睛再张开点看,将”眼睛睁开的角度”设置为178(180度表示平角,那时候我们将什么也看不到,眼睛睁太大了,眼大无神)我们只看到一个点,,,,,,,,,,,,,,,,,,,,,,,,,,,
  • 因为我们看的范围太大了,这个球本身大小没有改变,但是它在我们的”视界”内太小了,
  • 在我们距离该物体3000距离远,”眼睛睁开的角度”为1时,我们似乎走进了这个球内,这个是不是类似于相机的焦距?
  • 当我们将”透视角”设置为0时,我们相当于闭上双眼,这个世界清静了,
  • 我们什么也看不到,,,,,,,,,

glutIdleFunc

  • 之前是在 main 函数里写:glutDisplayFunc(&myDisplay);意思是对系统说:如果你需要绘制窗口了,请调用 myDisplay 这个函数。
  • 为什么我们不直接调用myDisplay,而要采用这种看似“舍近求远”的做法呢?
  • 原因在于——我们自己的程序无法掌握究竟什么时候该绘制窗口。
  • 因为一般的窗口系统——拿我们熟悉一点的来说——Windows 和 X 窗口系统,都是支持同时显示多个窗口的。
  • 假如你的程序窗口碰巧被别的窗口遮住了,后来用户又把原来遮住的窗口移开,这时你的窗口需要重新绘制。
  • 很不幸的,你无法知道这一事件发生的具体时间。因此这一切只好委托操作系统来办了。
  • 既然都可以交给操作系统来代办了,那让整个循环运行起来的工作是否也可以交给操作系统呢?
  • 答案是肯定的。我们先前的思路是:绘制,然后等待一段时间;再绘制,再等待一段时间。
  • 但如果去掉等待的时间,就变成了绘制,绘制,……,不停的绘制。
  • ——当然了,资源是公用的嘛,杀毒软件总要工作吧?
  • 我的下载不能停下来吧?我的 mp3播放还不能给耽搁了。
  • 总不能因为我们的动画,让其他的工作都停下来。因此,我们需要在 CPU空闲的时间绘制。
  • 这里的“在 CPU 空闲的时间绘制”和“在需要绘制的时候绘制”有些共通,都是“在XX 时间做 XX 事”,
  • GLUT 工具包也提供了一个比较类似的函数:glutIdleFunc,表示在 CPU 空闲的时间调用某一函数。
  • 其实 GLUT 还提供了一些别的函数,例如“在键盘按下时做某事”等

鼠标按键控制

  • 和键盘处理一样,GLUT为你的注册函数(也就是处理鼠标clicks事件的函数)提供了一个方法。函数glutMouseFunc,这个函数一般在程序初始化阶段被调用。函数原型如下:
  • void glutMouseFunc(void(*func)(int button,int state,int x,int y));
  • 参数:func:处理鼠标click事件的函数的函数名。
  • 从上面可以看到到,处理鼠标click事件的函数,一定有4个参数。
  • 第一个参数表明哪个鼠标键被按下或松开,这个变量可以是下面的三个值中的一个:
  • GLUT_LEFT_BUTTON
  • GLUT_MIDDLE_BUTTON
  • GLUT_RIGHT_BUTTON
  • 第二个参数表明,函数被调用发生时,鼠标的状态,也就是是被按下,或松开,可能取值如下:
  • GLUT_DOWN
  • GLUT_UP
  • 当函数被调用时,state的值是GLUT_DOWN,那么程序可能会假定将会有个GLUT_UP事件,甚至鼠标移动到窗口外面,也如此。然而,如果程序调用glutMouseFunc传递NULL作为参数,那么GLUT将不会改变鼠标的状态。
  • 剩下的两个参数(x,y)提供了鼠标当前的窗口坐标(以左上角为原点)。
  • 检测动作(motion)
  • GLUT提供鼠标motion检测能力。有两种GLUT处理的motion:active motion和passive motion。
  • Active motion是指鼠标移动并且有一个鼠标键被按下。(拖动鼠标)
  • Passive motion是指当鼠标移动时,并有没鼠标键按下。(移动鼠标)
  • 如果一个程序正在追踪鼠标,那么鼠标移动期间,没一帧将产生一个结果。
  • 和以前一样,你必须注册将处理鼠标事件的函数(定义函数)。GLUT让我们可以指定两个不同的函数,一个追踪passive motion,另一个追踪active motion
  • 它们的函数原型,如下:
  • void glutMotionFunc(void(*func)(int x,int y));
  • void glutPassiveMotionFunc(void (*func)(int x,int y));
  • 参数:Func:处理各自类型motion的函数名。
  • 处理motion的参数函数的参数(x,y)是鼠标在窗口的坐标。以左上角为原点。
  • 检测鼠标进入或离开窗口
  • GLUT还能检测鼠标鼠标离开,进入窗口区域。一个回调函数可以被定义去处理这两个事件。GLUT里,调用这个函数的是glutEntryFunc,函数原型如下:
  • void glutEntryFunc(void(*func)(int state));
  • 参数:Func:处理这些事件的函数名。
  • 上面函数的参数中,state有两个值:
  • GLUT_LEFT 鼠标离开窗口
  • GLUT_ENTERED 鼠标进入窗口
  • 表明,是离开,还是进入窗口。

你可能感兴趣的:(平移 & 旋转 & 双缓冲 & 双缓存 & 空闲调用 & 激活函数(启用功能) & 按键控制)