opengl入门记录--glPushMatrix和glPopMatrix原理

article/2025/10/28 8:24:47

glPushMatrix、glPopMatrix操作事实上就相当于栈里的入栈和出栈。
很多人不明确的可能是入的是什么,出的又是什么。

比如你当前的坐标系原点在你电脑屏幕的左上方。如今你调用glPushMatrix,然后再调用一堆平移、旋转代码等等,然后再绘图。那些平移和旋转都是基于左上角为原点进行变化的。并且都会改变坐标的位置,经过了这些变化后,你的坐标肯定不再左上角了。
  那假设想恢复怎么办呢?这时就调用glPopMatrix从栈里取出一个“状态”了,这个状态就是你调用glPushMatrix之前的那个状态。就如非常多opengl的书上所讲:调用glPushMatrix事实上就是把当前状态做一个副本放入堆栈之中。

当你做了一些移动或旋转等变换后,使用glPushMatrix();
OpenGL 会把这个变换后的位置和角度保存起来。
然后你再随便做第二次移动或旋转变换,再用glPopMatrix();
OpenGL 就把刚刚保存的那个位置和角度恢复。

比方:
glLoadIdentity();
glTranslatef(1,0,0);//向右移动(1,0,0)
glPushMatrix();//保存当前位置
glTranslatef(0,1,0);//如今是(1,1,0)
glPopMatrix();//这样,如今又回到(1,0,0)

1.原理解说

最终明确为什么使用glPushMatrix()和glPopMatrix()的原因了。将本次须要运行的缩放、平移等操作放在glPushMatrix和glPopMatrix之间。glPushMatrix()和glPopMatrix()的配对使用能够消除上一次的变换对本次变换的影响。使本次变换是以世界坐标系的原点为參考点进行。以下对上述结论做进一步的解释:

1)OpenGL中的modelview矩阵变换是一个马尔科夫过程:上一次的变换结果对本次变换有影响,上次modelview变换后物体在世界坐标系下的位置是本次modelview变换的起点。默认时本次变换和上次变换不独立。

2)OpenGL物体建模实际上是分两步走的。第一步,在世界坐标系的原点位置绘制出该物体;第二步,通过modelview变换矩阵对世界坐标系原点处的物体进行仿射变换,将该物体移动到世界坐标系的目标位置处。

3)将modelview变换放在glPushMatrix和glPopMatrix之间能够使本次变换和上次变换独立。

4)凡是使用glPushMatrix()和glPopMatrix()的程序一般能够判定是采用世界坐标系建模。既世界坐标系固定,modelview矩阵移动物体。

一般说来,矩阵堆栈经常使用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。比如,一辆自行车就是由两个轮子、一个三角架及其他一些零部件构成的。它的继承性表如今当自行车往前走时,首先是前轮旋转,然后整个车身向前平移,接着是后轮旋转,然后整个车身向前平移,如此进行下去,这样自行车就往前走了。将上述模型的构造过程放在glPushMatrix和glPopMatrix之间,则本次汽车在世界坐标系中的位置不是基于上一次汽车的位置而给出的(曾经一次的位置为參考),而是直接给出的以世界下的坐标(以世界坐标系的原点为參考)。整个过程是符合人的思维过程的,因为每次建模都是以单位阵为变换起点,故便于採用统一的实现方式进行处理。

矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。由于全部矩阵操作函数如glLoadMatrix()、glMultMatrix()、glLoadIdentity()等仅仅处理当前矩阵或堆栈顶部矩阵,这样堆栈中以下的其他矩阵就不受影响。堆栈操作函数有以下两个:
          void glPushMatrix(void);

          void glPopMatrix(void);

第一个函数表示将全部矩阵依次压入堆栈中,顶部矩阵第二个矩阵的备份;便于场景须要操作顶部矩阵时第二个矩阵的值不变,再弹出时又恢复压栈之前的场景;压入的矩阵数不能太多,否则出错。第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则出错。由此看出,矩阵堆栈操作与压入矩阵的顺序刚好相反,编程时要特别注意矩阵操作的顺序。为了更好地理解这两个函数,我们能够形象地觉得glPushMatrix()就是“记住自己在哪”,glPopMatrix()就是“返回自己原来所在地”。

