GLKit 框架詳細解析(三)—— 一個詳細示例和說明(二)

版本記錄

版本號 時間
V1.0 2018.08.10

前言

GLKit框架的設計目標是為了簡化基于OpenGL或者OpenGL ES的應用開發。 接下來幾篇我們就解析一下這個框架。感興趣的看下面幾篇文章。
1. GLKit 框架詳細解析(一)—— 基本概覽
2. GLKit 框架詳細解析(二)—— 一個詳細示例和說明(一)

Setting Up the Buffers - 設置緩沖

現在,您想要開始生成和綁定緩沖區,將數據傳遞給它們,以便OpenGL知道如何在屏幕上繪制方塊。 首先在setupGL()方法的底部添加以下輔助變量:

// 1
let vertexAttribColor = GLuint(GLKVertexAttrib.color.rawValue)
// 2
let vertexAttribPosition = GLuint(GLKVertexAttrib.position.rawValue)
// 3    
let vertexSize = MemoryLayout<Vertex>.stride
// 4
let colorOffset = MemoryLayout<GLfloat>.stride * 3
// 5
let colorOffsetPointer = UnsafeRawPointer(bitPattern: colorOffset)

下面進行詳細解析:

  • 1)生成緩沖區時,需要指定有關如何從數據結構中讀取顏色和位置的信息。 OpenGL期望GLuint用于顏色頂點屬性。在這里,您使用GLKVertexAttrib枚舉將color屬性作為原始GLint獲取。然后將其轉換為GLuint - OpenGL方法調用的內容 - 并將其存儲以用于此方法。

  • 2)與顏色頂點屬性一樣,您希望避免編寫該長代碼來存儲和讀取位置屬性作為GLuint。

  • 3)在這里,您可以利用MemoryLayout枚舉來獲取步長,即在數組中Vertex類型的item的大?。ㄒ宰止潪閱挝唬?/p>

  • 4)要獲取與頂點顏色對應的變量的內存偏移量,再次使用MemoryLayout枚舉,除非這次指定您希望GLfloat的步幅乘以3。這對應于Vertex結構中的x,y和z變量。

  • 5)最后,您需要將偏移量轉換為所需的類型:UnsafeRawPointer。

準備好一些輔助常量后,您就可以創建緩沖區并通過VAO進行繪制。

1. Creating VAO Buffers - 創建VAO緩沖

setupGL()中添加的常量后面添加以下代碼:

// 1
glGenVertexArraysOES(1, &vao)
// 2
glBindVertexArrayOES(vao)

第一行要求OpenGL生成或創建新的VAO。 該方法需要兩個參數:第一個是要生成的VAO的數量 - 在這個例子中是1 - 而第二個參數指向GLuint的指針,其中它將存儲生成的對象的ID。

在第二行中,您告訴OpenGL綁定您創建并存儲在vao變量中的VAO,并且任何即將進行的配置頂點屬性指針的調用都應存儲在此VAO中。 在進行繪制調用之前,OpenGL將使用您的VAO直到您解除綁定或綁定另一個VAO。

使用VAOs可以增加更多代碼,但是通過不必編寫代碼行來繪制甚至最簡單的幾何圖形所需的所有內容,這將節省大量時間。

創建并綁定VAO后,就可以創建和設置VBO了。

2. Creating VBO Buffers - 創建VBO對象

繼續在setupGL()的末尾添加此代碼:

glGenBuffers(1, &vbo)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vbo)
glBufferData(GLenum(GL_ARRAY_BUFFER), // 1
             Vertices.size(),         // 2
             Vertices,                // 3
             GLenum(GL_STATIC_DRAW))  // 4

與VAO一樣,glGenBuffers告訴OpenGL你想要生成一個VBO并將其標識符存儲在vbo變量中。

創建VBO之后,現在將其綁定為glBindBuffer調用中的當前VBO。綁定緩沖區的方法需要緩沖區類型和緩沖區標識符。 GL_ARRAY_BUFFER用于指定綁定頂點緩沖區,并且因為它需要GLenum類型的值。

