OpenGL ES二 – 簡單繪圖概述

還有許多理論知識需要討論,但與其花許多時間在複雜的數學公式或難以理解的概念上,還不如讓我們開始熟悉OpenGL ES的基本繪圖功能。


請下載OpenGL Xcode項目模板。我們使用此模板而不是Apple提供的模板。你可以解壓到下面目錄來安裝它:

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/

此模板用於全屏OpenGL程序,它具有一個OpenGL視圖以及相應的視圖控制器。 大部分時候你不需要動到此視圖。此視圖用於處理一些諸如緩存切換之類的事物,但在兩處調用了其控制器類。

首先,當設定視圖時,調用了一次控制器。調用視圖控制器的 setupView: 方法使控制器有機會增加所需的設定工作。這裏是你設定視口,添加光源以及進行其他項目相關設定的地方。現在我們將忽略此方法。此方法中已經有非常基本的設定以允許你進行簡單地繪圖。

控制器的 drawView: 方法根據常數kRenderingFrequency的值定期地被調用。kRenderingFrequency的初始值爲15.0,表示  drawView: 方法每秒鐘被調用15次。如果你希望改變渲染的頻率,你可以在ConstantsAndMacros.h中找到此常數的定義。

首先加入下列代碼到GLViewController.mdrawView: 方法中:

- (void)drawView:(GLView*)view;
{
    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);

    glLoadIdentity();
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
    glDrawArrays(GL_TRIANGLES, 0, 9);
    glDisableClientState(GL_VERTEX_ARRAY);
}

在討論我們到底做了什麼之前,先運行一下,你應該看到以下畫面:

這是個簡單的方法;如果你試過了,你可能已經知道我們到底做了什麼,但這裏我們還是一起過一遍。因爲我們的任務是畫三角形,所以需要三個頂點,因此我們創建三個上一篇文章中討論過的Vertex3D對象:

    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);

你應該注意到了三個頂點的z值是一樣的,其值(-3.0)是處於原點 “之後”的。因爲我們還沒有做任何改變,所以我們是站在原點上觀察虛擬世界的,這是默認的起點位置。將三角形放置在z值爲-3處,可以保證我們可以在屏幕上看到它。

隨後,我們創建一個由這三個頂點構成的三角形。

    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);

這些代碼很容易理解,對嗎?但是,在幕後,電腦是將其視爲一個包含9個 GLfloat 的數組。如下:

    GLfloat  triangle[] = {0.0, 1.0, -3.0, 1.0, 0.0, -3.0, -1.0, 0.0, -3.0};

並不是完全相同 – 這裏有一個很小但很重要的區別。在我們的示例中,我們傳遞給OpenGL的是 Triangle3D 對象的地址(即 &triangle ),但在第二個使用數組的示例中,由於C數組是指針,我們傳遞的是數組。現在不需要考慮太多,因爲這將是最後一次我用這種方式(第二種方法)定義一個 Triangle3D 對象。等一下我將解釋原因,但現在讓我們先過一遍代碼。下一步我們做的是加載單位矩陣。我將花至少一整篇文章討論變換矩陣。我們暫且將其視爲OpenGL的“復位開關”。它將清除虛擬世界中的一切旋轉,移動或其他變化並將觀察者置於原點。

    glLoadIdentity();

之後,我們告訴OpenGL所有的繪製工作是在一個灰色背景上進行的。OpenGL通常需要用四個鉗位值來定義顏色。上一篇文章中有提過,鉗位浮點數是0.0 到 1.0之間的浮點數。我們通過定義紅,綠,藍以及alpha元素來定義顏色, alpha值定義了顏色之後物體的透視程度。現在暫時不用管它,將其設爲1.0,代表完全不透明。

    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

在OpenGL中要定義白色,我們需要將四個元素全部設爲1.0。要定義不透明的黑色,則定義紅,綠,藍爲0.0,alpha爲1.0。上例代碼的第二行是通知OpenGL清除以前的一切圖形並將其設爲clear顏色。

