跳转至内容

OpenGL 编程/GLStart/Tut4

来自 Wikibooks,开放世界中的开放书籍

教程 4:平移、旋转和缩放

[编辑 | 编辑源代码]

本教程将教你如何在 3D 空间中操作对象。由于我们还没有介绍 3D 绘制,我们将操作在上一个教程中创建的 2D 图元。

投影和模型变换

[编辑 | 编辑源代码]

如果你还记得第二个教程,我们创建了一个名为 Resize() 的函数来处理窗口大小调整。我不确定该函数中的代码是否解释得很好,所以让我们再回顾一下。以下是 Resize() 函数的完整代码

    void Resize(int width, int height)
    {
         //set viewport
         glViewport(0,0,(GLsizei)width,(GLsizei)height);
         glMatrixMode(GL_PROJECTION);//change matrix mode to projection
         glLoadIdentity();//load identity matrix
         //set perspective
         gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,1.0f,1000.0f); 
    
          glMatrixMode(GL_MODELVIEW);//change matrix mode to modelview
         glLoadIdentity();//load identity matrix
    }


第一行,glViewport() 函数,确定 OpenGL 在窗口上绘制的区域。我们在这里将其设置为整个窗口。

这里需要注意的是 glMatrixMode() 函数。此函数将我们想要编辑的矩阵作为参数。我将稍后讨论矩阵(可能在不同的教程或一篇简单的文章中)。但基本上,第一次使用参数 GL_PROJECTION 调用 glMatrixMode() 会影响用户如何查看 OpenGL 场景。因此,在下一行中,我放入 glLoadIdentity() 将投影矩阵设置为单位矩阵。然后在下一行中,我使用了 gluPerspective() 函数,它允许对场景进行逼真的查看,因为靠近观察者的物体很大,而远离观察者的相同物体很小。gluPerspective() 的参数是视角的角度、纵横比(宽高比)、观察者可以看到的最近 z 位置和观察者可以看到的最远 z 位置。

在那行之后,我再次将矩阵模式更改为 GL_MODELVIEW,这会影响所谓的模型视图矩阵。编辑模型视图矩阵时,只会影响场景中的对象,而不是指向 3D 场景的实际相机。因此,对模型视图矩阵所做的任何更改只会影响对象。例如,如果你正在编辑模型视图矩阵,并且你使用 glRotatef() 函数(我将在本教程的后面部分详细介绍)执行旋转操作,结果将是对象本身旋转,而不是相机。为了更好地记住它,只需记住相机受投影影响,而对象受模型视图影响。

由于 Resize() 函数中的最后两行将矩阵更改为模型视图,然后加载单位矩阵,因此任何矩阵操作(如旋转或平移)只会影响对象。

如果你还记得之前的教程,在我们创建的 Render() 函数中,第一行之一是对 glTranslatef() 的调用,然后填充参数以将对象沿 z 轴移开 4 个单位。

如果你注意到 Render() 函数代码中,在 glClear() 函数调用之后,单位矩阵使用 glLoadIdentity() 加载,然后使用 glTranslatef() 函数执行平移操作。

    //clear color and depth buffer 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();//load identity matrix

    glTranslatef(0.0f,0.0f,-4.0f);//move forward 4 units

由于每次调用 Render() 函数时都会加载单位矩阵,因此之后发生的平移保持不变。现在假设你要在 Render() 函数中删除 glLoadIdentity() 函数。继续执行此操作,编译并运行程序。发生了什么?好吧,如果你只看到彩色形状出现了一毫秒,然后它开始以很快的速度缩小,那么对象(彩色形状)将以恒定的速度不断平移。由于我们将平移操作设置为将对象从观察者移开 4 个单位,因此每次调用 Render() 函数(根据计算机的速度,可能每秒数百次)时,对象都会移开 4 个单位。但是,在 Render() 函数开头调用 glLoadIdentity() 后,对象只会平移 4 个单位。

到目前为止,我们所做的只是沿 z 轴平移。glTranslatef() 函数以 x、y 和 z 单位(按此顺序)作为三个参数,以平移对象。让我们用其他轴做一些例子。

沿 X 轴平移

[编辑 | 编辑源代码]

我将解释的第一个示例将显示一个沿 X 轴来回移动的多边形动画。对于此示例,我使用的是关于图元的第三个教程中的“polygon”项目文件。

我将通过首先声明两个全局变量来执行此动画。第一个变量称为“xTrans”,类型为 float,我最初将其设置为 0.0f。“xTrans”变量将跟踪沿 x 轴平移的量。第二个变量称为“xDir”,类型为整数,最初设置为 1。“xDir”变量将确定多边形将在 x 轴上的哪个方向移动。如果值为 1,则多边形将沿 x 轴向右移动。如果值为 0,则多边形将沿 x 轴向左移动。

