在之前的博客中,我就说过后面会详细讲解这两个函数。今天让我们来认识下它们(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();
}