程序员爱装B

写装A程序 做装C的事情

[搬家文] 第二课 变换与时钟

原文地址:http://www.videotutorialsrock.com/opengl_tutorial/transform/home.php

视频下载:http://www.videotutorialsrock.com/opengl_tutorial/transform/video.flv

文本课件:http://www.videotutorialsrock.com/opengl_tutorial/transform/text.php

代码下载:http://www.videotutorialsrock.com/opengl_tutorial/transform/transform.zip

译文:

变换

    我们上一个程序有点单调。这是在编写3D程序么?看上去和2D一样!让我们搞点有趣的,我们想法让形状在3维中旋转。

    首先来了解一点OpenGL关于变换的知识。为了方便说明,想象一直围绕场景转到小鸟。一开始它是在原点,面对着z轴的负半轴。这个小鸟可以移动,旋转,甚至可以放大或缩小。无论什么时候在OpenGL中使用glVertex函数,OpenGL将其解释为和小鸟相关联的。因此,如果我们想从远景的角度看,将小鸟缩小2倍并向右边移动2个单位,那么相对于小鸟坐标为(0,4,0)的点实际上在(1,2,0)上。而如果我们将小鸟关于X轴旋转90度,并向前(向屏幕里面,在屏幕上看上去就是向前移动了。)移动2个单位,相对鸟坐标为(0,0,-1)的点在整个坐标系中坐标为(0,-1,-2)。这个用下面的图片所示。注意为了观察方便,我们是从旁边进行观察的。


bird

    说到这,你可能会想,为什么不直接指定确定的点呢?这个在本课结束时就知道原因了。

    我们沿用上节课的代码,只是删除了一些注释。首先,我们通过将我们的小鸟向前变换5个单位,并使用0作为z轴的值来代替所有点z轴坐标为-5。我们调用glTranslatef函数进行变换,我们将在x,y,z轴进行变换。

    glLoadIdentity(); //Reset the drawing perspective
glTranslatef(0.0f, 0.0f, -5.0f); //Move forward 5 units

glBegin(GL_QUADS);

//Trapezoid
glVertex3f(-0.7f, -1.5f, 0.0f);
glVertex3f(0.7f, -1.5f, 0.0f);
glVertex3f(0.4f, -0.5f, 0.0f);
glVertex3f(-0.4f, -0.5f, 0.0f);

glEnd();

glBegin(GL_TRIANGLES);

//Pentagon
glVertex3f(0.5f, 0.5f, 0.0f);
glVertex3f(1.5f, 0.5f, 0.0f);
glVertex3f(0.5f, 1.0f, 0.0f);

glVertex3f(0.5f, 1.0f, 0.0f);
glVertex3f(1.5f, 0.5f, 0.0f);
glVertex3f(1.5f, 1.0f, 0.0f);

glVertex3f(0.5f, 1.0f, 0.0f);
glVertex3f(1.5f, 1.0f, 0.0f);
glVertex3f(1.0f, 1.5f, 0.0f);

//Triangle
glVertex3f(-0.5f, 0.5f, 0.0f);
glVertex3f(-1.0f, 1.5f, 0.0f);
glVertex3f(-1.5f, 0.5f, 0.0f);

glEnd();

    保存修改文件,编译并运行得到同前面一样的图形,这也是我们想要得到的。

    现在要对上节课的glLoadIdentity()函数做些解释。这个函数的功能是重置我们的小鸟,使得开始位置在坐标原点并且面对z的负半轴。

    现在进行更多的变换,无论我们指定某个物体的某一点,都是相对于物体中心点而言的。

    glLoadIdentity(); //Reset the drawing perspective
glTranslatef(0.0f, 0.0f, -5.0f); //Move forward 5 units

glPushMatrix(); //Save the transformations performed thus far
glTranslatef(0.0f, -1.0f, 0.0f); //Move to the center of the trapezoid

glBegin(GL_QUADS);

