OpenGL基础图形编程(八)变换

news2024/11/18 13:48:33

八、OpenGL变换

  OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、矩阵堆栈等。学好了这章,才开始真正走进三维世界。

8.1、从三维空间到二维平面

   8.1.1 相机模拟

  在真实世界里,所有的物体都是三维的。但是,这些三维物体在计算机世界中却必须以二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面我们采用相机( Camera )模拟的方式来讲述这个概念,如图8-1所示。

  实际上,从三维空间到二维平面,就如同用相机拍照一样,通常都要经历以下几个步骤 (括号内表示的是相应的图形学概念):

  第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation)。

  第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation)。

  第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection Transformation)。

  第四步,决定二维像片的大小(视口变换,Viewport Transformation)。

  这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。

  8.1.2 三维图形显示流程

  运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达的三维图形显示流程。

  计算机本身只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。大家都知道,坐标建立了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系称为世界坐标系。

  计算机对数字化的显示物体作了加工处理后,要在图形显示器上显示,这就要在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大小可以是整型数。

  为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。投影的方法有两种,即正射投影和透视投影。

  有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视景体(Viewing Volume)。正射投影时一般是一个长方体的视景体,透视投影时一般是一个棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。在屏幕窗口内可以定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。

  为了适应物理设备坐标和视口所在坐标的差别,还要作一适应物理坐标的变换。这个坐标系称为物理设备坐标系。根据上面所述,三维图形的显示流程应如图8-2所示。

8.1.3 基本变换简单分析  下面举一个简单的变换例子,cube.c:

例8-4 简单变换例程(cube.c)

#include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>
 
  void myinit(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);
  void CALLBACK display(void);
 
  void CALLBACK display (void)
  {
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f (1.0, 1.0, 1.0);
    glLoadIdentity (); /* clear the matrix */
    glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */
    glScalef (1.0, 2.0, 1.0); /* modeling transformation */
    auxWireCube(1.0); /* draw the cube */
    glFlush();
  }
 
  void myinit (void)
  {
    glShadeModel (GL_FLAT);
  }
 
  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glMatrixMode (GL_PROJECTION); /* prepare for and then */
    glLoadIdentity (); /* define the projection */
    glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */
    glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */
    glViewport (0, 0, w, h); /* define the viewport */
  }
 
  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Perspective 3-D Cube");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

 以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。

  下面简单分析一下整个程序过程:

  1)视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图8-4所示。当矩阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。

  通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。

  这里相机移走后,仍然对准立方体。如果相机需要指向另一方向,则调用glRotatef()可以改变。

2) 模型变换 。模型变换是在世界坐标系中进行的。在这个坐标系中,可以对物体实施平移 glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例变换,glScalef(sx, sy, sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。

  3) 投影变换 。投影变换类似于选择相机的镜头。本例中调用了一个透视投影函数 glFrustum(),在调用它之前先要用glMatrixMode()说明当前矩阵方式是投影GL_PROJECTION。这个投影函数一共有六个参数,由它们可以定义一个棱台似的视景体。即视景体内的部分可见,视景体外的部分不可见,这也就包含了三维裁剪变换。

  4) 视口变换 。视口变换就是将视景体内投影的物体显示在二维的视口平面上。通常,都调用函数glViewport()来定义一个视口,这个过程类似于将照片放大或缩小。

  总而言之,一旦所有必要的变换矩阵被指定后,场景中物体的每一个顶点都要按照被指定的变换矩阵序列逐一进行变换。注意:OpenGL 中的物体坐标一律采用齐次坐标,即(x, y, z, w),故所有变换矩阵都采用4X4矩阵。一般说来,每个顶点先要经过视点变换和模型变换,然后进行指定的投影,如果它位于视景体外,则被裁剪掉。最后,余下的已经变换过的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样才能显示在屏幕上。

8.2、几何变换

  实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。

  只是视点变换一般只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系中的物体就相当于在世界坐标系中作反方向的平移或旋转。因此,从某种意义上讲,二者可以统一,只是各自出发点不一样而已。读者可以根据具体情况,选择其中一个角度去考虑,这样便于理解。

   8.2.1 两个矩阵函数解释

  这里先解释两个基本OpenGL矩阵操作函数,便于以后章节的讲述。函数解释如下:

void glLoadMatrix{fd}(const TYPE *m)

  设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0, m1, ..., m15)的指针,这16个元素就是当前矩阵M中的元素,其排列方式如下:

M = | m0 m4 m8 m12 |
        | m1 m5 m9 m13 |
        | m2 m6 m10 m14 |
        | m3 m7 m11 M15 |
 