2. 举例

例1. OpenGL光源位置的移动

移动方式: 先pushMatrix()一下,然后在进行移动操作,然后旋转操作,然后指定光源的位置,然后PopMatrix()一下,就完毕了。

#include "windows.h"
#include <gl/glut.h>

static int spin = 0;

void init()
{
glShadeModel( GL_SMOOTH );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_DEPTH_TEST );

}

void display()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 }; 

//第一点也是最重要的一点:OpenGL中的模型视图变换矩阵全是(顶点对象)右乘当前变换矩阵

//(右乘:顶点对象置于当前矩阵的右面。转载时gqb666增加。

//或者这样理解:每一次变换都是变换矩阵右乘栈顶矩阵,顶点对象右乘终于的栈顶矩阵

//(右乘:变换矩阵置于栈顶矩阵的右面。转载时gqb666增加。
glPushMatrix();  //将当前变换矩阵(单位阵)压入堆栈
glTranslatef( 0.0, 0.0, -5.0 );     // transformation 1
glPushMatrix();  //将平移变换后的的矩阵作为当前变换矩阵压入堆栈,
glRotated( (GLdouble)spin, 1.0, 0.0, 0.0 );
glLightfv( GL_LIGHT0, GL_POSITION, position );
glTranslated( 0.0, 0.0, 1.5 );
glDisable( GL_LIGHTING );
glColor3f( 0.0, 1.0, 0.0 );
glutWireCube( 0.1 );//绿色的下框,代表光源位置
glEnable( GL_LIGHTING );
glPopMatrix();  //消除绘制绿色WireCube时对当前变换矩阵的影响

glutSolidSphere( 0.5, 40, 40 );//被光照的物体
glPopMatrix(); // Pop the old matrix without the transformations.   //返回到单位矩阵
glFlush();
}

void reshape( int w, int h )
{
glViewport( 0, 0, (GLsizei)w, (GLsizei)h );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 40.0, (GLfloat)w/(GLfloat)h, 1.0, 20.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
}

void mouse( int button, int state, int x, int y )
{
switch ( button )
{
case GLUT_LEFT_BUTTON:
  if ( state == GLUT_DOWN )
  {
   spin = ( spin + 30 ) % 360;
   glutPostRedisplay();
  }
  break;
default:
  break;
}
}

int main( int argc, char ** argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH );
glutInitWindowPosition( 100, 100 );
glutInitWindowSize( 500, 500 );
glutCreateWindow( argv[0] );
init();
glutDisplayFunc( display );
glutReshapeFunc( reshape );
glutMouseFunc( mouse );
glutMainLoop();
return 0;
}

例2  机械手臂的旋转

以下样例中的机械手臂是由两个简单的长方体根据一定的继承关系构成的。glPushMatrix和glPopMatrix之间的变换相对前一次是独立的

#include "windows.h"
#include <GL/gl.h>

#include <GL/glu.h>

#include <GL/glaux.h>

void myinit(void);

void drawPlane(void);

void CALLBACK elbowAdd (void);

void CALLBACK elbowSubtract (void);

void CALLBACK shoulderAdd (void);

void CALLBACK shoulderSubtract (void);

void CALLBACK display(void);

void CALLBACK myReshape(GLsizei w, GLsizei h);


static int shoulder = 0, elbow = 0;

void CALLBACK elbowAdd (void)

{
   elbow = (elbow + 5) % 360;

}

void CALLBACK elbowSubtract (void)

{
   elbow = (elbow - 5) % 360;

}

void CALLBACK shoulderAdd (void)

{
   shoulder = (shoulder + 5) % 360;

}

void CALLBACK shoulderSubtract (void)

{
   shoulder = (shoulder - 5) % 360;

}

void CALLBACK display(void)