float xTrans = 0.0f;//keeps track of X translation
int xDir = 1;//1-right,0-left

现在进入 Render() 函数,在将对象沿 x 轴从用户移开 4 个单位的 glTranslatef() 函数调用之后,我们需要首先检查多边形移动的方向。为此,我们创建了一个 IF 语句,它检查“xDir”变量的值,并查看它是否具有值 1,这意味着多边形正在向右移动。此外,在该 IF 语句中,我们还需要通过检查“xTrans”变量是否小于 2.0f 来确保多边形不会移出屏幕,这是用户可以看到的右侧的单位数。如果这两个语句都为真,则我们将“xTrans”变量递增 0.1f。如果语句不为真,则表示多边形已到达窗口的最右侧,需要反向。我们通过将“xDir”变量设置为 0 来做到这一点,这意味着多边形将沿 x 轴向左移动。

    //checking if polygon can move right along x-axis
    if(xDir==1 && xTrans<2.0f)
    {
        xTrans+=0.1f;//increment xTrans
    }
    else//polygon has reached the right edge
    {
        xDir=0;//change direction
    }

现在我们再次执行相同的操作,除了我们检查“xDir”变量的值是否为 0,并确保“xTrans”变量大于 –2.0f,这是窗口的左侧。如果语句正确,则我们将“xTrans”中的值递减(减小)–0.1f。如果语句为假,我们设置“xDir”为 1,以便多边形开始沿 x 轴向右移动。

    //checking if polygon can move left along x-axis
    if(xDir==0 && xTrans>-2.0f)
    {
        xTrans-=0.1f;//decrement xTrans
    }
    else//polygon has reached the left edge
    {
        xDir=1;//change direction
    }

在所有这些之后,我们需要实际执行平移。在两个 IF 语句之后,创建对 glTranslatef() 的调用,并将第一个参数设置为“xTrans”变量,并将最后两个参数保留为 0.0f。

    //translate along the x-axis
    glTranslatef(xTrans,0.0f,0.0f);

现在,当你编译并运行程序时,你应该会看到一个在窗口左右快速移动的多边形。

以下是完整的 Render() 函数,以及此示例的示例输出。要查看此程序的完整代码,请在可下载文件中打开“xTranslate”项目文件夹。

void Render()
{     
    //clear color and depth buffer 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();//load identity matrix

    glTranslatef(0.0f,0.0f,-4.0f);//move forward 4 units
    
    //checking if polygon can move right along x-axis
    if(xDir==1 && xTrans<2.0f)
    {
        xTrans+=0.1f;//increment xTrans
    }
    else//polygon has reached the right edge
    {
        xDir=0;//change direction
    }
    
    //checking if polygon can move left along x-axis
    if(xDir==0 && xTrans>-2.0f)
    {
        xTrans-=0.1f;//decrement xTrans
    }
    else//polygon has reached the left edge
    {
        xDir=1;//change direction
    }
    
    //translate along the x-axis
    glTranslatef(xTrans,0.0f,0.0f);     
       
    glColor3f(0.0f,0.0f,1.0f); //blue color
    
    glBegin(GL_POLYGON);//begin drawing of polygon
      glVertex3f(-0.5f,0.5f,0.0f);//first vertex
      glVertex3f(0.5f,0.5f,0.0f);//second vertex
      glVertex3f(1.0f,0.0f,0.0f);//third vertex
      glVertex3f(0.5f,-0.5f,0.0f);//fourth vertex
      glVertex3f(-0.5f,-0.5f,0.0f);//fifth vertex
      glVertex3f(-1.0f,0.0f,0.0f);//sixth vertex
    glEnd();//end drawing of polygon
}

沿 y 和 z 轴平移可以遵循与上述类似的过程。只需确保知道,当沿 z 轴平移时,沿正方向移动会导致对象靠近你。因此,沿负方向移动会导致对象远离你。

对象的旋转涉及围绕轴移动对象。因此,如果你要围绕 x 轴旋转对象,则对象将围绕 x 轴旋转成圆形。其他轴也是如此。

为了实现这些旋转,我们使用 glRotatef() 函数,该函数将旋转对象的角作为第一个参数。第二个、第三个和第四个参数分别是旋转对象的 x、y 和 z 轴。例如,要沿 x 轴顺时针旋转对象 90 度,你可以在 glRotatef() 函数的第一个参数中输入 90.0f,然后在第二个参数中输入 1.0f 以指示你想要沿 x 轴旋转,然后将最后两个参数保留为 0.0f。

glRotatef(90.0f,1.0f,0.0f,0.0f);

绕 Y 轴旋转

[编辑 | 编辑源代码]

在这个我即将向您展示的示例中,我们将采用第三个教程中的“多边形”示例,并沿y轴旋转该多边形。因此,请打开“多边形”示例并尝试跟随操作。