void glMultMatrix{fd}(const TYPE *m)

 用当前矩阵去乘m所指定的矩阵,并将结果存放于m中。当前矩阵可以是用glLoadMatrix() 指定的矩阵,也可以是其它矩阵变换函数的综合结果。

  当几何变换时,调用OpenGL的三个变换函数glTranslate()、glRotate()和glScale*(),实质上相当于产生了一个近似的平移、旋转和比例矩阵,然后调用glMultMatrix()与当前矩阵相乘。但是直接调用这三个函数程序运行得快一些,因OpenGL自动能计算矩阵。

   8.2.2 平移

  平移变换函数如下:  

void glTranslate{fd}(TYPE x,TYPE y,TYPE z)

  三个函数参数就是目标分别沿三个轴向平移的偏移量。这个函数表示用这三个偏移量生成的矩阵乘以当前矩阵。当参数是(0.0,0.0,0.0)时,表示对函数glTranslate*()的操作是单位矩阵,也就是对物体没有影响。平移示意如图8-5所示。

  8.2.3 旋转

  旋转变换函数如下:  

void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)

  函数中第一个参数是表示目标沿从点(x, y, z)到原点的方向逆时针旋转的角度,后三个参数是旋转的方向点坐标。这个函数表示用这四个参数生成的矩阵乘以当前矩阵。当角度参数是0.0时,表示对物体没有影响。旋转示意如图8-6所示。

  8.2.3 缩放和反射

  缩放和反射变换函数如下: 

void glScale{fd}(TYPE x,TYPE y,TYPE z)

  三个函数参数值就是目标分别沿三个轴向缩放的比例因子。这个函数表示用这三个比例因子生成的矩阵乘以当前矩阵。这个函数能完成沿相应的轴对目标进行拉伸、压缩和反射三项功能。当参数是(1.0, 1.0, 1.0)时,表示对函数glScale*()操作是单位矩阵,也就是对物体没有影响。当其中某个参数为负值时,表示将对目标进行相应轴的反射变换,且这个参数不为1.0,则还要进行相应轴的缩放变换。最好不要令三个参数值都为零,这将导致目标沿三轴都缩为零。缩放和反射示意如图8-7所示。

8.2.5 几何变换举例  以上介绍了三个基本几何变换函数,下面举一个简单的例子进一步说明它们的用法。程序如下:

例 8-5 几何变换例程(geomtrsf.c)

#include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>
 
  void myinit(void);
  void draw_triangle(void);
  void CALLBACK display(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);
 
  void draw_triangle(void)
  {
    glBegin(GL_LINE_LOOP);
      glVertex2f(0.0, 25.0);
      glVertex2f(25.0, -25.0);
      glVertex2f(-25.0, -25.0);
    glEnd();
  }
 
  void CALLBACK display(void)
  {
    glClearColor (0.0, 0.0, 0.0, 1.0);
    glClear (GL_COLOR_BUFFER_BIT); 
 
    /* draw an original triangle */
    glLoadIdentity ();
    glColor3f (1.0, 1.0, 1.0);  /* white */
    draw_triangle ();
 
    /* translating a triangle along X_axis */
    glLoadIdentity ();
    glTranslatef (-20.0, 0.0, 0.0);
    glColor3f(1.0,0.0,0.0);   /* red */
    draw_triangle ();
 
    /* scaling a triangle along X_axis by 1.5 and along Y_axis by 0.5 */
    glLoadIdentity();
    glScalef (1.5, 0.5, 1.0);
    glColor3f(0.0,1.0,0.0);   /* green */
    draw_triangle ();
 
    /* rotating a triangle in a counterclockwise direction about Z_axis */
    glLoadIdentity ();
    glRotatef (90.0, 0.0, 0.0, 1.0);
    glColor3f(0.0,0.0,1.0);   /* blue */
    draw_triangle ();
 
    /* scaling a triangle along Y_axis and reflecting it about Y_axis */
    glLoadIdentity();
    glScalef (1.0, -0.5, 1.0);
    glColor3f(1.0,1.0,0.0);   /* yellow */
    draw_triangle ();
 
    glFlush();
  }
 
  void myinit (void)
  {
    glShadeModel (GL_FLAT);
  }
 
  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
      glOrtho(-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w,-1.0,1.0); 
    else
      glOrtho(-50.0*(GLfloat)w/(GLfloat)h, 50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0,-1.0,1.0); 
    glMatrixMode(GL_MODELVIEW);
  }
 
  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Geometric Transformations");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

  以上程序运行结果:第一个白色三角形是原始三角形,第二个红色三角形是白三角沿X 负轴平移后的三角形,第三个绿色三角形是白三角分别沿X轴和Y轴比例变换后的三角形,第四个蓝色三角形是白三角绕Z正轴逆时针转90度后的三角形,第五个黄色三角形是白三角沿Y轴方向缩小一倍且相对于X轴作反射后形成的三角形。