{
   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(0.0, 1.0, 1.0);

   glPushMatrix(); // 将此句凝视掉后能够发现上一次的变换结果对当前变换有影响,加上了glPushMatrix的目的是让各次变换相互独立。

   glTranslatef (-0.5, 0.0, 0.0); // 将旋转后的Wirebox向左移动0.5个单位
   glRotatef ((GLfloat) shoulder, 0.0, 0.0, 1.0);   //看到shoulder是相对于0的绝对角度,不是基于上一次位置的相对角度。
   glTranslatef (1.0, 0.0, 0.0); //Step 1将WireBox向右移动一个单位
   // void auxWireBox(GLdouble width,GLdouble height,GLdouble depth)

   auxWireBox(2.0, 0.2, 0.5);  //这个WireBox以x=1为中心,width=2从该中心開始向两边各延伸1个单位。

   glTranslatef (1.0, 0.0, 0.0);
   glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);
   glTranslatef (0.8, 0.0, 0.0);
   auxWireBox(1.6, 0.2, 0.5);

   glPopMatrix(); // 能够看出glPushMatrix和glPopMatrix之间的变换效果被消除。清除上一次对modelview矩阵的改动。
   glFlush();

}

void myinit (void)

{
   glShadeModel (GL_FLAT);

}

void CALLBACK myReshape(GLsizei w, GLsizei h)

{
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef (0.0, 0.0, -5.0);  /* 觉得是viewing transform 不好理解,因此时是物体不动,世界坐标系向z轴正方向移动5个单位,眼睛位于世界坐标系的原点; 此处理解为对模型的变换更加直观既将物体向负z轴移动5个单位。*/
}

void main(void)

{
   auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
   auxInitPosition (0, 0, 400, 400);
   auxInitWindow ("Composite Modeling Transformations");
   myinit ();
   auxKeyFunc (AUX_LEFT, shoulderSubtract);
   auxKeyFunc (AUX_RIGHT, shoulderAdd);
   auxKeyFunc (AUX_UP, elbowAdd);
   auxKeyFunc (AUX_DOWN, elbowSubtract);
   auxReshapeFunc (myReshape);
   auxMainLoop(display);
}

2013-7-23日加入:

在计算机图形学中,全部的变换都是通过矩阵相乘实现的,即物体定点构成的齐次坐标矩阵乘以三维变换矩阵就可得到变换后的物体齐次坐标矩阵。相同,在OpenGL中图远的坐标变换(移动、旋转、缩放)也是通过矩阵乘法实现的。OpenGL中比較重要的矩阵有投影矩阵和模型视图矩阵,外加纹理矩阵。矩阵堆栈基于矩阵引入,它能够以栈的形式保存相应的矩阵,压入和弹出操作同普通的栈,三种矩阵相应了三个矩阵堆栈。

      实际上在创建、装入、相乘模型视图变换和投影变换矩阵时都已经用到了矩阵堆栈。拿模型视图变换来说,在绘制复杂的场景时,会涉及到复杂的模型矩阵变换,经常我们须要保存这些变换的中间状态,以便在进行一些变换后高速恢复,而无需对当前模型矩阵进行反变换以进行恢复(费时)。比方,当前模型视图矩阵状态为矩阵A,场景绘制须要模型视图矩阵从A->B,然后从A->C......。利用矩阵堆栈,程序仅仅须要保存矩阵A(glPushMatrix),运行完A->B后,恢复模式视图矩阵为A(glPopMatrix),运行变换A->C。非常显然,使用矩阵堆栈比仅使用单个矩阵效率高非常多。

      函数glPushMatrix用于将当前矩阵压入矩阵堆栈(由函数glMatrixMode指定堆栈类型),即:该函数复制栈顶矩阵(比方矩阵A),并放置在栈顶(A‘),这样矩阵堆栈顶部有两个同样的矩阵,栈顶矩阵作为当前矩阵进行兴许变换。函数glPopMatrix弹出栈顶矩阵(A‘),以下一个元素变为栈顶(A),即恢复之前的模型视图状态。切记:当前矩阵永远位于堆栈顶部。矩阵堆栈是有深度的,假设压入过多矩阵或者堆栈仅仅有一个矩阵而调用glPopMatrix将导致错误。



