跳轉至內容

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


華夏公益教科書