8.3、投影变换

  投影变换是一种很关键的图形变换,OpenGL中只提供了两种投影方式,一种是正射投影,另一种是透视投影。不管是调用哪种投影函数,为了避免不必要的变换,其前面必须加上以下两句:

glMAtrixMode(GL_PROJECTION);
glLoadIdentity();

  事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分裁剪掉,最终图像只是视景体内的有关部分。本指南将详细讲述投影变换的概念以及用法。

  8.3.1 正射投影(Orthographic Projection)

  正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图8-9所示。正射投影的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。

  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 glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far);

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

   8.3.2 透视投影(Perspective Projection)

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

  OpenGL透视投影函数也有两个,其中函数glFrustum()在8.1.3节中提到过,它所形成的视景体如图8-10所示。

  这个函数原型为:  

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);

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

  以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。二者的应用实例将在后续章节中介绍。

8.4、裁剪变换

  在OpenGL中,空间物体的三维裁剪变换包括两个部分:视景体裁剪和附加平面裁剪。视景体裁剪已经包含在投影变换里,前面已述,这里不再重复。下面简单讲一下平面裁剪函数的用法。

  除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可自己再定义一个或多个附加裁剪平面,以去掉场景中无关的目标,如图8-12所示。

  附加平面裁剪函数为:

 void glClipPlane(GLenum plane,Const GLdouble *equation);

  函数定义一个附加的裁剪平面。其中参数equation指向一个拥有四个系数值的数组,这四个系数分别是裁剪平面Ax+By+Cz+D=0的A、B、 C、D值。因此,由这四个系数就能确定一个裁剪平面。参数plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面号。

  在调用附加裁剪函数之前,必须先启动glEnable(GL_CLIP_PLANEi),使得当前所定义的裁剪平面有效;当不再调用某个附加裁剪平面时,可用glDisable(GL_CLIP_PLANEi)关闭相应的附加裁剪功能。

  下面这个例子不仅说明了附加裁剪函数的用法,而且调用了gluPerspective()透视投影函数,读者可以细细体会其中的用法。例程如下:

   例8-6 裁剪变换例程 ( clipball.c)

#include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>
 
  void myinit(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);
  void CALLBACK display(void);
 
  void CALLBACK display(void)
  {
    GLdouble eqn[4] = {1.0, 0.0, 0.0, 0.0};
 
    glClear(GL_COLOR_BUFFER_BIT);
 
    glColor3f (1.0, 0.0, 1.0);
    glPushMatrix();
    glTranslatef (0.0, 0.0, -5.0);
 
    /* clip the left part of wire_sphere : x<0 */
    glClipPlane (GL_CLIP_PLANE0, eqn);
    glEnable (GL_CLIP_PLANE0);
    glRotatef (-90.0, 1.0, 0.0, 0.0);
    auxWireSphere(1.0);
    glPopMatrix();
    glFlush();
  }
 
  void myinit (void)
  {
    glShadeModel (GL_FLAT);
  }
 
  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
  }
 
  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Arbitrary Clipping Planes");
    myinit ();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

8.5、视口变换

  在前面几节内容中已相继提到过视口变换,这一节将针对OpenGL来讲述视口变换的原理及其相关函数的用法。运用相机模拟方式,我们很容易理解视口变换就是类似于照片的放大与缩小。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。OpenGL中相关函数是:   

glViewport(GLint x,GLint y,GLsizei width, GLsizei height);

  这个函数定义一个视口。函数参数(x, y)是视口在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视口的宽度和高度。缺省时,参数值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的实际尺寸大小。所有这些值都是以象素为单位,全为整型数。

   注意 :在实际应用中,视口的长宽比率总是等于视景体裁剪面的长宽比率。如果两个比率不相等,那么投影后的图像显示于视口内时会发生变形,如图8-14所示。另外,屏幕窗口的改变一般不明显影响视口的大小。因此,在调用这个函数时,最好实时检测窗口尺寸,及时修正视口的大小,保证视口内的图像能随窗口的变化而变化,且不变形。

