
前言
對于OpenGL ES,本人現(xiàn)在還是一個小白,所以我將用小白視角對OpenGL ES進行小白式的講解.希望能通過如此幫助更多的人.同時我要感謝一個人,那就是落影l(fā)oyinglin,落影大神關于OpenGL ES方面的知識寫的非常的詳細,大家可以去參考.好了,開始戰(zhàn)斗吧.
OpenGL ES簡介
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三維圖形 API 的子集,針對手機、PDA和游戲主機等嵌入式設備而設計。該API由Khronos集團定義推廣,Khronos是一個圖形軟硬件行業(yè)協(xié)會,該協(xié)會主要關注圖形和多媒體方面的開放標準。
OpenGL ES 是從 OpenGL 裁剪的定制而來的,去除了glBegin/glEnd,四邊形(GL_QUADS)、多邊形(GL_POLYGONS)等復雜圖元等許多非絕對必要的特性。經過多年發(fā)展,現(xiàn)在主要有兩個版本,OpenGL ES 1.x 針對固定管線硬件的,OpenGL ES 2.x 針對可編程管線硬件。OpenGL ES 1.0 是以 OpenGL 1.3 規(guī)范為基礎的,OpenGL ES 1.1 是以 OpenGL 1.5 規(guī)范為基礎的,它們分別又支持 common 和 common lite兩種profile。lite profile只支持定點實數(shù),而common profile既支持定點數(shù)又支持浮點數(shù)。 OpenGL ES 2.0 則是參照 OpenGL 2.0 規(guī)范定義的,common profile發(fā)布于2005-8,引入了對可編程管線的支持。
那么上面說了這么一些到底是什么意思呢.其實就是說OpenGL ES是移動端處理圖像的一個C語言庫.
OpenGL ES的顯示圖像
在iOS中,我們平常要加載一張圖片會怎么做呢?一個是使用UIKit框架的UIImage,一個是使用Core Graphics框架直接繪制.那么OpenGL ES是如何展現(xiàn)圖像的呢?今天我們就先用OpenGL ES中的GLKBaseEffect來展現(xiàn)圖像.實現(xiàn)效果如下所示.

HelloWorld的實現(xiàn)過程
一、 準備工作
</b>
為了簡便省時,我決定直接在ViewController中修改代碼,首先我們先導入GLKit.h頭文件,緊接著那個將ViewController的類型修改為GLKViewController.

然后在Main.storyboard修改ViewController中view的類型為GLKView.如圖所示.