就像变换示例一样,我们首先需要声明一个名为“yRot”的全局变量,该变量的类型为float,并最初设置为0.0f。此变量将保存旋转角度。

float yRot = 0.0f;//variable to hold y rotation angle

然后在Render()函数中,在标准平移调用之后,我们使用名为“yRot”变量作为第一个参数调用glRotatef()函数。第二个参数为0.0f,表示我们不沿x轴旋转。第三个参数为1.0f,表示我们沿y轴旋转。然后最后一个参数为0.0f,表示我们不想沿z轴旋转。

    //rotate along the y-axis
    glRotatef(yRot,0.0f,1.0f,0.0f);

在Render()函数的末尾,我们将“yRot”变量递增0.1f,以便多边形以恒定速率旋转。

    yRot+=0.1f;//increment the yRot variable

现在编译并运行程序,您应该会看到您的多边形沿y轴旋转。

以下是整个Render()函数的代码,以及示例输出。要查看完整代码,请参阅可下载文件中包含的“yRotation”项目文件。

void Render()
{     
    //clear color and depth buffer 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();//load identity matrix

    glTranslatef(0.0f,0.0f,-4.0f);//move forward 4 units
    
    //rotate along the y-axis
    glRotatef(yRot,0.0f,1.0f,0.0f);
    
    glColor3f(0.0f,0.0f,1.0f); //blue color
    
    glBegin(GL_POLYGON);//begin drawing of polygon
      glVertex3f(-0.5f,0.5f,0.0f);//first vertex
      glVertex3f(0.5f,0.5f,0.0f);//second vertex
      glVertex3f(1.0f,0.0f,0.0f);//third vertex
      glVertex3f(0.5f,-0.5f,0.0f);//fourth vertex
      glVertex3f(-0.5f,-0.5f,0.0f);//fifth vertex
      glVertex3f(-1.0f,0.0f,0.0f);//sixth vertex
    glEnd();//end drawing of polygon
    
    yRot+=0.1f;//increment the yRot variable
}

缩放允许您根据需要更改对象的大小。要执行缩放,您需要使用OpenGL函数glScalef(),它将对象的x、y和z缩放比例作为参数。在输入参数时,您需要记住glScalef()中的参数与对象的原始大小相乘。例如,如果您绘制了一个宽1个单位、高1个单位的正方形,要使对象的大小变为原来的两倍,您将在glScalef()函数的x和y参数中使用2.0f,以指示您想要将正方形的大小加倍。

在上图中,我将z参数保持为1.0f,以指示不更改正方形的深度。即使在这个二维正方形中实际上没有深度,您也应该养成这样做的习惯,尤其是在我们进行3D绘图时。

缩放示例

[编辑 | 编辑源代码]

在这个示例中,我将使用第三个教程中的“三角形”示例。我们将通过显示屏幕上两个三角形放大、几乎充满屏幕,然后缩小到您再也看不到三角形为止的动画来修改三角形程序。我们将让此动画自行循环。

我修改此程序的第一件事是声明三个全局变量。第一个名为“xScale”的变量,类型为float,最初设置为0.0f,将保存x轴上的缩放因子。第二个名为“yScale”的变量,类型也为float,设置为0.0f,保存y轴上的缩放因子。第三个名为“enlarge”的变量,类型为bool,最初设置为“true”。此变量控制三角形是放大(变大)还是缩小(变小)。如果此变量设置为true,则三角形放大,如果设置为false,则三角形缩小。

float xScale = 0.01f;//x scale factor
float yScale = 0.01f;//y scale factor
bool enlarge = true;//true - enlarge, false - shrink

在Render()函数中,在glColor3f()函数调用之后,我们需要检查三角形是否可以放大。我们首先创建一个IF语句,检查布尔变量“enlarge”是否为true,并确保“xScale”变量小于2.0,以便三角形不会放大超过其大小的两倍。如果这些语句为true,则我们将“xScale”和“yScale”变量递增0.01f,以允许三角形放大。如果这些语句不为true,则我们将变量“enlarge”设置为false,这意味着我们应该开始缩小三角形。

//check if triangles can be enlarged
    if(enlarge == true && xScale < 2.0f)
    {
          xScale += 0.01f;//increment x scale
          yScale += 0.01f;//increment y scale
    }
    else
    {
          enlarge = false;//start shrinking
    }

在此之后,我们创建另一个IF语句,但这次我们检查三角形是否可以缩小。我们检查以确保变量“enlarge”设置为false,并且“xScale”变量大于0.0,以便三角形不会以负比例缩小。如果这两个语句都为true,则我们将“xScale”和“yScale”变量递减0.01f,以允许三角形缩小。如果这些语句不为true,则我们将变量“enlarge”设置为true,这意味着三角形需要开始放大。

    //check if triangles can be shrunk
    if(enlarge == false && xScale > 0.0f)
    {
          xScale -= 0.01f;//decrement x scale
          yScale -= 0.01f;//decrement y scale
    }
    else
    {
          enlarge = true;//start enlarging
    }