你可能想知道 glClear()的兩個參數是什麼意思。簡單地說,它們是存儲與位域中的常量。OpenGL 保存了一系列 緩存(buffers),即用於繪圖各方面的內存塊。將這兩個值進行邏輯或是通知OpenGL清除兩個不同的緩存 – 顏色緩存(color buffer) 和 深度緩存(depth buffer)。 顏色緩存保存當前幀各像素的顏色。基本上就是你在屏幕上看到的。深度緩存(有時也稱爲“z-buffer”)保存每個潛在像素離觀察者距離的信息。使用此信息可以確定一個像素是否需要被繪製出來。這兩個緩存是OpenGL中最常見的緩存。還有其他一些緩存,如模板緩存(stencil buffer)和累積緩存(accumulation buffer),但現在我們暫時不討論這些。我們現在只需記住在繪製一幀之前,必須清除這兩個緩存以保證不會和以前的內容混雜。

然後,我們要啓動OpenGL的一項稱爲vertex arrays(頂點數組)的特性。此特性可能只需要setupView:方法中啓動一次,但作爲基本準則,我喜歡啓動和禁止我使用的功能。你永遠也不會知道是否另一段代碼會做不同處理。如果你打開你需要的功能然後關閉它,產生問題的機率將大爲減小。就本例來說,如果另一個類不使用頂點數組而使用頂點緩存的話,任何一段代碼遺留了啓動了的特性或沒有顯性啓動其需要的特性,這一段或兩段代碼都會導致不可預知的結果。

    glEnableClientState(GL_VERTEX_ARRAY);

接下來我們設置了繪圖時所需的顏色。此行代碼將繪圖顏色設爲鮮豔的紅色。

    glColor4f(1.0, 0.0, 0.0, 1.0);

現在,直到下次調用 glColor4f()前所有的圖形都是以紅色繪製。有一些例外的情況,例如繪製紋理形狀時,但基本上,這樣設定顏色可以使顏色保持。

由於我們使用頂點數組,我們必須通知OpenGL頂點的數組在什麼地方。記住,頂點數組只是一個GLfloat的C數組,每三個值代表一個頂點。我們創建了Triangle3D 對象,但在內存中,它完全等同於9個連續的GLfloat, 所以我們可以傳遞此三角形對象的地址。

    glVertexPointer(3, GL_FLOAT, 0, &triangle);

glVertexPointer() 的第一個參數指示了多少個GLfloat代表一個頂點。根據你是在進行二維或三維繪圖,你可以傳遞2或者3。儘管我們的物體是存在於一個平面的,我們仍然將其繪製在三維虛擬世界中,因此每個頂點用三個數值表示,所以我們傳遞3給函數。然後,我們傳遞一個枚舉值告訴OpenGL頂點是由GLfloat構成。OpenGL ES允許你在當地數組中使用大部分的數據類型,但除GL_FLOAT外,其他都很少見。下一個參數… 現在不需要考慮下一個參數。那是以後討論的主題。現在,它始終爲0。在以後的文章中,我將討論怎樣使用此參數將同一對象以不同的數據類型混雜在一個數據結構中。

隨後,我們通知OpenGL通過剛纔提交的頂點數組來繪製三角形。

    glDrawArrays(GL_TRIANGLES, 0, 9);

你可能已經可以猜到,第一個枚舉值是告訴OpenGL繪製什麼。儘管OpenGL ES不支持繪製三角形之外的四邊形或其他多邊形,但它仍然支持一些其他繪圖模式,如繪製點,線,線迴路,三角形條和三角形扇。稍後我們將討論這些繪圖模式。

最後,我們要禁止先前啓動了的特性以保證不會被其他地方的代碼弄混。本例中沒有其他的代碼了,但通常你可以使用OpenGL繪製多個物體。

    glDisableClientState(GL_VERTEX_ARRAY);

好了,我們的代碼可以工作了儘管它不是那麼引人入勝而且不是十分高效,但它確確實實可以工作。每秒鐘我們的代碼被調用數次。不相信?加入下列黑體代碼再次運行:

- (void)drawView:(GLView*)view;
{
    static      GLfloat rotation = 0.0;

    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);

    glLoadIdentity();
    glRotatef(rotation, 0.0, 0.0, 1.0);
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
    glDrawArrays(GL_TRIANGLES, 0, 9);
    glDisableClientState(GL_VERTEX_ARRAY);

    rotation+= 0.5;
}

當你再次運行時,三角形將沿着原點緩緩轉動。先不需要關注太多旋轉的邏輯。我只是想告訴你我們的代碼每秒鐘被調用了多次。

如果你想畫正方形怎麼辦?OpenGL ES並不支持正方形,所以我們只能通過三角形來定義正方形。這很簡單 – 一個正方形可以通過兩個三角形構成。我們要怎樣調整上敘代碼來繪製兩個三角形?是不是可以創建兩個Triangle3D?是的,你可以這樣做,但那沒有效率。我們最好將兩個三角形置入同一個頂點數組中。我們可以通過定義一個包含兩個Triangle3D對象的數組,或分配大小等於兩個Triangle3D對象或18個GLfloat的內存.

這是一種方法:

- (void)drawView:(GLView*)view;
{
    Triangle3D  triangle[2];
    triangle[0].v1 = Vertex3DMake(0.0, 1.0, -3.0);
    triangle[0].v2 = Vertex3DMake(1.0, 0.0, -3.0);
    triangle[0].v3 = Vertex3DMake(-1.0, 0.0, -3.0);
    triangle[1].v1 = Vertex3DMake(-1.0, 0.0, -3.0);
    triangle[1].v2 = Vertex3DMake(1.0, 0.0, -3.0);
    triangle[1].v3 = Vertex3DMake(0.0, -1.0, -3.0);

    glLoadIdentity();
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
    glDrawArrays(GL_TRIANGLES, 0, 18);
    glDisableClientState(GL_VERTEX_ARRAY);
}

運行,你將看到如下屏幕:

由於Vertex3DMake()方法在堆中創建新的Vertex3D然後複製其值到數組中而造成額外的內存被佔用,因此上述代碼並不理想。

對於這樣簡單的情況是沒有什麼問題的,但是在一個更爲複雜的情況下,如果定義的3D物體很大時,那麼你不會希望將其分配在堆中而且你不會希望對一個頂點不止一次地進行內存分配,所以最好是養成習慣通過我們的老朋友 malloc() (我更喜歡用 calloc() ,因爲它會將所有值設爲0,比較易於查找錯誤)將頂點分配在棧中。 首先我們需要一個函數設定現存頂點的值而不是像Vertex3DMake()一樣創建一個新對象。如下:
static inline void Vertex3DSet(Vertex3D *vertex, CGFloat inX, CGFloat inY, CGFloat inZ)
{
    vertex->x = inX;
    vertex->y = inY;
    vertex->z = inZ;
}

現在,我們使用新方法將兩個三角形分配在棧中,重寫代碼:

- (void)drawView:(GLView*)view;
{
    Triangle3D  *triangles = malloc(sizeof(Triangle3D) * 2);

    Vertex3DSet(&triangles[0].v1, 0.0, 1.0, -3.0);
    Vertex3DSet(&triangles[0].v2, 1.0, 0.0, -3.0);
    Vertex3DSet(&triangles[0].v3, -1.0, 0.0, -3.0);
    Vertex3DSet(&triangles[1].v1, -1.0, 0.0, -3.0);
    Vertex3DSet(&triangles[1].v2, 1.0, 0.0, -3.0);
    Vertex3DSet(&triangles[1].v3, 0.0, -1.0, -3.0);

    glLoadIdentity();
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, triangles);
    glDrawArrays(GL_TRIANGLES, 0, 18);
    glDisableClientState(GL_VERTEX_ARRAY);

    if (triangles != NULL)
        free(triangles);
}

好了,我們已經討論了許多基礎知識,我們現在更深入一點。記住我說過OpenGL ES不止一種繪圖方式嗎?現在正方形需要6個頂點(18個 GLfloat),實際上我們可以使用 triangle strips (GL_TRIANGLE_STRIP)方法通過四個頂點(12個 GLfloat)來繪製正方形。

