OpenGL之变换
这一次用到的有双缓冲、双缓存技术,空闲调用函数,激活函数(启用功能),平移和旋转等
Code:
#include<GL/glut.h> #include<stdlib.h> #pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") GLfloat rtri; //金字塔旋转角度,==这个东西叫金字塔貌似不怎么样。。。只是因为底面没有填充。。 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; //这家伙可以控制旋转角度,也就是速度,单位时间内转过的角度,当然越大越快了 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(); } 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就是空闲的、闲置的意思 glutMainLoop(); return 0; }
下面来说说以前没见过的函数的用法~
(各处总结来的,讲的都特别好~)
双缓冲技术
在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。
计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。
如果所需要绘制的图形很简单,那么这样也没什么问题。
但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。
让我们把计算机想象成一个画图比较快的人,
假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。
而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。
也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。
如何解决这一问题呢?
我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。
这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。
即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,
在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,
这一方法效率非常高,所以被广泛的采用。
注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是 OpenGL 标准中的内容。
OpenGL 为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用
的 PC 都是支持双缓冲技术的。
要启动双缓冲功能,最简单的办法就是使用 GLUT 工具包。我们以前在 main 函数里面
写:glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
其中 GLUT_SINGLE 表示单缓冲,如果改成 GLUT_DOUBLE就是双缓冲了。
当然还有需要更改的地方——每次绘制完成时,我们需要交换两个缓冲区,把绘制好的
信息用于屏幕显示(否则无论怎么绘制,还是什么都看不到)。如果使用 GLUT 工具包,
也可以很轻松的完成这一工作,只要在绘制完成时简单的调用 glutSwapBuffers函数就可
以了。-- 交换双缓存函数
总之一句话,使用双缓存,以避免把计算机作图的过程都表现出来,或者为了平滑地实现动画。
GLUT_DEPTH 和 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时,我们相当于闭上双眼,这个世界清静了,
我们什么也看不到,,,,,,,,,
之前是在 main 函数里写:glutDisplayFunc(&myDisplay);
为什么我们不直接调用myDisplay,而要采用这种看似“舍近求远”的做法呢?
原因在于——我们自己的程序无法掌握究竟什么时候该绘制窗口。
假如你的程序窗口碰巧被别的窗口遮住了,后来用户又把原来遮住的窗口移开,这时你的窗口需要重新绘制。
很不幸的,你无法知道这一事件发生的具体时间。因此这一切只好委托操作系统来办了。
既然都可以交给操作系统来代办了,那让整个循环运行起来的工作是否也可以交给操作系统呢?
我的下载不能停下来吧?我的 mp3播放还不能给耽搁了。
总不能因为我们的动画,让其他的工作都停下来。
这里的“在 CPU 空闲的时间绘制”和“在需要绘制的时候绘制”有些共通,都是“在XX 时间做 XX 事”,
GLUT 工具包也提供了一个比较类似的函数:glutIdleFunc,表示在 CPU 空闲的时间调用某一函数。
其实 GLUT 还提供了一些别的函数,例如“在键盘按下时做某事”等