最后我们需要做的事情是进行实际的缩放。我们对glScalef()函数进行调用,第一个参数为“xScale”变量,第二个参数为“yScale”变量。我们将第三个参数保留为1.0f,以指示我们不在z轴上进行缩放。

    //scale triangles at a constant rate
    glScalef(xScale,yScale,1.0f);

以下是整个Render()函数代码以及示例输出。如果您想查看完整代码,请参阅可下载文件中的“xyScale”项目文件夹。

void Render()
{     
    //clear color and depth buffer 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();//load identity matrix

    glTranslatef(0.0f,0.0f,-4.0f);//move forward 4 units
    
    glColor3f(0.0f,0.0f,1.0f); //blue color
    
    //check if triangles can be enlarged
    if(enlarge == true && xScale < 2.0f)
    {
          xScale += 0.01f;//increment x scale
          yScale += 0.01f;//increment y scale
    }
    else
    {
          enlarge = false;//start shrinking
    }
    
    //check if triangles can be shrunk
    if(enlarge == false && xScale > 0.0f)
    {
          xScale -= 0.01f;//decrement x scale
          yScale -= 0.01f;//decrement y scale
    }
    else
    {
          enlarge = true;//start enlarging
    }
    
    //scale triangles at a constant rate
    glScalef(xScale,yScale,1.0f);
    
    glBegin(GL_TRIANGLES);//start drawing triangles
      glVertex3f(-1.0f,-0.25f,0.0f);//triangle one first vertex
      glVertex3f(-0.5f,-0.25f,0.0f);//triangle one second vertex
      glVertex3f(-0.75f,0.25f,0.0f);//triangle one third vertex
      //drawing a new triangle
      glVertex3f(0.5f,-0.25f,0.0f);//triangle two first vertex
      glVertex3f(1.0f,-0.25f,0.0f);//triangle two second vertex
      glVertex3f(0.75f,0.25f,0.0f);//triangle two third vertex
    glEnd();//end drawing of triangles
}

自定义变换和旋转函数

[编辑 | 编辑源代码]

在结束本教程之前,我想介绍的最后一件事是如何创建一个自定义函数来处理旋转和平移。我不会在此函数中添加缩放,但如果您愿意,可以随时自行添加。

我创建的函数名为“Transform”,返回类型为void。传递了六个参数,这些参数都是float类型。前三个参数是对象平移到的x、y和z值。它们分别称为“xTrans”、“yTrans”和“zTrans”。后三个参数处理旋转的x、y和z值。它们分别称为“xRot”、“yRot”和“zRot”。

//custom function for transformations
void Transform(float xTrans, float yTrans, float zTrans, float xRot, float yRot, float zRot)
{

因此,在函数内部,我们将首先处理平移。您只需对glTranslatef()函数进行一次调用,并使用Transform()函数的前三个参数填充它即可。

    //translate object
    glTranslatef(xTrans, yTrans, zTrans);

现在我们将处理旋转。为此,我们对glRotatef()进行三次单独的调用,使用传递给Translate()函数的参数填充每个函数的第一个参数。然后,我们使用1.0f或0.0f的值填充glRotatef()的其余参数,具体取决于我们在哪个变量上进行旋转。

    //rotate along x-axis
    glRotatef(xRot,1.0f,0.0f,0.0f);
    //rotate along y-axis
    glRotatef(yRot,0.0f,1.0f,0.0f);
    //rotate along z-axis
    glRotatef(zRot,0.0f,0.0f,1.0f);

这就是函数的全部内容。以下是整个函数列表。我还创建了一个使用此自定义函数的示例程序。在可下载的项目文件中查找名为“customTrans”的文件。我还在此处列出了我在此示例程序中对Transform()函数的调用以及示例输出。

//custom function for transformations
void Transform(float xTrans, float yTrans, float zTrans, float xRot, float yRot, float zRot)
{
    //translate object
    glTranslatef(xTrans, yTrans, zTrans);
    
    //rotate along x-axis
    glRotatef(xRot,1.0f,0.0f,0.0f);
    //rotate along y-axis
    glRotatef(yRot,0.0f,1.0f,0.0f);
    //rotate along z-axis
    glRotatef(zRot,0.0f,0.0f,1.0f);
}
    //translate to upper - left corner and
    //rotate 45 degrees along the x,y, and z axis
    Transform(-1.0f,1.0f,0.0f,45.0f,45.0f,45.0f);


华夏公益教科书