版本記錄
版本號 | 時間 |
---|---|
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
的調用啟用了position
的vertex
屬性,以便在下一行代碼中,OpenGL知道此數據是用于幾何的位置。
glVertexAttribPointer
采用六個參數,以便OpenGL了解您的數據。 這是每個參數的作用:
- 1)指定要設置的屬性名稱。 您可以使用先前在方法中設置的常量。
- 2)指定每個頂點存在多少個值。 如果你回顧一下Vertex結構,你會看到,對于這個位置,有三個
GLfloat(x,y,z)
,對于顏色,有四個GLfloat(r,g,b,a)
。 - 3)指定每個值的類型,它對于位置和顏色都是浮點數。
- 4)指定是否要對數據進行規范化。 這幾乎總是設置為false。
- 5)步幅stride的大小,這是一種奇特的方式,表示“包含每個頂點數據的數據結構體的大小,當它在數組中時?!痹谶@里傳遞
vertexSize
。 - 6)位置數據的偏移量。 位置數據位于
Vertices
數組的最開頭,這就是該值為nil的原因。
對glEnableVertexttribArray
和glVertexAttribPointer
的第二組調用是相同的,除了你指定有四個顏色組件(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()
這一行代碼為您綁定和編譯著色器,它在幕后完成所有操作而無需編寫任何GLSL
或OpenGL
代碼。 很酷,對吧? 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
因為Indices
是GLubyte
元素的數組。 - 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)在效果
effect
的transform
屬性上設置投影矩陣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
對于反射貼圖,光照,霧等有很多效果,并且使用紋理加載類將紋理應用于幾何體。
后記
本篇主要講述了一個詳細示例和說明,感興趣的給個贊或者關注~~~~