這裏是三角形條的基本概念:第一個三角形條是由前三個頂點構成(索引0, 1, 2)。第二個三角形條是由前一個三角形的兩個頂點加上數組中的下一個頂點構成,繼續直到整個數組結束。看下圖更清楚 – 第一個三角形由頂點 1, 2, 3構成,下一個三角形由頂點 2, 3, 4構成,等等:

所以,我們的正方形是這樣構成的:

代碼如下:

- (void)drawView:(GLView*)view;
{
    Vertex3D  *vertices = malloc(sizeof(Vertex3D) * 4);

    Vertex3DSet(&vertices[0], 0.0, 1.0, -3.0);
    Vertex3DSet(&vertices[1], 1.0, 0.0, -3.0);
    Vertex3DSet(&vertices[2], -1.0, 0.0, -3.0);
    Vertex3DSet(&vertices[3], 0.0, -1.0, -3.0);

    glLoadIdentity();

    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 12);
    glDisableClientState(GL_VERTEX_ARRAY);

    if (vertices != NULL)
        free(vertices);
}

我們再返回到第一段代碼看看。記住我們是怎樣繪製第一個三角形嗎?我們使用glColor4f()設置顏色並且說設置的顏色一直適用於隨後的代碼。那意味着定義於頂點數組中的物體必須用同一種顏色繪製。很有侷限性,對嗎?

並非如此。正如 OpenGL ES 允許你將所有頂點置於一個數組中,它還允許你將每個頂點使用的顏色置於一個顏色數組(color array)中。如果你選擇使用顏色數組,那麼你需要爲每個頂點設置顏色(四個GLfloat值) 。 通過下面方法啓動顏色數組:

glEnableClientState(GL_COLOR_ARRAY);

我們可以象頂點數組一樣定義一個包含四個GLfloat成員的 Color3D結構。下面是怎樣爲原始三角形分配不同顏色的示例:

- (void)drawView:(GLView*)view;
{
    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);

    Color3D     *colors = malloc(sizeof(Color3D) * 3);
    Color3DSet(&colors[0], 1.0, 0.0, 0.0, 1.0);
    Color3DSet(&colors[1], 0.0, 1.0, 0.0, 1.0);
    Color3DSet(&colors[2], 0.0, 0.0, 1.0, 1.0);

    glLoadIdentity();
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
    glColorPointer(4, GL_FLOAT, 0, colors);
    glDrawArrays(GL_TRIANGLES, 0, 9);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);

    if (colors != NULL)
        free(colors);
}

運行,屏幕如下:

今天我們還要討論一個話題。如果我們不止一次使用一個頂點(三角形條或三角形扇的相鄰頂點除外),我們至今使用的方法存在一個問題,我們必須多次向OpenGL傳遞同一頂點。那不是什麼大問題,但通常我們需要儘量減少向OpenGL傳遞的數據量,所以一次又一次地傳遞同一個4字節浮點數是非常地不理想。在一些物體中,某個頂點會用在七個甚至更多的不同三角形中,因此你的頂點數組可能會增大許多倍。

當處理這類複雜的幾何體時,有一種方法可以避免多次傳遞同一個頂點,就是使用通過頂點對應於頂點數組中的索引的方法,此方法稱之爲元素(elements)。其原理是創建一個每個頂點只使用一次的數組。然後使用另一個使用最小的無符號整型數的數組來保存所需的唯一頂點號。換句話說,如果頂點數組具有小於256個頂點,那麼你應創建一個GLubyte數組,如果大於 256,但小於 65,536,應使用 GLushort。你可以通過映射頂點在第一個數組中的索引值來創建三角形(或其他形狀)。所以,如果你創建了一個具有12個頂點的數組,那麼數組中的第一個頂點爲0。你可以按以前一樣的方法繪製圖形,只不過不是調用glDrawArrays(),而是調用不同的函數 glDrawElements()並傳遞整數數組。

