在OpenGL中,大多数复杂模型都是通过简单的部件拼接而成。
比如一辆汽车由四个轮胎和车身组成,电脑由键盘屏幕和机身组成。
下面举得这个栗子,机械手臂,是一个Articulated Model的实例。
每一个部分在节点处相连接,可以通过制定各个节点的角度来指定模型的状态。
从图中可以看出,它大概是由三个部分组成:
底座,下臂,上臂。
每一个部件在各自坐标系中的自由度如下:
底座单独的进行旋转。
单独的旋转角度决定了坐标。
下臂链接到底座上。
它的坐标取决于底座的旋转。
必须也相对底座进行移动并且在连接点进行旋转。
上臂与下臂直接相连。
它的坐标由上臂和底座共同决定。
必须相对下臂进行移动并且在与下臂的连接点处进行旋转。
下面来列举构建这样一个机械手臂所需要的矩阵:
1.首先是底座的旋转:Rb
将M=Rb应用到底座上。
2.相对于底座移动下臂:Tlu
将下臂绕连接点旋转:Rlu
将M=Rb Tlu Rlu应用到下臂上。
3.将上臂相对下臂移动:Tuu
将上臂绕连接点旋转:Ruu
将M = Rb Tlu Rlu Tuu Ruu应用到上臂上。
机械手的简单源码如下:
#include <gl/glut.h> #include <iostream> using namespace std; static int theta = 0; static int phi = 0; static int psi = 0; static int h1 = 1; static int h2 = 2; void base() { glPushMatrix(); glTranslatef(0.0,0.5,0.0); glScalef(1.0,0.5,1.0); glutWireCube(1.0); glPopMatrix(); } void lower_arm() { glPushMatrix(); glTranslatef(0.0,1.0,0.0); glScalef(0.4,2.0,0.4); glutWireCube(1.0); glPopMatrix(); } void upper_arm() { glPushMatrix(); glTranslatef(0.0,1.0,0.0); glScalef(0.4,2.0,0.4); glutWireCube(1.0); glPopMatrix(); } void init(void) { glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT); } void display(void) { glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); glRotatef(theta, 0.0, 1.0, 0.0); base(); glTranslatef(0.0, h1, 0.0); glRotatef(phi, 0.0, 0.0, 1.0); lower_arm(); glTranslatef(0.0, h2, 0.0); glRotatef(psi, 0.0, 0.0, 1.0); upper_arm(); glPopMatrix(); glutSwapBuffers(); } void reshape(int w,int h) { glViewport(0,0,(GLsizei)w,(GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(65.0,(GLfloat)w/(GLfloat)h,1.0,20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0,-3.0,-5.0); } void keyboard(unsigned char key,int x,int y) { switch(key) { case 'q': theta=(theta+5)%360; glutPostRedisplay(); break; case 'Q': theta=(theta-5)%360; glutPostRedisplay(); break; case 'w': phi=(phi+5)%360; glutPostRedisplay(); break; case 'W': phi=(phi-5)%360; glutPostRedisplay(); break; case 'e': psi=(psi+5)%360; glutPostRedisplay(); break; case 'E': psi=(psi-5)%360; glutPostRedisplay(); break; default: break; } } int main(int argc,char**argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB); glutInitWindowPosition(100,10); glutInitWindowSize(1000,600); glutCreateWindow(argv[0]); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; }
下面贴上一段老师考卷上使用的源码仅供参考:
#include <gl/glut.h> #include <stdlib.h> #define BASE_HEIGHT 0.2 #define BASE_WIDTH 0.2 #define LOWER_ARM_HEIGHT 0.5 #define LOWER_ARM_WIDTH 0.05 #define UPPER_ARM_HEIGHT 0.5 #define UPPER_ARM_WIDTH 0.05 void base() { glPushMatrix(); glTranslatef(0.0,0.5*BASE_HEIGHT,0.0); glScalef(BASE_WIDTH,BASE_HEIGHT,BASE_WIDTH); glutSolidCube(1.0); glPopMatrix(); } void upper_arm() { glPushMatrix(); glTranslatef(0.0,0.5*UPPER_ARM_HEIGHT,0.0); glScalef(UPPER_ARM_WIDTH,UPPER_ARM_HEIGHT,UPPER_ARM_WIDTH); glutSolidCube(1.0); glPopMatrix(); } void lower_arm() { glPushMatrix(); glTranslatef(0.0,0.5*LOWER_ARM_HEIGHT,0.0); glScalef(LOWER_ARM_WIDTH,LOWER_ARM_HEIGHT,LOWER_ARM_WIDTH); glutSolidCube(1.0); glPopMatrix(); } void display(void) { GLfloat theta[]={0.0,45.0,45.0}; glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glColor3f(1.0,0.0,0.0); glRotatef(theta[0],0.0,1.0,0.0); base(); glColor3f(0.0,1.0,0.0); glTranslatef(0.0,BASE_HEIGHT,0.0); glRotatef(theta[1],0.0,0.0,1.0); lower_arm(); glColor3f(0.0,0.0,1.0); glTranslatef(0.0,LOWER_ARM_HEIGHT,0.0); glRotatef(theta[2],0.0,0.0,1.0); upper_arm(); glFlush(); glutSwapBuffers(); } void main(int argc,char**argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH); glutInitWindowSize(500,500); glutInitWindowPosition(100,100); glutCreateWindow("Robot"); glEnable(GL_DEPTH_TEST); glClearColor(1.0,1.0,1.0,1.0); glutDisplayFunc(display); glutMainLoop(); }
很多同学对与glPushMatrix和glPopMatrix的使用还是不是很清楚,在这里简单的说明一下。
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. 代码举举栗:光源位置的移动
移动方式讲解: 先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中的模型视图变换矩阵全是右乘当前变换矩阵 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; }
对了,刚好在这里给大家演示一下GL_FLAT和GL_SMOOTH的区别。
GL_FLAT单调着色:对点,直线或多边形采用一种颜色进行绘制,整个图元的颜色就是它的任何一点的颜色。
GL_SMOOTH平滑着色:用多种颜色进行绘制,每个顶点都是单独进行处理的,各顶点和各图元之间采用均匀插值。