旋转金字塔
#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);
glEnable(GL_DEPTH_TEST);
}
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 += 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);
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 鼠标进入窗口
- 表明,是离开,还是进入窗口。