前言:
這篇文章是作者iOS開發之OpenGL ES系列文章的第二篇,將結合一個使用OpenGL ES渲染一個三角形的Demo來講解GLKit,以及提供給GPU的數據都應該放入緩存中,為緩存提供數據的7個步驟,雖然只有一個三角形,但是執行OpenGL ES渲染的步驟與更復雜場景的步驟是一致的。
注:(如果你覺得我的文章有所幫助,點個喜歡或關注。您的每一份鼓勵都是我前進的動力??)
本文系列第一篇:初見篇已經完結,感興趣可以看看:
iOS開發之OpenGL ES—初見
正文:
GLKit是蘋果iOS 5引入的一個為簡化OpenGL ES的使用的框架,它為OpenGL ES的使用提供了相關的類和函數,GLKit是Cocoa Touch以及多個其他的框架(包含UIKit)的一部分。而GLKView和GLKViewController類名字中的GLK前綴表明這些類是GLKit框架的一部分。
GLKViewController類是支持OpenGL ES特有的行為和動畫時的UIViewController的內建子類。
GLKView是Cocoa Touch UIView類的內建子類。GLKView簡化了通過用Core Animation層來自動創建并管理幀緩存和渲染緩存共享內存所需要做的工作。GLKView相關的GLKViewController實例是視圖的委托并接收當視圖需要重繪時的消息。
OpenGL ES 01-GLKit示例:
ViewController類的interface
- 通過#import編譯指令導入GLKit框架
- 將ViewController繼承的UIViewController改為GLKViewController,使其繼承GLKViewController的基本功能(如:接收當視圖需要重繪時的消息),GLKViewController又從它的超類繼承了很多功能。
- vertexBufferID變量保存了用于盛放本例中用到的頂點數據的緩存的OpenGL ES標識符。
- baseEffect屬性聲明了一個GLKBaseEffect實例的指針。GLKBaseEffect是GLKit提供的另一個內建類。GLKBaseEffect的存在是為了簡化OpenGL ES的很多常用的操作。GLKBaseEffect隱藏了iOS設備支持的多了OPenGL ES版本間的差異。在應用中使用GLKBaseEffect能減少需要編寫的代碼量。
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
{
GLuint vertexBufferID;
}
@property (nonatomic,strong) GLKBaseEffect *baseEffect;
@end
ViewController類的實現
- SceneVertex是一個C結構體,用來保存一個GLKVector3類型的成員positionCoords。GLKit的GLKVector3類型保存了3個坐標:X、Y和Z。
- vertices變量是一個用頂點數據初始化的普通C數組,這個變量用來定義一個三角形。這個例子的頂點坐標是挑選出來的,因為默認的用于OpenGL上下文的可見坐標系是分別沿著X、Y、Z軸從-1.0延伸到1.0的。
typedef struct{
GLKVector3 positionCoords;
}SceneVertex;
static const SceneVertex vertices[] = {
{{-0.5f,-0.5f,0.0}},//lower left corner
{{0.5f,-0.5f,0.0}},//lower right corner
{{-0.5f,0.5f,0.0}}//upper left corner
};
- viewDidLoad方法為OpenGL ES提供三角形的頂點數據。此方法會將它繼承的view屬性的值轉換成GLKView類型。類似ViewController的GLKViewController的子類只能與GLKView實例或者GLKView的子類的實例一起工作。
- OpenGL ES的上下文不僅會保存OpenGL ES的狀態,還會控制GPU去執行渲染運算。在這里分配并初始化一個內建的EAGLContext類的實例,這個實例會封裝夜歌特定于某個平臺的OpenGL ES上下文,在任何其他的OpenGL ES配置或者渲染發生之前,應用的GLKView實例的上下文屬性都需要設置為當前。EAGLContext實例既支持OpenGL ES 1.1又支持OpenGL ES 2.0和OpenGL ES 3.0。本例使用的是2.0版本。EAGLContext的方法“+setCurrentContext:”會為接下來的OpenGL ES運算設置將會用到的上下文,而“-initWithAPI:”方法傳入一個kEAGLRenderingAPIOpenGLES2常量將它初始化為OpenGL ES 2.0。
- baseEffect屬性為一個新分配并初始化的GLKBaseEffect類型的實例,useConstantColor屬性說明使用恒定不變的顏色,而constantColor定義了這種恒定顏色的顏色值。GLKVector4Make是GLKit中定義的用于保存4個顏色值的C數據結構體。
- glClearColor()函數設置當前OpenGL ES的上下文的“清除顏色”為不透明白色,清除顏色由RGBA顏色元素值組成,用于在上下文的幀緩存被清除時初始化每個像素的顏色值。
- 上一篇文章介紹了用于CPU和GPU控制的內存之間交換數據的緩存的概念。用于定義要緩存的三角形的頂點位置數據必須要發送到GPU來渲染。創建并使用一個用于保存頂點數據的頂點數組屬性數組緩存。前三步驟如下:
- 為緩存生成一個獨一無二的標識符。
glGenBuffers(1, &vertexBufferID)
參數1:要生成緩存標識符的數量
參數2:指向生成的標識符的內存保存位置的指針 - 為接下來的運算綁定緩存。
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID)
參數1:用于指定要綁定那一種類型的緩存,OPenGL ES2.0對于glbindBuffer()的實現只支持兩種類型的緩存
GL_ARRAY_BUFFER:頂點緩沖區對象
GL_ELEMENT_ARRAY_BUFFER:頂點索引緩沖區對象
參數2:要綁定緩存的標識符 - 復制數據到緩存中。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
參數1:指定要更新當前上下文中所綁定的是哪一種緩存
參數2:指定要復制這個緩存字節的數量
參數3:復制的字節的地址
參數4:緩存在未來的運算中可能將會被怎樣使用
GL_STATIC_DRAW提示會告訴上下文,緩存中的內容適合復制到GPU控制的內存,因為很少對其修改
GL_DYNAMIC_DRAW會告訴上下文,緩存內的數據會頻繁改變,同時提示OpenGL ES以不同的方式來處理緩存的存儲
viewDidLoad {}中的整體代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
GLKView *view = (GLKView *)self.view;
//使用NSAssert()函數的一個運行時檢查會驗證self.view是否為正確的類型
NSAssert([view isKindOfClass:[GLKView class]], @"viewcontroller’s view is not a GLKView");
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];//分配一個新的EAGLContext的實例,并將它初始化為OpenGL ES 2.0
[EAGLContext setCurrentContext:view.context];//在任何其他的OpenGL ES配置或者渲染之前,應用的GLKview實例的上下文屬性都要設置為當前
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(0.4f,//red
0.6f,//green
0.2f,//blue
1.0f);//alpha
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//設置當前OpenGL ES的上下文的“清除顏色”為不透明白色
/*
*參數一:要生成緩存標識符的數量
*參數二:指向生成的標識符的內存保存位置的指針
*/
glGenBuffers(1, &vertexBufferID);//為緩存生成一個獨一無二的標識符
/*
*參數一:用于指定要綁定那一種類型的緩存,OPenGL ES2.0對于glbindBuffer()的實現只支持兩種類型的緩存
GL_ARRAY_BUFFER:頂點緩沖區對象
GL_ELEMENT_ARRAY_BUFFER 頂點索引緩沖區對象
參數二:要綁定緩存的標識符
*/
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);//為接下來的應用綁定緩存
/*
*參數1:指定要更新當前上下文中所綁定的是哪一種緩存
*參數2:指定要復制這個緩存字節的數量
*參數3:復制的字節的地址
*參數4:緩存在未來的運算中可能將會被怎樣使用
GL_STATIC_DRAW提示會告訴上下文,緩存中的內容適合復制到GPU控制的內存,因為很少對其修改
GL_DYNAMIC_DRAW會告訴上下文,緩存內的數據會頻繁改變,同時提示OpenGL ES以不同的方式來處理緩存的存儲
*/
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
- 每當一個GLKView實例需要被重繪時,他都會讓保存在視圖的上下文屬性中的OpenGL ES上下文成為當前上下文。如果需要的話,GLKView實例會綁定一個與與一個Core Animation層分享的幀緩存,執行其他的標準的OpenGL ES配置,并發送一個消息來調用ViewController的-glkView: drawInRect:方法,-glkView: drawInRect:是GLKView的委托方法。
- 委托方法的實現[self.baseEffect prepareToDraw],告訴baseEffect準備好當前的OpenGL ES的上下文,以便為使用baseEffect生成的屬性和Shading Language程序的繪圖做好準備。
- glClear()來設置當前綁定的幀緩存的像素顏色渲染緩存中的每一個像素的顏色為前面使用glClearColor()函數設定的值。glClear()函數會有效的設置幀緩存中的每一個像素的顏色為背景色。
- 使用緩存的前三步已經在- viewDidLoad方法中被執行了。-glkView: drawInRect:方法會執行剩下的幾個步驟:
- 啟動。
通過glEnableVertexAttribArray()來啟動頂點緩存渲染操作。OpenGL ES 所支持的每一個渲染操作都可以單獨的使用保存在當前OpenGL ES上下文中的設置來開啟或關閉。 - 設置指針。
glVertexAttribPointer()函數會告訴OpenGL ES頂點數據在哪里,以及怎么解釋為每個頂點保存的數據。
glVertexAttribPointer(GLKVertexAttribPosition, 3
, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL);
參數1:指示當前綁定的緩存包含每個頂點的位置信息
參數2:指示每個位置有三個部分
參數3:告訴OpenGL ES每個部分都保存為一個浮點類型的值
參數4:告訴OPenGL ES小數點固定數據是否可以被改變
參數5:叫做步幅,他指定了每個頂點的保存需要多少個字節
參數6:告訴OpenGL ES可以從當前綁定的頂點緩存的位置訪問頂點數據 - 繪圖。
通過調用glDrawArrays()來執行繪圖。
glDrawArrays(GL_TRIANGLES, 0, 3);
參數1:告訴GPU怎么處理在綁定的頂點緩存內的頂點數據
GL_POINTS //把每一個頂點作為一個點進行處理,頂點n即定義了點n,共繪制N個點
GL_LINES //把每一個頂點作為一個獨立的線段,頂點2n-1和2n之間共定義了n條線段,總共繪制N/2條線段
GL_LINE_LOOP //繪制從第一個頂點到最后一個頂點依次相連的一組線段,然后最后一個頂點和第一個頂點相連,第n和n+1個頂點定義了線段n,總共繪制n條線段
GL_LINE_STRIP //繪制從第一個頂點到最后一個頂點依次相連的一組線段,第n和n+1個頂點定義了線段n,總共繪制n-1條線段
GL_TRIANGLES //把每個頂點作為一個獨立的三角形,頂點3n-2、3n-1和3n定義了第n個三角形,總共繪制N/3個三角形
GL_TRIANGLE_STRIP //繪制一組相連的四邊形。每個四邊形是由一對頂點及其后給定的一對頂點共同確定的。頂點2n-1、2n、2n+2和2n+1定義了第n個四邊形,總共繪制N/2-1個四邊形
GL_TRIANGLE_FAN //繪制一組相連的三角形,三角形是由第一個頂點及其后給定的頂點確定,頂點1、n+1和n+2定義了第n個三角形,總共繪制N-2個三角形
參數2:需要渲染的第一個頂點
參數3:需要渲染的頂點的個數
-glkView: drawInRect:中的整體代碼如下:
//這個方法每幀都執行一次(循環執行),執行頻率與屏幕刷新率相同,iPhone屏幕的刷新頻率是60Hz
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
[self.baseEffect prepareToDraw];
glClear(GL_COLOR_BUFFER_BIT);
//啟動頂點緩存渲染操作
glEnableVertexAttribArray(GLKVertexAttribPosition);
/*告訴OpenGL ES頂點數據在哪里,以及解釋為每個頂點保存的數據
*參數1:指示當前綁定的緩存包含每個頂點的位置信息
*參數2:指示每個位置有三個部分
*參數3:告訴OpenGL ES每個部分都保存為一個浮點類型的值
*參數4:告訴OPenGL ES小數點固定數據是否可以被改變
*參數5:叫做步幅,他指定了每個頂點的保存需要多少個字節
*參數6:告訴OpenGL ES可以從當前綁定的頂點緩存的位置訪問頂點數據
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3
, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL);
/*繪圖
*參數1:告訴GPU怎么處理在綁定的頂點緩存內的頂點數據
GL_POINTS //把每一個頂點作為一個點進行處理,頂點n即定義了點n,共繪制N個點
GL_LINES //把每一個頂點作為一個獨立的線段,頂點2n-1和2n之間共定義了n條線段,總共繪制N/2條線段
GL_LINE_LOOP //繪制從第一個頂點到最后一個頂點依次相連的一組線段,然后最后一個頂點和第一個頂點相連,第n和n+1個頂點定義了線段n,總共繪制n條線段
GL_LINE_STRIP //繪制從第一個頂點到最后一個頂點依次相連的一組線段,第n和n+1個頂點定義了線段n,總共繪制n-1條線段
GL_TRIANGLES //把每個頂點作為一個獨立的三角形,頂點3n-2、3n-1和3n定義了第n個三角形,總共繪制N/3個三角形
GL_TRIANGLE_STRIP //繪制一組相連的四邊形。每個四邊形是由一對頂點及其后給定的一對頂點共同確定的。頂點2n-1、2n、2n+2和2n+1定義了第n個四邊形,總共繪制N/2-1個四邊形
GL_TRIANGLE_FAN //繪制一組相連的三角形,三角形是由第一個頂點及其后給定的頂點確定,頂點1、n+1和n+2定義了第n個三角形,總共繪制N-2個三角形
*參數2:需要渲染的第一個頂點
*參數3:需要渲染的頂點的個數
*/
glDrawArrays(GL_TRIANGLES, 0, 3);
- ViewController實現的最后一個方法是-(void)dealloc{},在這個函數里刪除不再需要的頂點緩存和上下文。設置vertexBufferID為0避免了在對應的緩存被刪除后還使用其無效的標識符。設置視圖的上下文屬性為nil并設置當前的上下文為nil,以便讓Cocoa Touch收回所有上下文使用的內存和其他資源。
-(void)dealloc{
GLKView *view = (GLKView *)self.view;
[EAGLContext setCurrentContext:view.context];
if (0!=vertexBufferID) {
glDeleteBuffers(1, &vertexBufferID);
//設置vertexBufferID為0避免了在對應的緩存被刪除后還使用其無效的標識符。
vertexBufferID = 0;
}
//設置視圖的上下文屬性為nil并設置當前的上下文為nil,以便讓Cocoa Touch收回所有上下文使用的內存和其他資源
((GLKView *)self.view).context = nil;
[EAGLContext setCurrentContext:nil];
}
源碼已上傳至fenglinyunshi-git,歡迎下載,并提出寶貴意見。
結語:
本文是iOS開發之OpenGL ES的第二篇文章,主要關于GLKit以及提供給GPU的數據都應該放入緩存中,為緩存提供數據的7個步驟。如果看后你有所收獲,那么我會非常高興,如果文中有不準確的地方還望提出指證,筆者將非常感謝!
未完待續 ...
業精于勤,荒于嬉;行成于思,毀于隨。