iOS開發(fā)學習OpenGL ES系列 -- 第一個OpenGL ES項目

在第一個項目學習之前我們需要先了解一下iOS開發(fā)中管理OpenGL ES渲染的視圖控制器 - GLKViewController

先看一下蘋果官方概述:

A GLKViewController object works in conjunction with a GLKView object to display frames of animation in the view, and also provides standard view controller functionality.
To use this class, allocate and initialize a new GLKViewController subclass and set its view property to point to a GLKView object. Then, configure the view controller’s preferredFramesPerSecond property to the desired frame rate your application requires. You can set a delegate or configure other properties on the view controller, such as whether the animation loop is automatically paused or resumed when the application moves into the background.

一個GLKViewController對象與GLKView對象在視圖中顯示動畫幀作品的同時,也提供了標準視圖控制器的功能。使用這個類,分配和初始化一個新的GLKViewController類并設置其視圖(View)屬性指向一個GLKView對象。然后,配置視圖控制器的preferredframespersecond屬性設置應用程序要求所需的幀速率。您可以在視圖控制器上設置委托或配置其他屬性,例如當應用程序移動到后臺時是否自動暫?;蚧謴蛣赢嬔h(huán)。

When you set the view property to point to a GLKView object, if the view does not already have a delegate, then the view controller is automatically set as the view’s delegate.

如果你設置view屬性指向一個GLKView對象,并且view沒有設置代理,那么當前控制器會自動成為view的代理

When active, rendering loop automatically updates the view’s contents each time a new frame must be displayed. Each frame is rendered by the view controller using these steps:

  • The view controller calls its delegate’s glkViewControllerUpdate: method. Your delegate should update frame data that does not involve rendering the results to the screen.
  • The view controller calls its view’s display method. Your view should redraw the frame.

當運行時,渲染循環(huán)每次更新一個新的幀時會自動更新視圖的內容。每個幀使用這些步驟由視圖控制器呈現(xiàn):
視圖控制器調用代理方法glkViewControllerUpdate:,代理應該更新不需要將結果呈現(xiàn)到屏幕上的數(shù)據(jù)。
視圖控制器調用視圖的disolay方法。視圖重新繪制。

Subclassing Notes
Your application should subclass GLKViewController and override the viewDidLoad and viewDidUnload methods. Your viewDidLoad method should set up your context and any drawable properties and can perform other resource allocation and initialization. Similarly, your class’s viewDidUnload method should delete the drawable object and free any unneeded resources.
As an alternative to implementing a glkViewControllerUpdate: method in a delegate, your subclass can provide an update method instead. The method must have the following signature:

子類注意:
你的應用程序應該集成自GLKViewController,并且重寫viewDidLoad和viewDidUnload方法。在viewDidLoad中設置視圖的上下文和初始化任何繪畫可執(zhí)行屬性。同樣,在viewDidUnload方法中可以刪除繪制對象和釋放不必要的資源。
glkViewControllerUpdate作為一個可供選擇實現(xiàn)的方法,在代理方法中,子類可以提供一個更新的方法代替,方法必須具備以前特征: - (void)update

屬性:
preferredFramesPerSecond: 設置視圖更新內容的速率, 默認值為 30

framesPerSecond: 視圖更新內容實際速率,只讀屬性

delegate: 設置代理對象

paused:bool值,設置暫停

pauseOnWillResignActive: bool值,表示當控制器掛起狀態(tài)時是否自動暫停渲染循環(huán),默認YES

resumeOnDidBecomeActive: bool值,表示當控制器激活時是否自動恢復渲染循環(huán),默認YES

獲取關于視圖更新的信息(可讀屬性)
framesDisplayed:視圖控制器自創(chuàng)建以來已發(fā)送的幀更新數(shù)。
timeSinceFirstResume:自第一次視圖控制器恢復發(fā)送更新事件以來所經(jīng)過的時間量。
timeSinceLastResume:自上一次視圖控制器恢復發(fā)送更新事件以來所經(jīng)過的時間量。
timeSinceLastUpdate:自從上次視圖控制器調用代理方法glkviewcontrollerupdate經(jīng)過的時間量
timeSinceLastDraw:自上次視圖控制器調用視圖的display方法以來所經(jīng)過的時間量。

OpenGL ES渲染視圖
GLKView:使用opengl繪制內容的默認實現(xiàn)視圖

剩下兩個代理屬性:GLKViewDelegate和GLKViewControllerDelegate