讓我們以一個真實的,如假包換的3D形狀來結束我們的教程:一個二十面體。每個人都使用正方體,但我們要更怪異一點,我們要畫一個二十面體。替換drawView:

- (void)drawView:(GLView*)view;
{

    static GLfloat rot = 0.0;

    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
         {1.0, 0.0, 0.0, 1.0},
         {1.0, 0.5, 0.0, 1.0},
         {1.0, 1.0, 0.0, 1.0},
         {0.5, 1.0, 0.0, 1.0},
         {0.0, 1.0, 0.0, 1.0},
         {0.0, 1.0, 0.5, 1.0},
         {0.0, 1.0, 1.0, 1.0},
         {0.0, 0.5, 1.0, 1.0},
         {0.0, 0.0, 1.0, 1.0},
         {0.5, 0.0, 1.0, 1.0},
         {1.0, 0.0, 1.0, 1.0},
         {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-3.0f);
    glRotatef(rot,1.0f,1.0f,1.0f);
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);

    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}

運行,屏幕上將看到一個漂亮的旋轉物體:

因爲我們沒有使用光源而且即使使用了光源我們也沒有告訴OpenGL怎樣進行光源反射,所以它看上去還不是一個完美的3D物體。有關光線的部分將在後續文章中討論。

這裏我們做了什麼?首先,我們建立了一個靜態變量來跟蹤物體的旋轉。

    static GLfloat rot = 0.0;

然後我們定義頂點數組。我們使用了一個與前不同的方法,但結果是一樣的。由於我們的幾何體根本不會變化,所以我們將其定義爲const,這樣就不需要每一幀都分配/清除內存:

    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

然後用同樣方法建立一個顏色數組 。創建一個Color3D 對象數組,每項對應於前一個數組的頂點:

    static const Color3D colors[] = {
         {1.0, 0.0, 0.0, 1.0},
         {1.0, 0.5, 0.0, 1.0},
         {1.0, 1.0, 0.0, 1.0},
         {0.5, 1.0, 0.0, 1.0},
         {0.0, 1.0, 0.0, 1.0},
         {0.0, 1.0, 0.5, 1.0},
         {0.0, 1.0, 1.0, 1.0},
         {0.0, 0.5, 1.0, 1.0},
         {0.0, 0.0, 1.0, 1.0},
         {0.5, 0.0, 1.0, 1.0},
         {1.0, 0.0, 1.0, 1.0},
         {1.0, 0.0, 0.5, 1.0}
    };

最後,創建二十面體。上述十二個頂點本身並未描述形狀。OpenGL需要知道怎樣將它們聯繫在一起,所以我們創建了一個整型數組( GLubyte)指向構成各三角形的頂點。

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

二十面體的第一個面的三個數是1,2,6,代表繪製處於索引1 (0.850651, 0, 0.525731), 2 (0.850651, 0, 0.525731), 和6 (0.525731, 0.850651, 0)之間的三角形。

下面一段代碼沒有新內容,只是加載單元矩陣(所以變換復位),移動並旋轉幾何體,設置背景色,清除緩存,啓動頂點和顏色數組,然後提供頂點數組數據給OpenGL。所有這些在前一個例子中都有涉及。

    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-3.0f);
    glRotatef(rot,1.0f,1.0f,1.0f);
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);

然後,我們沒有使用glDrawArrays()。而是調用glDrawElements()

    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

接着,執行禁止功能,根據距上一幀繪製的時間增加旋轉變量值:

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];

記住:如果你按繪製的正確次序提供頂點,那麼你應該使用glDrawArrays(),但是如果你提供一個數組然後用另一個以索引值區分頂點次序的數組的話,那麼你應該使用glDrawElements()

請花些時間測試繪圖代碼,添加更多的多邊形,改變顏色等。OpenGL繪圖功能遠超過本文涉及的內容,但你應該清楚iPhone上3D物體繪製的基本概念了:創建一塊內存保存所有的頂點,傳遞頂點數組給OpenGL,然後由OpenGL繪製出來。

源碼下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章