OpenGL学习进程(12)第九课:矩阵乘法实现3D变换

    本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈。

    (1)3D变换:

    OpenGL中绘制3D世界的空间变换包括:模型变换、视图变换、投影变换和视口变换。

    现实世界是一个3维空间,如果我们要观察一个物体,我们可以:

1.从不同的位置去观察它。(视图变换)
2.移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它。(模型变换)
3.如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(剪裁)。(投影变换)
4.我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部。(视口变换)

    实现原理:

    OpenGL变换实际上是通过矩阵乘法来实现。无论是移动、旋转还是缩放大小,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。

    1)模型变换和视图变换:

    从"相对移动"的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。因此这里视图变换和模型变换就一起讲,甚至在OpenGL中实现这两行总功能所用的函数都是相同的。

    如何进行模型或视图视图变换:

    1.1.1准备:

1.glMatrixMode(GL_MODELVIEW);//先设置当前操作的矩阵为“模型视图矩阵”;
2.glLoadIdentity();//通常需要把此矩阵设为单位矩阵;

    函数glMatrixMode(GLenum mode):

    mode告诉计算机哪一个矩阵堆栈将是下面矩阵操作的目标,即将什么矩阵设置为当前矩阵,它的可选值有:

1.GL_MODELVIEW:表示接下来的矩阵操作都是针对模型视景矩阵堆栈,直到下一次调用这个函数并更改参数为止。   
2.GL_PROJECTION:表示接下来的矩阵操作都是针对投影矩阵堆栈,直到下一次调用这个函数并更改参数为止。
3.GL_TEXTURE:表示接下来的矩阵操作都是针对纹理矩阵堆栈,直到下一次调用这个函数并更改参数为止。

    注意:在设置好当前矩阵之后,任何可以做的操作只能是针对当前矩阵的。

    函数glLoadIdentity()

    将当前的用户坐标系的原点移到了屏幕中心,类似于一个复位操作:(如果进行过空间变换后再次调用这个函数则视图回到初始状态)

1.X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
2.OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。 
3.中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。 移入屏幕深处是负值,移出屏幕则是正值。

    1.1.2变换:

    进行模型和视图变换,主要涉及到这几个函数:

模型变换函数三个:缩放glScalef(),移动glTranslatef(),旋转glRotatef().//模型变换的目的是设置模型的位置和方向
视图变换函数一个:观察gluLookAt().

    函数void glScalef()

    glScalef(2.0f,3.0f,4.0f):将模型按x,y,z方向分别拉伸了2,3,4倍。

    glScalef(1.0f,1.0f,-1.0f):将模型关于z轴翻转了180°(即关于xy轴所在平面对称)。

    函数glTranslatef()

    glTranslatef(-1.5f,0.0f,-6.0f):将模型向x轴负方向平移1.5,z轴负方向平移6.0。

    函数glRotatef()

    glRotatef(45,0.0f,0.0f,0.0f):将模型沿x轴旋转45°。

    函数gluLookAt()