8.6 矩阵堆栈

  学过计算机的人也许都知道这个使用频率极高的名词 — “堆栈”。顾名思义,堆栈指的是一个顶部打开底部封闭的柱状物体,通常用来存放常用的东西。这些东西从顶部依次放入,但取出时也只能从顶部取出,即“先进后出,后进先出”。在计算机中,它常指在内存中开辟的一块存放某些变量的连续区域。因此,OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。

  实际上,在创建、装入、相乘模型变换和投影变换矩阵时,都已用到堆栈操作。一般说来,矩阵堆栈常用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。例如,一辆自行车就是由两个轮子、一个三角架及其它一些零部件构成的。它的继承性表现在当自行车往前走时,首先是前轮旋转,然后整个车身向前平移,接着是后轮旋转,然后整个车身向前平移,如此进行下去,这样自行车就往前走了。

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

void glPushMatrix(void);
void glPopMatrix(void);

 第一个函数表示将所有矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,否则出错。第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则出错。由此看出,矩阵堆栈操作与压入矩阵的顺序刚好相反,编程时要特别注意矩阵操作的顺序。

  为了更好地理解这两个函数,我们可以形象地认为glPushMatrix()就是“记住自己在哪”,glPopMatrix()就是“返回自己原来所在地”。请看下面一例:

   例8-7 堆栈操作例程 ( arm.c )  

#include "glos.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();
    glTranslatef (-0.5, 0.0, 0.0);
    glRotatef ((GLfloat)
    shoulder, 0.0, 0.0, 1.0);
    glTranslatef (1.0, 0.0, 0.0);
    auxWireBox(2.0, 0.2, 0.5);
 
    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();
    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 */
  }
 
  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);
  }

 从以上例程可以看出,复杂的机械手臂是由两个简单的长方体依据一定的继承关系构成的,而这个继承关系是由矩阵堆栈的顺序决定的。

原文链接:https://blog.csdn.net/heyuchang666/article/details/51122659

Android NDK Andoird音视频开发必备手册-372页.pdf

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/72628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于多时间尺度滚动优化的多能源微网双层调度研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

网络套接字编程(TCP协议)

文章目录简单的TCP网络程序服务器绑定服务端监听服务端获取连接客户端连接服务器多线程版本的大小写字母转换服务简单的TCP网络程序 int socket(int domain, int type, int protocol);参数说明&#xff1a; domain&#xff1a;创建套接字的域或者叫做协议家族&#xff0c;也就…

百万基建狂魔们的赛博世界

钉钉完成的&#xff0c;是基于PaaS底座和底层基础产品&#xff0c;与生态伙伴一起提供低代码的普惠化定制开发模式&#xff0c;让大型企业自己可以具备诊断自己的能力和梳理流程的能力&#xff0c;并且将过往的经验和积累进行数字化应用层面的表达&#xff0c;进而寻找出一条最…

ffplay调试环境搭建

前言 ffplay是基于FFmpeg的最简单的官方播放器。麻雀虽小&#xff0c;五脏俱全&#xff0c;虽说ffplay简单&#xff0c;但是各种播放器应有的功能一一俱全&#xff0c;说它简单或许仅仅是因为它只有一个点c文件而已吧。 想要开发一个优秀的播放器&#xff0c;参考是必不可少的&…

Netron可视化Pytorch保存的网络模型

目录 一.理清网络的输入与输出 二. 将模型转换为onnx格式 三.Netron可视化工具 一.理清网络的输入与输出 我自定义的网络模型&#xff08;主要看看前向传播函数即可&#xff09;&#xff1a; import torch import torch.nn as nn#导入数据预处理之后的相关数据 from dataP…

Acrel-EMS企业微电网能效管理平台在某食品加工厂35kV变电站应用-Susie 周

1、概述 该食品加工厂变电站工程规模&#xff1a;电压等级&#xff1a;35/10.5kV&#xff0c;规划主变容量16.3MVA1台8MVA。有一个总配电室&#xff0c;包括35kV开关柜、10kV开关柜和0.4kV配电柜&#xff0c;两个独立变压器室&#xff0c;变压器为干式变压器。35kV供电系统采用…

(2)ITK中迭代器的时间效率

背景 ITK对图像处理中&#xff0c;为了提高代码运行效率&#xff0c;通过迭代器Iterator可以实现对时间的优化。 在ITK的官方文档中也有明确的说明&#xff1a; 针对此说明&#xff0c;本次使用对图像获取最大值最小值的方式&#xff0c;来实验和测试其效率。 代码实现 &am…

JDBC 数据库连接池之Driud