上面的準備工作已經是做完了,那么接下來,就是正題部分了,我們現(xiàn)在ViewController.m中聲明兩個屬性.一個是OpenGL ES 上下文屬性的EAGLContext對象,一個是矩陣相關的GLKBaseEffect對象.
@interface ViewController ()
@property(nonatomic,strong)EAGLContext *mContext;
@property(nonatomic,strong)GLKBaseEffect *mEffect;
@end
通過官方的API文檔,我們知道,EAGLContext對象管理一個OpenGL ES渲染環(huán)境狀態(tài)信息,命令,以及使用OpenGL ES的所需要資源。OpenGL ES執(zhí)行任何命令之前,都需要通過EAGLContext對象來實現(xiàn)。同時官方文檔也提到,繪制一個上下文之前,你必須完成framebuffer對象綁定到上下文。
GLKBaseEffect這個類實現(xiàn)了OpenGL ES 1.0公共(common)的shading行為,簡化(從1.0)到OpenGL ES 2.0的轉化。它們也提供了讓光和紋理(lighting and texturing)工作的簡單方法。GLKBaseEffect對象提供了著色器這一功能.其實著色器應該算的上是OpenGL ES的一大特色,但是GLKBaseEffect已經包含了這一功能,相當于封裝了著色器.現(xiàn)在只是知道有著色器就行.
</b>
接下來,我們了解兩個方法,他們的刷新頻率和屏幕的刷新頻率是一致的.我們需要在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect這個方法中進行渲染操作.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;
-(void)update;
</b>
二、ViewDidLoad的配置工作
</b>
我們接下來在ViewDidLoad中需要做以下幾個工作.
1、配置OpenGL ES 上下文信息
self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
GLKView *view = (GLKView *)self.view;
view.context = self.mContext;
//顏色緩沖區(qū)格式
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
[EAGLContext setCurrentContext:self.mContext];
代碼講解:第一行代碼是對控制器自身的EAGLContext對象使用- (instancetype) initWithAPI:(EAGLRenderingAPI) api;進行初始化.EAGLRenderingAPI是一個枚舉類型.包含了三個值,分別代表著1.0、2.0和3.0的OpenGL ES,我們現(xiàn)在使用的OpenGL ES2.0,所以選擇的是kEAGLRenderingAPIOpenGLES2;
typedef NS_ENUM(NSUInteger, EAGLRenderingAPI)
{
kEAGLRenderingAPIOpenGLES1 = 1,
kEAGLRenderingAPIOpenGLES2 = 2,
kEAGLRenderingAPIOpenGLES3 = 3,
};
第二行和第三行代碼則是把當前控制器的View的context設置為self.mContext.
第四行代碼則是設置頁面的顏色緩沖區(qū)格式,默認的也是GLKViewDrawableColorFormatRGBA8888,所以不做設置也可以.
第五行代碼則是將此“EAGLContext”實例設置為OpenGL的“當前激活”的“Context”。這樣,以后所有“GL”的指令均作用在這個“Context”上。
2、配置繪制矩陣數(shù)組信息
OpenGL ES的坐標系是和iOS常用的Quartz 2D坐標系是不一樣的.OpenGL ES是左手坐標系(待議~),Quartz 2D坐標系則是右手坐標系.OpenGL ES的坐標系是以中心為原點,原點到屏幕的邊緣為單位1(不管屏幕尺寸如何變化,都是單位1).OpenGL ES的坐標系如下所示.

接下來,我們創(chuàng)建頂點數(shù)組,數(shù)組中的元素類型為GLfloat類型.數(shù)組中包含了兩個坐標一個是頂點坐標(x,y,z軸信息),一個是紋理坐標(x,y信息),注意,頂點要與紋理的點一一對應.關于紋理相關只是可以參考我在SpriteKit的文集中的SpriteKit框架之SKTextureAtlas第一部分內容.代碼如下所示.
(PS:問什么要這么創(chuàng)建數(shù)組呢?難道是系統(tǒng)規(guī)定的?回答:并不是,數(shù)組的形式規(guī)則定好之后,我們可以按照一定的規(guī)律取出對應的數(shù)據(jù)元素.然后進行操作.)
//頂點數(shù)據(jù),前三個是頂點坐標,后面兩個是紋理坐標
GLfloat squareVertexData[] =
{
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
};
上面的頂點坐標組成看似是一個正方形,但是實際上真的如此嗎?NONONO,事實上,由于屏幕的寬高不相同的原因.所選擇區(qū)域會是下面的這個樣子的.

</b>
對于初學者還有個容易忽視的技術點,那就是 在OpenGL ES只能繪制三角形,不能繪制多邊形,但是在OpenGL中確實可以直接繪制多邊形.
那么我們改如何繪制一個矩形呢?我們可以認為一個矩形是兩個三角形拼接而成的.這時候,我們就需要整出另外一個東西:那就是頂點索引數(shù)組.有了它就可以規(guī)定繪制的順序.如下所示.
//頂點索引
GLuint indices[] ={
0, 1, 2,
1, 3, 0
};
繪制過程如圖所示.先是做下三角形,再是右上三角形.

