OpenGL的glPushMatrix和glPopMatrix矩阵栈顶操作函数

xiaoxiao2021-02-28  42

在之前的博客中,我就说过后面会详细讲解这两个函数。今天让我们来认识下它们(glPushMatrix和glPopMatrix函数)。


OpenGL中图形绘制后,往往需要一系列的变换来达到用户的目的,而这种变换实现的原理是又通过矩阵进行操作的。Opengl中的变换一般包括视图变换、模型变换、投影变换等,在每次变换后,opengl将会呈现一种新的状态(这也就是我们为什么会成其为状态机)。

有时候在经过一些变换后我们想回到原来的状态,就像我们谈恋爱一样,换来换去还是感觉初恋好,怎么办?opengl就帮我们提供了两个函数:giPushMatrix()和glPopMatrix();

首先我们要知道,对于矩阵的操作都是对于矩阵栈的栈顶来操作的。当前矩阵即为矩阵栈的栈顶元素,而对当前矩阵进行平移、旋转等的变换操作也同样是对栈顶矩阵的修改。所以我们在变换之前调用giPushMatrix()的话,就会把当前状态压入第二层,不过此时栈顶的矩阵也与第二层的相同。

当经过一系列的变换后,栈顶矩阵被修改,此时调用glPopMatrix()时,栈顶矩阵被弹出,且又会恢复为原来的状态。 函数的作用过程可以用下图描述,更为直观。

在opengl场景中一般存在多种矩阵变换操作,而控制这些操作的命令主要用到

glMatrixMode(GLenum mode); 作用:用于指定用哪个矩阵作为当前矩阵,mode用于指定哪一种矩阵栈是其后矩阵操作的目标。mode可取: GL_MODELVIEW: 把其后的矩阵操作施加于造型视图矩阵栈。(默认) GL_PROJECTION: 把其后的矩阵操作施加于投影矩阵栈。 GL_TEXTURE: 把其后的矩阵操作施加于纹理矩阵栈。 注意上述三种模式分别对应了三种矩阵栈。 所以在场景中存在多种矩阵变换时,glPushMatrix()和glPopMatrix()一般情况下也要结合glMatrixMode(GLenum mode)运用,系统才知道具体操作的是哪个矩阵栈。

注意: 摄像机矩阵和模型矩阵用的是同一个矩阵,就是GL_MODELVIEW (model是模型搜索矩阵,view是摄像机矩阵,GL_MODELVIEW里保存的是这两个矩阵的积)。所以选择GL_MODELVIEW之后直接用glTranslate,glRotate之类的就行。

其实摄像机和模型矩阵本质上是一回事(这也是为什么OpenGL把这两个矩阵放在一起保存的原因),因为比如把整个世界向y+方向移动10跟把摄像机向y-方向移动10是等价的。旋转也是一样。

虽然矩阵里可以保存任何变换,但按照OpenGL的概念,model和view矩阵里只能保存平移,旋转和缩放;project矩阵里只能保存投影矩阵,viewport矩阵里只能保存二维平移和缩放。这样来看把model和view放在一起是合理的。他们之间的区别纯粹是人为的。

示例:

绘制太阳

在坐标系中心(原点)绘制一个红色的太阳。首先设置为红色,绘制一个半径为10的球体。代码如下:

glColor3ub(255, 0, 0); glutSolidSphere(10.0f, 15, 15);

2、绘制火星 火星在相对太阳偏移90个单位的距离:

glRotatef(saturnRotAngle, 0.0f, 1.0f, 1.0f);

设置火星的颜色:

glColor3ub(255, 200, 100);

绘制火星,假设火星的半径为5:

glutSolidSphere(5.0f, 15, 15);

由于后面我们还需要绘制地球,而地球的位置时相对于太阳的位置,所以不需要上面的位置偏移积累。所以绘制完火星后要返回上一步的位置,分别在绘制火星的代码前面与后面添加函数glPushMatrix()与glPopMatrix()。完整代码如下:

glColor3ub(255, 200, 100); //入栈 glPushMatrix(); glTranslatef(90.0f, 0.0f, 0.0f); glutSolidSphere(5.0f, 15, 15); //出栈 glPopMatrix();

3、绘制地球 以相同于绘制火星的原理绘制地球,代码如下:

glColor3ub(0, 0, 200); //入栈 glPushMatrix(); //沿x轴平移60个单位 glTranslatef(60.0f, 0.0f, 0.0f); //画球 glutSolidSphere(7.0f, 15, 15); //出栈 glPopMatrix();

4、绘制月亮 由于月球是地球的卫星,绕着地球转,所以月球的位置时相对于地球的位置。所以绘制月球跟绘制地球的代码要被glPushMatrix()与glPopMatrix()函数包着:

//地球 glColor3ub(0, 0, 200); //入栈 glPushMatrix(); //平移60个单位 glTranslatef(60.0f, 0.0f, 0.0f); glutSolidSphere(7.0f, 15, 15); //月亮 glColor3ub(88, 88, 88); //平移20个单位 glTranslatef(20.0f, 0.0f, 0.0f); glutSolidSphere(2.0f, 15, 15); //出栈 glPopMatrix();

5、让天体动起来 每次重新绘制时要给火星、地球一个旋转的角度,每次以相同方向一定的角速度旋转。同样的原理去实现月球绕地球转。