void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,//相机在世界坐标的位置
               GLdouble centerx,GLdouble centery,GLdouble centerz,//相机镜头对准的物体在世界坐标的位置
               GLdouble upx,GLdouble upy,GLdouble upz);//相机向上的方向在世界坐标中的方向

    gluLookAt (0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0):相机在z轴上,歪45度(摄像机顶朝着(1.0,1.0,0.0)这个方向)看向原点。

    gluLookAt(0.0,0.0,5.0, 0.0,0.0,0.0, 0.0,1.0,0.0):相机在z轴上,摄像机顶朝向y轴(即上方),看向坐标原点。 

    示例:将3D物体变换后从不同位置观察:(glViewport()和glOrtho()http://www.cnblogs.com/yxnchinahlj/archive/2010/10/30/1865298.html)

#include <GL/glut.h>
#pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"")

void axis(double length)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPushMatrix();
    glBegin(GL_LINES);
    glVertex3d(0.0, 0.0, 0.0);
    glVertex3d(0.0, 0.0, length);
    glEnd();
    //将当前操作点移到指定位置
    glTranslated(0.0, 0.0, length - 0.2);
    glColor3f(1.0, 0.0, 0.0);
    glutWireCone(0.04, 0.3, 8, 8);
    glPopMatrix();
}
void SetupRC()
{
    glClearColor(0.5,0.5,0.5,1.0);//设置背景色为灰色
}
void paint(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glPointSize(1.0f);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-3.0, 3.0, -3.0, 3.0, -3, 3);
    //gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);//正视图
    gluLookAt(1, 1, 0.5, 0, 0, 0, 0, 0, 1);//斜视图
    //gluLookAt(0, 2, 0, 0, 0, 0, 1, 0, 0);//俯视

    //画坐标系
    axis(2.0);

    glPushMatrix();
    glRotated(90.0, 0, 1.0, 0);//绕y轴正方向旋转90度
    axis(2.0);
    glPopMatrix();

    glPushMatrix();
    glRotated(-90.0, 1.0, 0.0, 0.0);//绕x轴负方向旋转
    axis(2.0);
    glPopMatrix();

    glRotated(90, 0, 1, 0);
    glutWireTeapot(0.5);

    glFlush();
}
void changeSize(int width, int height)
{
    /*GLfloat mid = width > height ? height : width;
    glViewport(0, 0, mid, mid);*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (width <= height)
        glOrtho(-3, 3, -3 * (GLfloat)height/(GLfloat)width, 3 * (GLfloat)height/(GLfloat)width, -3, 3);
    else
        glOrtho(-3 * (GLfloat)width / (GLfloat)height, 3 * (GLfloat)width / (GLfloat)height, -3, 3, -3, 3);
}
int main(int argc,char *argv[])
{
    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(400,400);
    glutInitWindowPosition(200,200);
    glutCreateWindow("模型视图变换");
    SetupRC();
    glutReshapeFunc(changeSize);
    glutDisplayFunc(paint);
    glutMainLoop();
}

    遇到的问题:

    这三句话是一起的,如果只用第三句会出现"每在窗体点击一下,窗体中的物体就会在当前视图状态下发生改变"的情况。

glMatrixMode(GL_PROJECTION);
glLoadIdentity();//glLoadIdentity()函数设置为单位矩阵,阻止了连续变换产生的累积效果
glOrtho(-3.0, 3.0, -3.0, 3.0, -3, 3);

    防止当窗体变大时图形发生变形有两种解决方法:

GLfloat mid = width > height ? height : width;
glViewport(0, 0, mid, mid);
glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 if (width <= height)
    glOrtho(-3, 3, -3 * (GLfloat)height/(GLfloat)width, 3 *(GLfloat)height/(GLfloat)width, -3, 3);
 else
    glOrtho(-3 * (GLfloat)width / (GLfloat)height, 3 * (GLfloat)width / (GLfloat)height, -3, 3, -3, 3);

    2)投影变换:

    投影变换就是定义一个可视空间(视景体),可视空间以外的物体不会被绘制到屏幕上。投影矩阵指定了可视区域的大小和形状,在未使用投影变换时,默认可见坐标范围为(-1.0,1.0),使用后可以自定义在窗体显示的空间坐标范围。

    准备:

    要对当前矩阵进行投影变换,必须首先指定当前矩阵为投影矩阵并初始化单位矩阵:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

    OpenGL支持两种类型的投影变换,即透视投影和正投影:

    1.2.1正投影(Orthographic Projection):(示例如上)

    正投影通常用于2D绘图,它的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。

    此种模式下,不需要设定照相机位置、方向以及视点的位置,也就是说可以不需要gluLookAt函数。(此时默认是正投影)

    OpenGL正射投影函数共有两个:

    void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)

    它创建一个平行视景体。实际上这个函数的操作是创建一个正射投影矩阵,并且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。所有的near和far值同时为正或同时为负。如果没有其他变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。

    void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)

    它是一个特殊的正射投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,所有二维物体的Z坐标都为0.0。因此它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。

    实例如下:

#include <GL/glut.h>
#include <stdlib.h>
#pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"")

void display(void)
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glRectf(-0.9, 0.9, 0.9, -0.9);
    glFlush();
}
void init(void)
{
    glClearColor(0.5, 0.5, 0.5, 0.5);
    glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //仍然是原来1:1的比例,所以起不到任何作用
    gluOrtho2D(-(GLdouble)h / h, (GLdouble)h / h, -(GLdouble)h / h, (GLdouble)h / h);
}
void changeSize(int w, int h)
{  /*相当于按窗体的显示区域动态定义裁剪区域的大小
   然后填充到显示区域时图像比例并不发生变化。*/
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    float mid = w > h ? (w*1.0) / h : (h*1.0) / w;
    if (w<h)
        gluOrtho2D(-1.0, 1.0, -mid, mid);
    else
        gluOrtho2D(-mid, mid, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(400, 400);
    glutCreateWindow("2D正投影变换");
    init();
    glutDisplayFunc(display);
    //glutReshapeFunc(reshape);此语句中的reshape函数实际上起不到任何作用
    glutReshapeFunc(changeSize);
    glutMainLoop();
    return 0;
}

    1.2.2透视投影:

    透视投影符合人们心理习惯:即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被切除掉的棱椎,也就是棱台。这个投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。

    运用透视投影时需要用gluLookAt设定照相机位置、照相机方向(一般设置为(0,1,0))、以及视点位置。

    透视投影函数也有两个:

    void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far);

    它创建一个透视视景体。其操作是创建一个透视投影矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的Z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值。

    void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);

    它也创建一个对称透视视景体,但它的参数定义于前面的不同,如图所示。其操作是创建一个对称的透视投影矩阵,并且用这个矩阵乘以当前矩阵。参数fovy定义视野在X-Z平面的角度,范围是[0.0,180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和Far分别是远近裁剪面到眼睛的距离,它们总为正值。

  

     透视变换的实例:

#include <GL/glut.h>
#include <math.h>
#pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"")
//旋转的步进值
static float fMoonRot = 0.0f;
static float fEarthRot = 0.0f;

void SetupRC()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    //打开深度测试,一般运用在绘制3D场景中
    glEnable(GL_DEPTH_TEST);
}