http://chatgpt.dhexx.cn/article/B0aKjYm4.shtml

相关文章

OpenGL编程指南9:裁剪平面+glPushMatrix和glPopMatrix矩阵栈顶操作

1.任意裁剪平面 Opengl中&#xff0c;除了视景体的立方体裁剪平面之外&#xff0c;另外还可以额外指定多达6个裁剪平面&#xff0c;对视景体做进一步限制。每一个平面都由平面公式定义&#xff1a;AxByCzD 0.裁剪平面的指定通过函数&#xff1a;glClipPlane(GLenum plane,cons…

使用glPushMatrix和glPopMatrix的原因

转自 百度百科 glPushMatrix 函数将当前矩阵堆栈推送&#xff0c;通过一个&#xff0c;复制当前矩阵。 这就是后 glPushMatrix 的调用堆栈的顶部矩阵是它下面的相同的。 1. 原理讲解 终于明白为什么使用glPushMatrix()和glPopMatrix()的原因了。将本次需要执行的缩放、平移等操…

【已解决】DQN报错:NameError: name ‘glPushMatrix‘ is not defined

1、问题 pycharm在运行DQN平衡杆代码时报错&#xff1a;NameError: name ‘glPushMatrix’ is not defined。 画面只出现一个白色背景。 2、分析 pyglet版本过高&#xff0c;降低版本即可。 pip install pyglet1.5.273、测试 没有报错&#xff0c;运行成功。 参考链接…

opengl glPushMatrix()

OpenGL有三个矩阵堆栈&#xff0c;分别是GL_MODELVIEW&#xff08;模型视图矩阵堆栈&#xff09;、GL_PROJECTION&#xff08;投影矩阵堆栈&#xff09;、GL_TEXTURE&#xff08;纹理矩阵堆栈&#xff09;&#xff0c;用法和普通堆栈一样&#xff1b; 这里我们只讲模型视图矩阵…

解决:nameerror: name ‘glpushmatrix‘ is not defined

在尝试gym的render()时&#xff0c;出现错误&#xff1a; nameerror: name glpushmatrix is not defined最后解决的办法&#xff1a;更换pyglet包的版本 出现错误时的pyglet版本&#xff1a; 然后将版本更换为&#xff1a; 就可以使用env.render()啦&#xff01;

NameError: name ‘glPushMatrix‘ is not defined

完整报错如下图所示&#xff1a; 问题原因&#xff1a;pyglet版本导致 我本地安装的pyglet版本是2.0.7改成&#xff0c;问题解决 pip install pyglet1.5.0

glPushMatrix()和glPopmatirx()

OpenGL有三个矩阵堆栈&#xff0c;分别是GL_MODELVIEW&#xff08;模型视图矩阵堆栈&#xff09;、GL_PROJECTION&#xff08;投影矩阵堆栈&#xff09;、GL_TEXTURE&#xff08;纹理矩阵堆栈&#xff09;&#xff0c;用法和普通堆栈一样&#xff1b; 这里我们只讲模型视图矩阵…

OpenGL:glPushMatrix();和glPopMatrix();的作用及其原理分析

今天做到一道题&#xff0c;大致就是问glPushMatrix();和glPopMatrix();存在会对图形绘制造成什么影响&#xff0c;为了能够清晰的反应到底会存在什么影响&#xff0c;我特地写了两行代码&#xff1a; 代码①&#xff1a; void draw1() {//glClear(GL_COLOR_BUFFER_BIT); //注…

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

在之前的博客中&#xff0c;我就说过后面会详细讲解这两个函数。今天让我们来认识下它们&#xff08;glPushMatrix和glPopMatrix函数&#xff09;。 OpenGL中图形绘制后&#xff0c;往往需要一系列的变换来达到用户的目的&#xff0c;而这种变换实现的原理是又通过矩阵进行操作…

