现在的位置: 首页 > 综合 > 正文

计算机图形学 — 变换之旋转一 [金字塔旋转] [各种详解哦]

2017年10月13日 ⁄ 综合 ⁄ 共 8863字 ⁄ 字号 评论关闭

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
开启使用模板测试并且更新模版缓存。参见glStencilFuncglStencilOp.
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 还提供了一些别的函数,例如“在键盘按下时做某事”等

抱歉!评论已关闭.