3.將頂點數(shù)據(jù)和頂點索引數(shù)據(jù)寫入通用的頂點屬性存儲區(qū) (重點核心部分??????)
其實我一直沒有理解"通用"這個詞(寫這篇博客寫到最后竟然理解了,因為頂點屬性集中包含五種屬性:位置、法線、顏色、紋理0,紋理1,所以只能用"通用"這個詞了).那么把頂點數(shù)據(jù)和頂點索引數(shù)據(jù)寫入通用的頂點屬性存儲區(qū),是怎么樣的過程呢?首先將頂點數(shù)據(jù)和頂點索引數(shù)據(jù)保存進GUP的一個緩沖區(qū)中,然后再按一定規(guī)則,將數(shù)據(jù)取出,復制到各個通用頂點屬性中.
那么接下來,我們先將頂點數(shù)組保存進GPU緩沖區(qū)中.代碼如下所示.
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);
然后就是把頂點索引數(shù)組寫進GPU緩沖區(qū)中.
GLuint index;
glGenBuffers(1, &index);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
函數(shù)說明:
glGenBuffers(GLsizei n,GLuint *buffers):任何非零的無符合整數(shù)都可以作為緩沖區(qū)對象的標識符使用。這個函數(shù)的作用就是向系統(tǒng)申請n個緩沖區(qū),系統(tǒng)把這n個緩沖區(qū)的標識符都放進buffers數(shù)組中。還可以調用glIsBuffer()函數(shù)判斷一個標識符是否正被使用。
例如,glGenBuffers(1, &index);這是是向系統(tǒng)申請1個緩沖區(qū),標識符為index.glBindBuffer(GLenum target, GLuint buffer):把這個緩沖區(qū)綁定給頂點還是索引.通俗點,也就是定義了這個緩沖區(qū)存儲的是什么.target用于決定綁定的是頂點數(shù)據(jù)(GL_ARRAY_BUFFER)還是索引數(shù)據(jù)(GL_ELEMENT_ARRAY_BUFFER).glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage):把CPU中的內存中的數(shù)組復制到GPU的內存中.target用于決定綁定的是頂點數(shù)據(jù)(GL_ARRAY_BUFFER)還是索引數(shù)據(jù)(GL_ELEMENT_ARRAY_BUFFER).size決定數(shù)據(jù)的存儲長度.data則是數(shù)據(jù)信息.usage表示數(shù)據(jù)的讀寫方式,是一個枚舉類型,這里使用的是GL_STATIC_DRAW,它表示此緩沖區(qū)內容只能被修改一次,但可以無限次讀取。
</b>
然后,將GPU緩沖區(qū)的頂點數(shù)據(jù)復制進通用頂點屬性中.注意:索引數(shù)據(jù)不需要復制到通用頂點屬性中.具體代碼如下.
//頂點數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat *) NULL +0);
//紋理數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat *)NULL +3);
函數(shù)說明:
-
glEnableVertexAttribArray (GLuint index): 激活頂點屬性(默認它的關閉的).在剛開始,我們就說到頂點屬性集中包含五種屬性:位置、法線、顏色、紋理0,紋理1.頂點屬性集是一個枚舉值.這里我們只用到了位置和紋理這兩個屬性.
typedef NS_ENUM(GLint, GLKVertexAttrib)
{
GLKVertexAttribPosition,
GLKVertexAttribNormal,
GLKVertexAttribColor,
GLKVertexAttribTexCoord0,
GLKVertexAttribTexCoord1
} NS_ENUM_AVAILABLE(10_8, 5_0);
</b>
-
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr): 往對應的頂點屬性中添加數(shù)據(jù).indx為頂點屬性類型.size為每個數(shù)據(jù)中的數(shù)據(jù)長度.type為元素數(shù)據(jù)類型,normalized填充時需不需要單位化.stride需要填寫的是在數(shù)據(jù)數(shù)組中每行的跨度,最后一個ptr指針是說的是每一個數(shù)據(jù)的起始位置將從內存數(shù)據(jù)塊的什么地方開始。例如頂點屬性的數(shù)據(jù)填充示意圖如下所示.