SurfaceView、GLSurfaceView、SurfaceTexture、TextureView、SurfaceHolder、Surface

SurfaceView、GLSurfaceViewe\SurfaceTexture、TextureView、SurfaceHolder、Surface 一、简介 SurfaceTexture: SurfaceTexture是从Android3.0&#xff08;API 11&#xff09;加入的一个新类。这个类跟SurfaceView很像&#xff0c;可以从video decode里面获取图像流&#xff…

android之通过SurfaceView以及SurfaceHolder进行视频播放

使用AudioView进行视频播放的时候&#xff0c;是不是很不爽&#xff0c;千篇一律的模式&#xff0c;恶心吧。这里&#xff0c;我们可以通过一些方式对MediaPlayer进行包装。而所用到的正是SurfaceView以及SurfaceHolder。 最终效果图&#xff1a; 我们提供了四个按钮&#xff…

ANativeWindow 和 Surface

Android播放视频从解码到显示实质也是BufferQueue的生产消费的过程&#xff0c;如下图所示&#xff1a; 其中生产者是Surface&#xff0c;消费者是SurfaceFlinger。 本文主要针对Surface进行分析&#xff0c;理清ANativeWindow 和 Surface之间的关系。 ANativeWindow的定义如…

Android之Surface 与 SurfaceFlinger关系

通过前面的知识我们知道了&#xff0c;Android 系统从按下开机键到桌面&#xff0c;从桌面点击 App 图标到 Activity 显示的过程。但是 Activity 是怎么显示在屏幕上的呢&#xff1f;下面我们就来讨论下这一过程。 SurfaceFlinger 启动过程 SurfaceFlinger 进程是由 init 进程…

SurfaceView、SurfaceHolder与Surface

相关文章 SurfaceView、SurfaceHolder与SurfaceTextureView、SurfaceTexture与Surface 按照官方文档的说法&#xff0c;SurfaceView继承自View&#xff0c;并提供了一个独立的绘图层&#xff0c;你可以完全控制这个绘图层&#xff0c;比如说设定它的大小&#xff0c;所以Surf…

surfaceView、surface和sufaceHolder的关系

surfaceView、surface和sufaceHolder的关系 1.SurfaceView与Surface的联系2.SurfaceView3.Surface4.SurfaceHolder 1.SurfaceView与Surface的联系 简单来说&#xff0c;Surface是管理显示内容的数据&#xff08;implementsParcelable&#xff09;&#xff0c;包括存储于数据的…

线程天敌TerminateThread与SuspendThread http://blog.csdn.net/magictong/article/details/6304439

线程天敌TerminateThread与SuspendThread 标签&#xff1a; thread null delete dll c user 2011-04-06 13:22 10295人阅读 评论(1) 收藏 举报 分类&#xff1a; C Win32&#xff08;93&#xff09; 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得…

511遇见易语言API模块线程挂起(SuspendThread)

线程挂起用到的API是SuspendThread 511遇见易语言模块API教程 API 暂停指定的线程 函数功能&#xff1a; 挂起线程。 参数&#xff1a; 第1个参数&#xff1a; HANDLE hThread 线程句柄。 返回值&#xff1a; 成功&#xff1a;线程的前一个挂起数。 失败&#xff1a;-1。 …

面向对象设计6大原则

概览 单一职责 1、单一职责 Single Responsibility Principle&#xff0c;简称是SRP。SRP的英文定义是&#xff1a; There should never be more than one reason for a class to change. 翻译过来的意思是&#xff1a; 应该有且仅有一个原因引起类的变更。 或许我们可以…

带你了解面向对象的设计原则

##一 摘要 今天晚上给大家介绍一下面向对象的设计原则,为什么要介绍这个呢,原因很简单,大家平时所接触的语言,无论是object-C,C,JavaScrpt,C#等都是属于面向对象语言,既然是面向对象设计语言,那么就有必要去了解一下面向对象的设计原则.那么面向对象的设计原则有哪些呢?今天来…