關于GLKViewController就說這么多,接下來的OpenGL ES學習我們都是基于GLKViewController環(huán)境實現(xiàn)的,因為蘋果的封裝我們可以省掉一些基本的配置,比如生成默認的FrameBufferObject

開始項目:
在新建工程完畢,我們需要做幾處修改。首先導入GLKit,修改當前控制器集成自GLKViewController,如下:

然后修改Main.storyboard中控制器view的class為GLKView,如下:

接下來在viewDidLoad中將它的View屬性轉為GLKView類型。OpenGL ES的上下文不僅會保存OpenGL ES的狀態(tài),還會控制GPU去執(zhí)行渲染運算。所以需要初始化一個內建的EAGLContext類的實例對象,這個對象會封裝一個特定于某個平臺的OpenGL ES上下文。另外在任何其他的OpenGL ES配置或者渲染發(fā)生之前,應用的GLKView實例的上下文屬性都需要設置為當前。

下面是代碼實現(xiàn):

// 將當前view強制轉換為GLKView
GLKView *view = (GLKView *)self.view;

// 初始化當前視圖上下文
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

// 在任何其他的OpenGL ES配置或者渲染發(fā)生前,應用的GLKView實例的上下文屬性都需要設置為當前
[EAGLContext setCurrentContext:view.context];

環(huán)境配置完畢,接下來需要實例化一個GLKBaseEffect對象:

// 定義全局屬性
@property (nonatomic, strong) GLKBaseEffect *baseEffect;

// 初始化
self.baseEffect = [[GLKBaseEffect alloc] init];

// 啟用constantColor屬性
self.baseEffect.useConstantColor = GL_TRUE;

// 設置渲染顏色(紅色)
self.baseEffect.constantColor = GLKVector4Make(1.0, 0.0, 0.0, 1.0); 

GLKBaseEffect類提供了不依賴所使用的OpenGL ES版本的控制OpenGL ES渲染方法,會在需要的時候自動地構建GPU程序并極大地簡化本例。也就是說,如果我們定義數(shù)據(jù)渲染圖形,那么就需要定義兩個著色器程序來處理數(shù)據(jù)才能渲染出圖形來。而這里并沒有寫著色器程序,用的是GLKit框架提供的GLKBaseEffect類來自動生成著色器程序。

每當一個GLKView實例需要被重繪時,它都會讓保存在視圖的上下文屬性中的OpenGL ES的上下文成為當前上下文。如果需要的話,GLKView實例會綁定與一個Core Animation層分享的幀緩存,執(zhí)行其他的標準OpenGL ES配置,并發(fā)送一個消息來調用glkView:(GLKView *)view drawInRect:(CGRect)rect方法。

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    
    // 定義頂點坐標
    static GLfloat vertices[] = {
        0.0f,  0.5f,  0,
       -0.5f, -0.5f,  0,
        0.5f, -0.5f,  0,
    };
    
    /*
     “prepareToDraw”方法,是讓“效果Effect”針對當前“Context”的狀態(tài)進行一些配置,
     它始終把“GL_TEXTURE_PROGRAM”狀態(tài)定位到“Effect”對象的著色器上。
     */
    [self.baseEffect prepareToDraw];
    
    // 前兩行為渲染前的“清除”操作,清除顏色緩沖區(qū)和深度緩沖區(qū)中的內容。
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    
    // 啟動頂點緩存渲染操作
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    
    /*
     發(fā)送頂點數(shù)據(jù)到GPU內存
     第一個參數(shù):指示當前綁定的緩存包含每個頂點的位置信息,也就是給哪個頂點屬性賦值
     第二個參數(shù):指定每個位置又3個數(shù)據(jù)部分
     第三個參數(shù):告訴OpenGL ES每個部分都保存為一個浮點類型的值
     第四個參數(shù):告訴OpenGL ES小數(shù)點固定數(shù)據(jù)是否可以被改變,本例中沒有使用小數(shù)點固定的數(shù)據(jù),因此賦值為GL_FALSE
     第五個參數(shù):可以稱為"步幅",指定了沒哥頂點的保存需要多少個字節(jié)。簡單點就是指定了GPU從一個頂點的內存礙事轉到下一個頂點的內存開始位置需要跳過多少字節(jié)
     第六個參數(shù):告訴OpenGL ES可以從當前綁定的頂點緩存的開始位置訪問頂點數(shù)據(jù)
     */
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_TRUE, 6 * sizeof(GLfloat), (char *)vertices);
    
    /*
     告訴OpenGL ES如何使用緩存數(shù)據(jù)之后就可以調用glDrawArrays()函數(shù)通過這些數(shù)據(jù)來繪制圖形了
     第一個參數(shù):告訴OpenGL ES怎么處理在綁定的頂點緩存內的頂點數(shù)據(jù)。本例中屎指示OpenGL ES去渲染三角形
     第二個參數(shù):指定緩存內的需要渲染的第一個頂點的位置
     第三個參數(shù):需要渲染的頂點數(shù)量
     */
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