glBufferData的調用是將所有頂點信息傳遞給OpenGL的地方。此方法需要四個參數:

  • 1)指示傳遞數據的緩沖區。
  • 2)指定數據的大小(以字節為單位)。在這個例子中,您在之前編寫的Array上使用size()輔助方法。
  • 3)您要使用的實際數據。
  • 4)告訴OpenGL您希望GPU如何管理數據。在這個例子中,您使用GL_STATIC_DRAW,因為傳遞給圖形卡的數據很少會發生變化(如果有的話)。這允許OpenGL針對給定場景進一步優化。

到目前為止,您可能已經注意到在Swift中使用OpenGL有一種模式,必須將某些變量或參數轉換為特定于OpenGL的類型。 這些是類型別名,沒有什么可以讓你擔心。 它使你的代碼最初看起來更長或更難閱讀,但是一旦你進入事物流程就不難理解。

您現在已將所有頂點的顏色和位置數據傳遞給GPU。 但是當你要求它在屏幕上繪制所有數據時,你仍然需要告訴OpenGL如何解釋這些數據。 為此,請在setupGL()的末尾添加此代碼:

glEnableVertexAttribArray(vertexAttribPosition)
glVertexAttribPointer(vertexAttribPosition,       // 1 
                      3,                          // 2
                      GLenum(GL_FLOAT),           // 3
                      GLboolean(UInt8(GL_FALSE)), // 4 
                      GLsizei(vertexSize),        // 5
                      nil)                        // 6
    
glEnableVertexAttribArray(vertexAttribColor)
glVertexAttribPointer(vertexAttribColor, 
                      4, 
                      GLenum(GL_FLOAT), 
                      GLboolean(UInt8(GL_FALSE)), 
                      GLsizei(vertexSize), 
                      colorOffsetPointer)

您會看到另一組非常相似的方法調用。 這是每次要做的事情,以及他們采取的參數。 在您告訴OpenGL解釋您的數據之前,您需要首先告訴它它甚至是什么解釋。

glEnableVertexAttribArray的調用啟用了positionvertex屬性,以便在下一行代碼中,OpenGL知道此數據是用于幾何的位置。

glVertexAttribPointer采用六個參數,以便OpenGL了解您的數據。 這是每個參數的作用:

  • 1)指定要設置的屬性名稱。 您可以使用先前在方法中設置的常量。
  • 2)指定每個頂點存在多少個值。 如果你回顧一下Vertex結構,你會看到,對于這個位置,有三個GLfloat(x,y,z),對于顏色,有四個GLfloat(r,g,b,a)。
  • 3)指定每個值的類型,它對于位置和顏色都是浮點數。
  • 4)指定是否要對數據進行規范化。 這幾乎總是設置為false。
  • 5)步幅stride的大小,這是一種奇特的方式,表示“包含每個頂點數據的數據結構體的大小,當它在數組中時?!痹谶@里傳遞vertexSize
  • 6)位置數據的偏移量。 位置數據位于Vertices數組的最開頭,這就是該值為nil的原因。

glEnableVertexttribArrayglVertexAttribPointer的第二組調用是相同的,除了你指定有四個顏色組件(r,g,b,a),并且你傳遞一個指針,傳遞一個指針用于頂點數組書中每個頂點Vertices的顏色內存的偏移量。

隨著VBO及其數據準備就緒,是時候通過使用EBO告訴OpenGL您的索引indices了。 這將告訴OpenGL要繪制哪些頂點以及以什么順序繪制。

3. Creating EBO Buffers - 創建EBO緩沖

setupGL()的底部添加以下代碼:

glGenBuffers(1, &ebo)
glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), ebo)
glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), 
             Indices.size(), 
             Indices, 
             GLenum(GL_STATIC_DRAW))

這段代碼看起來應該很熟悉。 它與您用于VBO的內容完全相同。 首先生成一個緩沖區并將其標識符存儲在ebo變量中,然后將此緩沖區綁定到GL_ELEMENT_ARRAY_BUFFER,最后將Indices數組數據傳遞給緩沖區。