4.將圖片紋理賦值給GLKBaseEffect對象
本文的前面我就提到了圖片紋理和紋理集,紋理集最好的好處就是節(jié)省內存,具體看我以前寫的博客,上面有提到,這里就不啰嗦了.在OpenGL ES也是有紋理(GLKTextureInfo)這一概念,應該說Sprite Kit框架就是封裝的OpenGL ES??.下面我們先把圖片加載到紋理對象中.
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil ];
然后創(chuàng)建GLKBaseEffect對象并且開啟它的可編輯狀態(tài),然后把紋理賦值給GLKBaseEffect對象.
self.mEffect = [[GLKBaseEffect alloc]init];
self.mEffect.texture2d0.enabled = GL_TRUE;
self.mEffect.texture2d0.name = textureInfo.name;
</b>
三、渲染場景
</b>
可能沒接觸過Sprite Kit的童靴不太了解場景(Scene),你可以理解為是繪制圖層,當然了,這個繪制的頻率是跟屏幕刷新頻率是一致的(默認的).在GLKit框架中,GLKView對象是完全不需要做任何操作的,只要在控制器中執(zhí)行- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect和-(void)update這兩個方法就可以在GLKView對象上顯示圖像了.一般我們把渲染代碼寫在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect.如下所示.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//啟動著色器
[self.mEffect prepareToDraw];
glDrawElements(GL_TRIANGLES, self.mCount, GL_UNSIGNED_INT, 0);
}
函數(shù)說明:
glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha): 渲染前的“清除”操作,指定在清除屏幕之后填充什么樣的顏色.四個參數(shù)就是RGB值.glClear (GLbitfield mask):指定需要清除的緩沖.mask指定緩沖的類型.可以使用 | 運算符組合不同的緩沖標志位,表明需要清除的緩沖.可以使用以下標識符.
GL_COLOR_BUFFER_BIT: 當前可寫的顏色緩沖
GL_DEPTH_BUFFER_BIT: 深度緩沖
GL_ACCUM_BUFFER_BIT: 累積緩沖
GL_STENCIL_BUFFER_BIT: 模板緩沖
[self.mEffect prepareToDraw];這個就是啟動當前GLKBaseEffect對象.
-
glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices): 通過頂點索引繪制圖像.mode指定的繪制的類型.類型展示如下,這里使用的是GL_TRIANGLES,count指定的頂點索引數(shù)組中元素的個數(shù),type 為索引數(shù)組(indices)中元素的類型,只能是下列值之一:GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT,GL_UNSIGNED_INT. indices指向索引數(shù)組的指針。
GL_POINTS: 單獨的將頂點畫出來。
GL_LINES: 單獨地將直線畫出來。
GL_LINE_STRIP: 連貫地將直線畫出來。
GL_LINE_LOOP: 連貫地將直線畫出來。行為和GL_LINE_STRIP類似,但是會自動將最后一個頂點和第一個頂點通過直線連接起來。
GL_TRIANGLES:這個參數(shù)意味著OpenGL使用三個頂點來組成圖形。所以,在開始的三個頂點,將用頂點1,頂點2,頂點3來組成一個三角形。完成后,在用下一組的三個頂點(頂點4,5,6)來組成三角形,直到數(shù)組結束。
GL_TRIANGLE_STRIP: OpenGL的使用將最開始的兩個頂點出發(fā),然后遍歷每個頂點,這些頂點將使用前2個頂點一起組成一個三角形。
GL_TRIANGLE_FAN: 在跳過開始的2個頂點,然后遍歷每個頂點,讓OpenGL將這些頂點于它們前一個,以及數(shù)組的第一個頂點一起組成一個三角形。
HelloWorld之路的結束
如果沒有太大的問題的話,那么我們就會出現(xiàn)一開始的模擬器效果了.想想一張圖片展示底層顯示代碼是這么的多,想哭有木有??.其實這只是OpenGL ES的冰山一角.接下來,我將對著色器相關的部分進行研究講解,如果有任何疑問,可以在評論區(qū)回復,共同討論進步.最后附上HelloWorld的實現(xiàn)Demo.