// 绘制场景(显示回调函数)
void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 保存矩阵状态(模型视图矩阵)
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // 平移坐标系,注意是相对于视觉坐标的位置
    glTranslatef(0.0f, 0.0f, -300.0f);

    // 绘制红色的太阳
    glColor3f(1.0f, 0.0f, 0.0f);//等价于glColor3ub(255, 0, 0);
    glutSolidSphere(25.0f, 30, 30);

    // 旋转坐标系(此坐标系的中心是太阳)
    glRotatef(fEarthRot, 0.0f, 1.0f, 0.0f);
    // 绘制蓝色的地球
    glColor3ub(0, 0, 255);
    // 平移坐标系
    glTranslatef(100.0f, 0.0f, 0.0f);//表明太阳与地球的距离是100
    // 设置地球的旋转步进
    fEarthRot += 5.0f;
    if (fEarthRot > 360.0f) {
        fEarthRot = 0.0f;
    }
    glutSolidSphere(15.0f, 30, 30);

    // 绘制月球
    glColor3ub(200, 200, 255);
    // 旋转坐标系(此坐标系的中心是地球)
    glRotatef(fMoonRot, 0.0f, 1.0f, 0.0f);
    // 平移坐标系
    glTranslatef(30.0f, 0.0f, 0.0f);
    // 设置月亮的旋转步进
    fMoonRot += 20.0f;
    if (fMoonRot > 360.0f) {
        fMoonRot = 0.0f;
    }
    glutSolidSphere(6.0f, 30, 30);
    glPopMatrix();

    glutSwapBuffers();
}