要添加到此方法的最后一部分代碼如下:

glBindVertexArrayOES(0)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), 0)

首先,解除綁定(分離)VAO,以便在此VAO上不進行任何進一步調用以設置緩沖區,屬性指針或其他內容。 對頂點和元素緩沖區對象也是如此。 雖然沒有必要,但解除綁定是一種很好的做法,可以通過不將設置和配置與錯誤的對象相關聯來幫助您避免將來出現邏輯錯誤。

構建您的項目以確保它編譯可以通過。


Tidying Up - 清理

您已經創建了幾個需要清理的緩沖區。 在ViewController中添加以下方法來執行此操作:

private func tearDownGL() {
  EAGLContext.setCurrent(context)
    
  glDeleteBuffers(1, &vao)
  glDeleteBuffers(1, &vbo)
  glDeleteBuffers(1, &ebo)
        
  EAGLContext.setCurrent(nil)
        
  context = nil
}

使用上面的代碼,您:

  • EAGLContext設置為您的上下文 - 您一直在使用的那個。
  • 刪除VBO,EBO和VAO。
  • 取出上下文并將上下文變量設置為nil以防止其他任何操作。

現在,添加以下方法:

deinit {
  tearDownGL()
}

這是deinitializer,它簡單地稱為拆解方法。

構建并運行項目 - 注意到還是相同的灰色屏幕? 關于圖形編程和使用OpenGL的事情是,在看到事物之前,它通常需要大量的初始設置代碼。


Introducing GLKBaseEffect - 引入GLKBaseEffect

現在是下一個話題的時候了:著色器Shaders

現代OpenGL使用所謂的可編程管道programmable pipeline,使開發人員可以完全控制每個像素的渲染方式。 這為您提供了驚人的靈活性,并允許渲染一些華麗的場景和效果。 然而,權衡的是程序員的工作量比過去多。 著色器是用GLSL(OpenGL著色語言)編寫的,需要先編譯才能使用。

使用GLKBaseEffect,您不必擔心編寫著色器。 它可以幫助您用很少的代碼實現基本的照明和著色效果。

要創建效果,請將此變量添加到ViewController:

private var effect = GLKBaseEffect()

然后,在glkView(_ view:,drawIn rect :)的底部添加這一行:

effect.prepareToDraw()

這一行代碼為您綁定和編譯著色器,它在幕后完成所有操作而無需編寫任何GLSLOpenGL代碼。 很酷,對吧? Build您的項目以確保它編譯。

準備好緩沖區和效果后,您現在還需要三行代碼來告訴OpenGL要繪制什么以及如何繪制它。 在剛剛添加的行的正下方添加以下行:

glBindVertexArrayOES(vao);
glDrawElements(GLenum(GL_TRIANGLES),     // 1
               GLsizei(Indices.count),   // 2
               GLenum(GL_UNSIGNED_BYTE), // 3
               nil)                      // 4
glBindVertexArrayOES(0)

這是每個方法調用的部分。 對glBindVertexArrayOES的調用綁定(附加)您的VAO,以便OpenGL使用它 - 以及所有設置和配置 - 用于即將進行的繪制調用。

glDrawElements()是執行繪圖的調用,它需要四個參數。 以下是他們每個的作用:

  • 1)這告訴OpenGL你想要繪制什么。 在這里,您可以使用GL_TRIANGLES參數強制轉換為GLenum來指定三角形。
  • 2)告訴OpenGL要繪制多少個頂點。 它被轉換為GLsizei,因為這是方法所期望的。
  • 3)指定每個索引中包含的值的類型。 您使用GL_UNSIGNED_BYTE因為IndicesGLubyte元素的數組。
  • 4)指定緩沖區內的偏移量。 由于您使用的是EBO,因此該值為nil。

真相的時刻到了! 是時候構建和運行你的項目了,如下所示:

不錯,但也不是你所期待的。 這不是正方形,也不是旋轉。 這是怎么回事? 好吧,沒有為GLKBaseEffect設置屬性,特別是投影和模型視圖矩陣的transform屬性。


Setting Up the Geometry - 設置幾何

首先看一下一些理論。

投影矩陣projection matrix是告訴GPU如何在2D平面上渲染3D幾何體的方式。可以把它想象成從你的眼睛通過屏幕中的每個像素畫出一串線。繪制到屏幕的像素由每條線擊中的最前面的3D對象確定。

GLKit有一些方便的功能來設置投影矩陣。您將要使用的那個允許您指定沿y軸的視野,縱橫比以及近和遠平面。

視野field of view就像一個相機鏡頭。一個小視場(例如10)就像一個長焦鏡頭 - 它通過“拉”它們靠近你來放大圖像。大視場(例如100)就像一個廣角鏡頭 - 它使一切看起來更遠。用于此的典型值約為65-75。

寬高比aspect ratio是您要渲染的寬高比(例如,視圖的寬高比)。它將其與用于y軸的視場結合使用,以確定沿x軸的視野。

近處和遠處的平面是場景中“可視viewable”體積的邊界框。如果某物比近平面更接近眼睛,或者遠離遠處平面,則不會渲染。

注意:這是一個常見的問題 - 你嘗試渲染一些沒有出現的東西。 要檢查的一件事是物體實際上位于近平面和遠平面之間。

將以下代碼添加到glkViewControllerUpdate(_ :)的底部:

// 1
let aspect = fabsf(Float(view.bounds.size.width) / Float(view.bounds.size.height))
// 2
let projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 4.0, 10.0)
// 3
effect.transform.projectionMatrix = projectionMatrix

下面詳細看一下這幾個步驟:

  • 1)計算GLKView的縱橫比。
  • 2)使用GLKit數學庫中的內置輔助函數來創建透視矩陣; 你所要做的就是傳遞上面討論的參數。 您將近平面設置為距離眼睛四個單位,將遠平面設置為10個單位。
  • 3)在效果effecttransform屬性上設置投影矩陣projection matrix

您需要在效果上再設置一個屬性 - modelViewMatrix。 這是應用于效果渲染的任何幾何體的變換。

再一次,GLKit數學庫帶來了一些方便的函數,即使您對矩陣數學知之甚少,也能輕松執行平移,旋轉和縮放。 將以下行添加到glkViewControllerUpdate(_ controller :)的底部:

// 1
var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -6.0)
// 2
rotation += 90 * Float(timeSinceLastUpdate)
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(rotation), 0, 0, 1)
// 3
effect.transform.modelviewMatrix = modelViewMatrix

如果回頭看看為方塊設置頂點的位置,請記住每個頂點的z坐標為0。如果您嘗試使用此透視矩陣渲染它,它將不會顯示,因為它比近平面更靠近眼睛。

以下是使用上述代碼修復問題的方法:

  • 1)您需要做的第一件事是向后移動對象。 在第一行中,您使用GLKMatrix4MakeTranslation函數創建一個向后平移六個單位的矩陣。
  • 2)接下來,您要使立方體旋轉。 您將增加一個實例變量,該變量將在一秒鐘內增加,用于跟蹤當前旋轉并使用GLKMatrix4Rotate方法通過旋轉來更改當前變換。 它需要弧度,因此您可以使用GLKMathDegreesToRadians方法進行轉換。
  • 3)最后,在效果的transform屬性上設置模型視圖矩陣。

最后,將以下屬性添加到類的頂部:

private var rotation: Float = 0.0

最后一次構建并運行應用程序并查看結果:

旋轉的方塊!完美!

到這里,您已經了解了重要的概念和技術,如頂點和元素緩沖區,頂點屬性,頂點數組對象和轉換。 GLKBaseEffect對于反射貼圖,光照,霧等有很多效果,并且使用紋理加載類將紋理應用于幾何體。

后記

本篇主要講述了一個詳細示例和說明,感興趣的給個贊或者關注~~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容