//Trapezoid
glVertex3f(-0.7f, -0.5f, 0.0f);
glVertex3f(0.7f, -0.5f, 0.0f);
glVertex3f(0.4f, 0.5f, 0.0f);
glVertex3f(-0.4f, 0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the trapezoid
glPushMatrix(); //Save the current state of transformations
glTranslatef(1.0f, 1.0f, 0.0f); //Move to the center of the pentagon

glBegin(GL_TRIANGLES);

//Pentagon
glVertex3f(-0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(-0.5f, 0.0f, 0.0f);

glVertex3f(-0.5f, 0.0f, 0.0f);
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, 0.0f, 0.0f);

glVertex3f(-0.5f, 0.0f, 0.0f);
glVertex3f(0.5f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the pentagon
glPushMatrix(); //Save the current state of transformations
glTranslatef(-1.0f, 1.0f, 0.0f); //Move to the center of the triangle

glBegin(GL_TRIANGLES);

//Triangle
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(0.0f, 0.5f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the triangle

    同样,保存并运行,得到和以前一样的图形。

    这里有两个重要的函数:glPushMatrix()和glPopMatrix(),用来存储我们小鸟的状态。glPushMatrix()保存它的状态而glPopMatrix()还原到这个状态。注意和glBegin,glEnd一样,glPushMatrix和glPopMatrix必须匹配。为了撤销将物体向中间移动的状态必须先使用glPushMatrix。

    每次可以保存多个状态,实际上是使用的一个堆栈保存状态的。每次我们调用glPushMatrix,就在堆栈顶部增加一个状态,每次调用glPopMatrix就移除一个状态。这个堆栈可以保存至少32种变换状态。

    glPushMatrix和glPopMatrix的名字是由于OpenGL通过矩阵来描述物体的状态的。目前来说你无需关心这个矩阵到底是如何工作的。

    好了,现在我们实际上已经改变了我们程序。现在我们将所有的物体旋转30度并缩小70%。

float _angle = 30.0f;

//Draws the 3D scene
void drawScene() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_MODELVIEW); //Switch to the drawing perspective
glLoadIdentity(); //Reset the drawing perspective
glTranslatef(0.0f, 0.0f, -5.0f); //Move forward 5 units

glPushMatrix(); //Save the transformations performed thus far
glTranslatef(0.0f, -1.0f, 0.0f); //Move to the center of the trapezoid
glRotatef(_angle, 0.0f, 0.0f, 1.0f); //Rotate about the z-axis

glBegin(GL_QUADS);

//Trapezoid
glVertex3f(-0.7f, -0.5f, 0.0f);
glVertex3f(0.7f, -0.5f, 0.0f);
glVertex3f(0.4f, 0.5f, 0.0f);
glVertex3f(-0.4f, 0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the trapezoid
glPushMatrix(); //Save the current state of transformations
glTranslatef(1.0f, 1.0f, 0.0f); //Move to the center of the pentagon
glRotatef(_angle, 0.0f, 1.0f, 0.0f); //Rotate about the y-axis
glScalef(0.7f, 0.7f, 0.7f); //Scale by 0.7 in the x, y, and z directions

glBegin(GL_TRIANGLES);

//Pentagon
glVertex3f(-0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(-0.5f, 0.0f, 0.0f);

glVertex3f(-0.5f, 0.0f, 0.0f);
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(0.5f, 0.0f, 0.0f);

glVertex3f(-0.5f, 0.0f, 0.0f);
glVertex3f(0.5f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the pentagon
glPushMatrix(); //Save the current state of transformations
glTranslatef(-1.0f, 1.0f, 0.0f); //Move to the center of the triangle
glRotatef(_angle, 1.0f, 2.0f, 3.0f); //Rotate about the the vector (1, 2, 3)

glBegin(GL_TRIANGLES);

//Triangle
glVertex3f(0.5f, -0.5f, 0.0f);
glVertex3f(0.0f, 0.5f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);

glEnd();

glPopMatrix(); //Undo the move to the center of the triangle

    现在我们的程序看起来像这样:


shrink picture

    我们引入了一个新的变量_angle用来指定物体旋转的度数。同时我们使用了两个新的函数:glRotatef旋转我们的小鸟。glRotatef(_angle,0.0f,0.0f,1.0f)表示物体将绕z轴旋转_angle度,glRotatef(_angle,1.0f,2.0f,3.0f)表示物体将绕向量(1,2,3)旋转_angle度;glScalef(0.7f,0.7f,0.7f)将我们的小鸟分别在x,y,z轴上变为原来的0.7,0.7,0.7倍。如果我们用glScalef(2.0f,1.0f,1.0f),就在我们的视线里的水平方向上放大两倍(x轴)。

    注意glTranslatef,glRotatef,glScalef不能在一个glBegin-glEnd区域内调用。

    现在我们改变我们的镜头使得我们向左偏10度来观察。

float _cameraAngle = 10.0f;

//Draws the 3D scene
void drawScene() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_MODELVIEW); //Switch to the drawing perspective
glLoadIdentity(); //Reset the drawing perspective
glRotatef(-_cameraAngle, 0.0f, 1.0f, 0.0f); //Rotate the camera
glTranslatef(0.0f, 0.0f, -5.0f); //Move forward 5 units

    结果看起来像这样:


camera

    这里我们使用了一点小技巧来改变镜头角度,我们只将整个场景向反方向移动了10度,这在3D编程中经常使用的技巧。

    在我们进入时钟这个概念之前我想解释一下glMatrixMode这个函数。如果我们调用glMatrixMode(GL_MODEL_VIEW),我们是对整个场景里的点进行的变换。如果调用glMatrixMode(GL_PROJECTION),就像我们在handleResize函数中做的一样,将对物体里的点除了普通的变换外还进行特殊的变换。看下handleResize函数,我们转到射影矩阵模式(projection matrix mode),调用glLoadIdentity()重设所有的变换并调用gluPerspective。gluPerspective进行一个奇怪的变换来“透视”(perspective)所有点。不必关心具体如何实现,只需知道我们使用GL_PROJECTION模式来设置为们的远景,剩下的用GL_MODEL_VIEW设置。

    GL_PROJECTION被解释对镜头变换模式,但这并不准确,因为光照在“透视”(projection)模式中并不会受变换的影响。使用这个模式来设置镜头不是一个好办法。

    现在我们已经改变了镜头的角度,每个物体都不方便观察了,因此我们将_cameraAngle改为0.

时钟

    现在我们使用GLUT的时钟增加一些动作。使用时钟的基本思路是我们想让一段代码经常执行。在我们这个例子中我们将物体的每25毫秒旋转2度。下面是做法。

void update(int value) {
_angle += 2.0f;
if
(_angle > 360) {
_angle -= 360;
}


glutPostRedisplay(); //Tell GLUT that the scene has changed

//Tell GLUT to call update again in 25 milliseconds
glutTimerFunc(25, update, 0);
}

    这是我们的更新函数。首先我们将角度增加2度。如果角度大于360度,我们就减去360,这样并不改变表示的角度。这样做并不是必须的,但是这样做可以使得角度值比较小,因为涉及到浮点数精度的问题,这里不展开叙述。然后调用glutPostRedisplay()告诉GLUT场景已经改变需要重新绘制。最后调用glutTimerFunc(25,update,0)让GLUT在25毫秒之后调用update函数。

    value参数是GLUT传递给update函数的,同我们传给glutTimerFunc函数的最后一个参数一样,大多数时候为0。我们并不需要使用这个参数,只是忽略它。

  glutTimerFunc(25, update, 0); //Add a timer

    我们在main函数中还要加上一个glutTimerFunc的调用,这样GLUT在程序运行25毫秒以后第一次调用这个函数。

    好了,下载源码,编译运行下吧。牛B吧,现在我们可以让物体旋转了!

练习:

  • 另外使用一个函数,使所有物体缩小一半,同时不能改变任何glVertex3f函数。
  • 再使用一个glTranslatef函数,在不改变glVertex3f函数的情况下将三角形和五边形向右移动一个单位。
  • 在不加额外时钟的情况下让不同的形状以不同的速度旋转。
  • 不使用额外时钟,使得镜头围绕(0.1,-0.1,1)连续旋转。

posted on 2010-07-19 20:23 camel 阅读(254) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

统计

常用链接

留言簿

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