最后運行就可以看到你想要的圖形了:

背景色為我們設置的黑色:

glClearColor(0.0, 0.0, 0.0, 1.0);

三角形的顏色為紅色:

self.baseEffect.constantColor = GLKVector4Make(1.0, 0.0, 0.0, 1.0); 

上面我們是設置baseEffect對象的constantColor屬性來設置圖形的渲染顏色。下面我們換一種方式來設置圖形顏色,既然坐標可以通過頂點數(shù)組發(fā)送,那么顏色值也可以,修改頂點坐標數(shù)組:

static GLfloat vertices[] = {
        0.0,   0.5f,  0,  1,  0,  0, 
       -0.5f, -0.5f,  0,  0,  1,  0,
        0.5f, -0.5f,  0,  0,  0,  1,
};
// 每個坐標點的顏色都不一樣,等會可以看看最終渲染出來是什么樣的

激活GLKVertexAttribColor屬性和賦值

glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_TRUE, 6 * sizeof(GLfloat), (char *)vertices + 3 * sizeof(GLfloat));

glVertexAttribPointer函數(shù)在上面已經(jīng)對每個參數(shù)做了解釋,現(xiàn)在每個頂點不止X、Y、Z三個坐標值,還加了R、G、B三個顏色值。所以第五個參數(shù)為6 * sizeof(GLfloat)

每個頂點數(shù)據(jù)前面三個為坐標值,如果要取顏色值,就需要往后跳過3個位置,所以第六個參數(shù)為(char *)vertices + 3 * sizeof(GLfloat),從每個頂點的開始位置跳過3個sizeof(GLfloat)的位置再取就是顏色值了。

所以現(xiàn)在glkView:(GLKView *)view drawInRect方法為:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

    [self.baseEffect prepareToDraw];
    
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    
    static GLfloat vertices[] = {
        0.0,   0.5f,  0,  1,  0,  0, 
       -0.5f, -0.5f,  0,  0,  1,  0,
        0.5f, -0.5f,  0,  0,  0,  1,
    };
    
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_TRUE, 6 * sizeof(GLfloat), (char *)vertices);
    
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_TRUE, 6 * sizeof(GLfloat), (char *)vertices + 3 * sizeof(GLfloat));
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

最后效果:

這個圖片可能不是你所期望的那種,因為我們只提供了3個顏色,而不是我們現(xiàn)在看到的大調色板。這是在片段著色器中進行的所謂片段插值(Fragment Interpolation)的結果。當渲染一個三角形時,光柵化(Rasterization)階段通常會造成比原指定頂點更多的片段。光柵會根據(jù)每個片段在三角形形狀上所處相對位置決定這些片段的位置。
基于這些位置,它會插值(Interpolate)所有片段著色器的輸入變量。比如說,我們有一個線段,上面的端點是綠色的,下面的端點是藍色的。如果一個片段著色器在線段的70%的位置運行,它的顏色輸入屬性就會是一個綠色和藍色的線性結合;更精確地說就是30%藍 + 70%綠。
這正是在這個三角形中發(fā)生了什么。我們有3個頂點,和相應的3個顏色,從這個三角形的像素來看它可能包含50000左右的片段,片段著色器為這些像素進行插值顏色。如果你仔細看這些顏色就應該能明白了:紅首先變成到紫再變?yōu)樗{色。片段插值會被應用到片段著色器的所有輸入屬性上。(這一段是在學習OpenGL ES資料上查到的,如果想要深入了解插值的可以自行搜索相關資料了)

看完這篇,你應該可以繪制出這個三角形了。如果還有不懂的歡迎留言討論。近期在對OpenGL ES的學習之后才寫下記錄文章,如果有講解不到位或者是錯誤的地方還請大家留言指正,謝謝!

最后附上源碼 LearningOpenGL ES GitHub

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容