static float earthRotAngle = 0.0f; static float saturnRotAngle = 0.0f; static float moonRotAngle = 0.0f; //当旋转角度累加到大于360度时,控制角度0 moonRotAngle += 15.0f; if(moonRotAngle > 360.0f) moonRotAngle -= 360.0f; glutSolidSphere(2.0f, 15, 15); //出栈 glPopMatrix(); earthRotAngle += 10.0f; if(earthRotAngle > 360.0f) earthRotAngle -= 360.0f; saturnRotAngle += 6.0f; if(saturnRotAngle > 360.0f) saturnRotAngle -= 360.0f; //注册一个回调函数,定时刷新屏幕: void TimerFunc(int value) { //重绘函数 glutPostRedisplay(); //定时器 glutTimerFunc(100, TimerFunc, 1); }

总结:学了数据结构后,入栈出栈比较易懂,把各种矩阵就当作数据,无论栈顶的元素怎么改变,我把上一个压入的元素弹出后,那个元素没有改变,所以还是恢复到原来的样子。

完整代码:

#include <windows.h> #include <gl/glut.h> #include<math.h> const GLfloat PI = 3.1415f; GLfloat xRot = 0.0f; GLfloat yRot = 0.0f; void rendererScene(void); void changeWindowSize(GLsizei w, GLsizei h); void setupRC(void); void rotateMode(int key, int x, int y); //定时刷新显示 void TimerFunc(int value) { glutPostRedisplay();//刷新 glutTimerFunc(100, TimerFunc, 1);//定时器 } int main(int argc, char* argv[]) { glutInit(&argc, argv); //设置显示模式 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); //设置窗口大小 glutInitWindowSize(300, 300); //设置窗口在屏幕上的位置 glutInitWindowPosition(200, 200); //创建窗口标题 glutCreateWindow("三角形绘3D模型"); glOrtho(-100.0f, 100.0f, -100.0f, 100.0f, -100.0f, 100.0f); //注册窗口大小改变时回调函数 glutReshapeFunc(changeWindowSize); //注册点击上下左右方向按钮时回调rotateMode函数 glutSpecialFunc(rotateMode); //注册显示窗口时回调渲染函数 glutDisplayFunc(rendererScene); glutTimerFunc(500, TimerFunc, 1); setupRC(); //消息循环(处理操作系统等的消息,例如键盘、鼠标事件等) glutMainLoop(); return 0; } /** 渲染函数 */ void rendererScene(void) { static float earthRotAngle = 0.0f; static float saturnRotAngle = 0.0f; static float moonRotAngle = 0.0f; BOOL bCull = TRUE; //是否开启回溯剔除 BOOL bDepth = TRUE; //是否开启深度检测 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(bCull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if(bDepth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //glTranslatef(0.0f, 0.0f, -100.0f); //太阳 glColor3ub(255, 0, 0); glutSolidSphere(10.0f, 15, 15); //火星 glColor3ub(255, 200, 100); glPushMatrix(); glRotatef(saturnRotAngle, 0.0f, 1.0f, 1.0f); glTranslatef(90.0f, 0.0f, 0.0f); glutSolidSphere(5.0f, 15, 15); glPopMatrix(); //地球 glColor3ub(0, 0, 200); glPushMatrix(); glRotatef(earthRotAngle,0.0f, 1.0f, 1.0f); glTranslatef(50.0f, 0.0f, 0.0f); glutSolidSphere(7.0f, 15, 15); //月亮 glColor3ub(88, 88, 88); glRotatef(moonRotAngle, 0.0f, 1.0f, 1.0f); glTranslatef(20.0f, 0.0f, 0.0f); moonRotAngle += 15.0f; if(moonRotAngle > 360.0f) moonRotAngle -= 360.0f; glutSolidSphere(2.0f, 15, 15); glPopMatrix(); earthRotAngle += 10.0f; if(earthRotAngle > 360.0f) earthRotAngle -= 360.0f; saturnRotAngle += 6.0f; if(saturnRotAngle > 360.0f) saturnRotAngle -= 360.0f; glutSwapBuffers(); } /** 改变窗口大小时回调函数 */ void changeWindowSize(GLsizei w, GLsizei h) { GLfloat length = 100.0f; if(h == 0) h = 1; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if(w <= h) glOrtho(-length, length, -length * h / w, length * h / w, -length*2.0f, length*2.0f); else glOrtho(-length * w / h, length * w / h, -length, length, -length*2.0f, length*2.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } /** 设置 */ void setupRC(void) { //背景颜色 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glShadeModel(GL_FLAT); } /** 旋转 */ void rotateMode(int key, int x, int y) { if(key == GLUT_KEY_UP) xRot -= 5.0f; else if(key == GLUT_KEY_DOWN) xRot += 5.0f; else if(key == GLUT_KEY_LEFT) yRot -= 5.0f; else if(key == GLUT_KEY_RIGHT) yRot += 5.0f; if(xRot < 0) xRot = 355.0f; if(xRot > 360.0f) xRot = 0.0f; if(yRot < 0) yRot = 355.0f; if(yRot > 360.0f) yRot = 0.0f; glutPostRedisplay(); }
转载请注明原文地址: https://www.6miu.com/read-2626752.html

最新回复(0)