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

在之前的博客中,我就说过后面会详细讲解这两个函数。今天让我们来认识下它们(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放在一起是合理的。他们之间的区别纯粹是人为的。

示例:

  1. 绘制太阳

在坐标系中心(原点)绘制一个红色的太阳。首先设置为红色,绘制一个半径为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();

}
  • 9
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值