void ChangeSize(GLsizei w, GLsizei h)//typdef int GLsizei 
{
    GLfloat fAspect;
    // 防止被0除
    if (0 == h) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    fAspect = (GLfloat)w / (GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 定义平截头体, 45度视野,近、远平面为1.0和425.0
    //gluPerspective(45.0f, fAspect, 100.0, 500);能达到相同的效果
    glFrustum(-100,100,-100,100,200,500);
}

// 计时器函数
void TimerFunc(int value)
{
    //每100毫秒调用一次函数
    glutPostRedisplay();
    glutTimerFunc(100, TimerFunc, 1);
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(200,200);
    glutInitWindowSize(600, 450);
    glutCreateWindow("透视测试");
    glutDisplayFunc(RenderScene);
    glutReshapeFunc(ChangeSize);
    // 设置计时器函数
    glutTimerFunc(100, TimerFunc, 1);
    SetupRC();
    glutMainLoop();

    return 0;
}

    遇到的问题:

    这里显示的是正面的透视视图,视平线与z轴重合,而我想要观察倾斜的俯视图,用学习进程(9)的方法(使用glulookat函数)并没有达到效果,而是用了下面的方法:

void ChangeSize(GLsizei w, GLsizei h)//typdef int GLsizei 
{
    GLfloat fAspect;
    // 防止被0除
    if (0 == h) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    fAspect = (GLfloat)w / (GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 定义平截头体, 45度视野,近、远平面为1.0和425.0
    gluLookAt(0,0.5,1,0,0,0,0,-1,0);
    gluPerspective(45.0f, fAspect, 100.0, 500);
}

    在学习进程(9)中用glulookat()函数改换视点时,是这样做的:(在本例中并没有任何效果)

void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0, 6.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

    两个程序的另一个不同点在于:绘制场景时,之前的程序每绘制一次物体就会有一次进出栈的操作,只有一个相对坐标系;而当前程序一次完成,每进行一次坐标系平移或旋转就换一个相对坐标系,效果是累积的。它解决了月亮绕地球转时不需考虑太阳的依赖关系。(详解见后文)

    glulookat()函数和透视变换函数的组合用法依然不清晰。

    3)视口变换:(示例:http://www.cnblogs.com/MenAngel/p/5630475.html)

    当一切工作已经就绪,只需要把像素绘制到屏幕上了。这时候还剩最后一个问题:应该把像素绘制到窗口的哪个区域呢?通常情况下,默认是完整的填充整个窗口,但我们完全可以只填充一半。(即:把整个图象填充到一半的窗口内)

    Viewport(int x, int y, int width, int height)

    OpenGL使用此函数来定义视口区域,它代表了即将绘制的图形显示在2D窗体的位置和大小。

    (2)操作矩阵堆栈:

    OpenGL中对图形的变换是基于矩阵操作的,有可能需要先保存某个矩阵,过一段时间再恢复它:

glPushMatrix()
glPopMatrix()

    OpenGL规定堆栈的容量至少可以容纳32个矩阵。

    为什么要使用矩阵堆栈:

    一般我们将需要执行的缩放、平移等操作放在glPushMatrix和glPopMatrix之间。glPushMatrix()和glPopMatrix()的配对使用可以消除上一次的变换对本次变换的影响,使本次变换是以世界坐标系的原点为参考点进行。

    矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。因为所有矩阵操作函数如glLoadMatrix()、glMultMatrix()、glLoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。

    原理详解:

1.OpenGL中模型视图矩阵变换是一个马尔科夫过程:上一次的变换结果对本次变换有影响,上次模型视图变换后物体在世界坐标系下的位置是本次模型变换的起点。默认时本次变换和上次变换不独立。
2.OpenGL物体建模实际上是分两步走的。第一步,在世界坐标系的原点位置绘制出该物体;第二步,通过模型视图变换矩阵对世界坐标系原点处的物体进行仿射变换,将该物体移动到世界坐标系的目标位置处。
3.将模型视图变换放在glPushMatrix和glPopMatrix之间可以使本次变换和上次变换独立。
4.凡是使用glPushMatrix()和glPopMatrix()的程序一般可以判定是采用世界坐标系建模。既世界坐标系固定,模型视图矩阵移动物体。

     示例:

#include<GL/glut.h>
#pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"")

static int spin = 0;
void init()
{
    glClearColor(0.3,0.3,0.3,1.0);
    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(); //1.保存矩阵状态
    //先画灯
    glTranslatef(0.0, 0.0, -5.0);
    glPushMatrix();//2.由于先后画的物体使用同一个世界坐标系,因此这里入栈消除本次操作对下次操作的影响 
    //将0号光源和矩形线框绘制在一起
    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(1.0, 0.0, 0.0);
    glLineWidth(2.0f);
    glutWireCube(0.1);
    glEnable(GL_LIGHTING);
    //再画被照物体
    glPopMatrix(); 
    //直接绘制在世界坐标的(0,0,0)位置
    glutSolidSphere(0.5, 40, 40);

    glPopMatrix(); //取出矩阵状态,返回到单位矩阵
    glFlush();
}
void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //gluLookAt(1.0, 0, 0, 0, 0, 0, 0, 1, 10);这句话取消注释后是左视图,俯瞰旋转图。
    gluPerspective(40.0, (GLfloat)w / (GLfloat)h, 3, 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("矩阵堆栈操作");
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMainLoop();
    return 0;
}

 

    (3)空间变换绘制实例:(空间变换矩阵堆栈综合应用,仅作了解)

#include <GL/glut.h>
#include <glaux.h>
#pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"")
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);
}
void main(void)
{
    auxInitDisplayMode(AUX_SINGLE | AUX_RGBA);
    auxInitPosition(200,200, 600, 400);
    auxInitWindow("机械手臂");
    myinit();
    auxKeyFunc(AUX_LEFT, shoulderSubtract);
    auxKeyFunc(AUX_RIGHT, shoulderAdd);
    auxKeyFunc(AUX_UP, elbowAdd);
    auxKeyFunc(AUX_DOWN, elbowSubtract);
    auxReshapeFunc(myReshape);
    auxMainLoop(display);
}

    (4)相关知识:

    1)CALLBACK标志:(#define CALLBACK _stdcall)

    用CALLBACK标志一个函数为持续回调函数。

    void CALLBACK changeSize(void)等价于void changeSize()。

    2)auxInitPosition(int,int,int,int):

    有初始化窗体的位置和大小两重功能:

auxInitPosition(200,200, 600, 400);
等价于
glutInitWindowPosition(200, 200);
glutInitWindowSize(600, 400);

    3)auxKeyFunc();

    向键盘按键上绑定函数指针,每一次按键操作都会引发此函数的一次回调。按键包括:

AUX_LEFT
AUX_RIGHT
AUX_UP
AUX_DOWN
原文地址:https://www.cnblogs.com/MenAngel/p/5799332.html