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