1 数据库连接池简介 数据库连接池是个容器&#xff0c;负责分配、管理数据库连接(Connection) 它允许应用程序重复使用一个现有的数据库连接&#xff0c;而不是再重新建立一个&#xff1b; 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据…

数据安全新战场,EasyMR为企业筑起“安全防线”

2020年1月&#xff0c;时间跨度长达14年的&#xff0c;微软2.5亿条客户服务和支持记录在网上泄露&#xff1b; 同年4月&#xff0c;微盟发生史上最贵“删库跑路”事件&#xff0c;造成微盟市值一夜之间缩水约24亿港币&#xff1b; 今年7月&#xff0c;网信办依据《数据安全法…

PCIEBPMCx4板卡

PCIEBPMCx4本板卡可以使标准的PMC板卡安装于带有PCIE插槽的PC机上使用&#xff0c;安装后占一个槽位&#xff0c;槽位可以为PCIE x4 PCIE x8、PCIE x16&#xff0c;安装后工作在PCIE x4模式。PCIE X1 后开口也可以使用&#xff0c;但只运行在PCIE X1模式。PCIE支持X4 V2.0,板载…

Python对json的操作总结

Json简介&#xff1a;Json&#xff0c;全名 JavaScript Object Notation&#xff0c;是一种轻量级的数据交换格式。Json最广泛的应用是作为AJAX中web服务器和客户端的通讯的数据格式。现在也常用于http请求中&#xff0c;所以对json的各种学习&#xff0c;是自然而然的事情。 J…

C++学习笔记(十四)——vector的模拟实现

vector各函数接口总览 vector当中的成员变量介绍 默认成员函数 构造函数1 构造函数2 构造函数3 拷贝构造函数 赋值运算符重载函数 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size和capacity reserve resize empty 修改容器内容相关函数 push_ba…

centos8:安装java

一、背景 因为centos 8 安装Jenkins需要java环境&#xff0c;所以本文记录安装java环境过程。 二、环境 开发电脑&#xff1a;Windows 10 CentOS 8.4 64位 三、安装 3.1、java -version检查是否已安装 java -version 没有安装 3.2、检查系统是否自带jdk rpm -qa |grep …

Word控件Spire.Doc 【超链接】教程(1):如何在C#/VB.NET中给Word 文档插入超链接

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

系统移植 uboot 2

一、uboot源码获取 1.1 uboot官网获取 ftp://ftp.denx.de/pub/u-boot/ 前提是是芯片厂家将uboot源码开源到uboot官网上 1.2 ST开发社区获取 https://wiki.stmicroelectronics.cn/stm32mpu/wiki/STM32MP1_Developer_Package 1.3 ST官网 https://www.st.com/en/embedded-sof…

opcj3—人人开源三大套件的简单用法

renren开源是一个很不错的开源开发组件&#xff0c;人人开源 其中目前对我们最有用的有三个&#xff1a;renren-fast、renren-fast-vue和renren-generator。 renren-generator是核心服务&#xff0c;可以根据数据库自动生成从controller层到service层&#xff0c;再到持久层的…

.net开发安卓入门 - 环境安装

文章目录工具VS2022Android SDK Manager如下图&#xff0c;安装一个镜像和工具模拟器设备管理器如下图启动模拟器&#xff0c;看一下效果常见问题工具 VS2022 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?skuCom…

Linux邮件服务Postfix部署

我们看下邮件协议&#xff1a; 简单邮件传输协议&#xff08;SMTP&#xff09;&#xff1a;用于发送和中转出的电子邮件。使用TCP/25端口。 邮局协议版本&#xff08;POP3&#xff09;&#xff1a;用于将邮件存储到本地&#xff0c;占用服务器的TCP/110端口。 Internet 消息访问…

【Python游戏】一个csdn小编用Python语言写了一个足球游戏,成功模拟世界杯决赛现场

前言 halo&#xff0c;包子们下午好 最近世界杯不是很火呀 很多小伙伴应该都知道球赛反正买&#xff0c;别墅靠大海&#xff01; 今天就给大家实现一个类似世界杯的足球小游戏&#xff0c;咱就说真的堪比国足了&#xff01; 哈哈哈~ 好啦 直接开整&#xff01;&#xff01;&am…

「以代码作画」从数据角度剖析Art Blocks生成艺术

作者&#xff1a;Mia Bao, co-founder of thepass.to, chief partner of WHALE members 数据&#xff1a;Jin, data analyst of thepass.to 出品&#xff1a;ThePASS & BeepCrypto 文章数据&#xff1a;https://docs.google.com/spreadsheets/d/1zDun4eUTwA-BMU5Hl2c5EC…