前言
對于OpenGL ES,本人現(xiàn)在還是一個(gè)小白,所以我將用小白視角對OpenGL ES進(jìn)行小白式的講解.希望能通過如此幫助更多的人.同時(shí)我要感謝一個(gè)人,那就是落影l(fā)oyinglin,落影大神關(guān)于OpenGL ES方面的知識寫的非常的詳細(xì),大家可以去參考.好了,開始戰(zhàn)斗吧.
OpenGL ES簡介
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三維圖形 API 的子集廉嚼,針對手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì)。該API由Khronos集團(tuán)定義推廣,Khronos是一個(gè)圖形軟硬件行業(yè)協(xié)會狈定,該協(xié)會主要關(guān)注圖形和多媒體方面的開放標(biāo)準(zhǔn)。
OpenGL ES 是從 OpenGL 裁剪的定制而來的巷挥,去除了glBegin/glEnd烹卒,四邊形(GL_QUADS)、多邊形(GL_POLYGONS)等復(fù)雜圖元等許多非絕對必要的特性倦西。經(jīng)過多年發(fā)展能真,現(xiàn)在主要有兩個(gè)版本,OpenGL ES 1.x 針對固定管線硬件的扰柠,OpenGL ES 2.x 針對可編程管線硬件粉铐。OpenGL ES 1.0 是以 OpenGL 1.3 規(guī)范為基礎(chǔ)的,OpenGL ES 1.1 是以 OpenGL 1.5 規(guī)范為基礎(chǔ)的卤档,它們分別又支持 common 和 common lite兩種profile蝙泼。lite profile只支持定點(diǎn)實(shí)數(shù),而common profile既支持定點(diǎn)數(shù)又支持浮點(diǎn)數(shù)劝枣。 OpenGL ES 2.0 則是參照 OpenGL 2.0 規(guī)范定義的汤踏,common profile發(fā)布于2005-8,引入了對可編程管線的支持舔腾。
那么上面說了這么一些到底是什么意思呢.其實(shí)就是說OpenGL ES是移動(dòng)端處理圖像的一個(gè)C語言庫.
OpenGL ES的顯示圖像
在iOS中,我們平常要加載一張圖片會怎么做呢?一個(gè)是使用UIKit框架的UIImage,一個(gè)是使用Core Graphics框架直接繪制.那么OpenGL ES是如何展現(xiàn)圖像的呢?今天我們就先用OpenGL ES中的GLKBaseEffect來展現(xiàn)圖像.實(shí)現(xiàn)效果如下所示.
HelloWorld的實(shí)現(xiàn)過程
一溪胶、 準(zhǔn)備工作
</b>
為了簡便省時(shí),我決定直接在ViewController中修改代碼,首先我們先導(dǎo)入GLKit.h頭文件,緊接著那個(gè)將ViewController的類型修改為GLKViewController.
然后在Main.storyboard修改ViewController中view的類型為GLKView.如圖所示.
上面的準(zhǔn)備工作已經(jīng)是做完了,那么接下來,就是正題部分了,我們現(xiàn)在ViewController.m中聲明兩個(gè)屬性.一個(gè)是OpenGL ES 上下文屬性的EAGLContext對象,一個(gè)是矩陣相關(guān)的GLKBaseEffect對象.
@interface ViewController ()
@property(nonatomic,strong)EAGLContext *mContext;
@property(nonatomic,strong)GLKBaseEffect *mEffect;
@end
通過官方的API文檔,我們知道,EAGLContext對象管理一個(gè)OpenGL ES渲染環(huán)境狀態(tài)信息,命令,以及使用OpenGL ES的所需要資源。OpenGL ES執(zhí)行任何命令之前,都需要通過EAGLContext對象來實(shí)現(xiàn)稳诚。同時(shí)官方文檔也提到,繪制一個(gè)上下文之前,你必須完成framebuffer對象綁定到上下文哗脖。
GLKBaseEffect這個(gè)類實(shí)現(xiàn)了OpenGL ES 1.0公共(common)的shading行為,簡化(從1.0)到OpenGL ES 2.0的轉(zhuǎn)化。它們也提供了讓光和紋理(lighting and texturing)工作的簡單方法才避。GLKBaseEffect對象提供了著色器這一功能.其實(shí)著色器應(yīng)該算的上是OpenGL ES的一大特色,但是GLKBaseEffect已經(jīng)包含了這一功能,相當(dāng)于封裝了著色器.現(xiàn)在只是知道有著色器就行.
</b>
接下來,我們了解兩個(gè)方法,他們的刷新頻率和屏幕的刷新頻率是一致的.我們需要在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
這個(gè)方法中進(jìn)行渲染操作.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;
-(void)update;
</b>
二丘损、ViewDidLoad的配置工作
</b>
我們接下來在ViewDidLoad中需要做以下幾個(gè)工作.
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;
進(jìn)行初始化.EAGLRenderingAPI是一個(gè)枚舉類型.包含了三個(gè)值,分別代表著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,
};
第二行和第三行代碼則是把當(dāng)前控制器的View的context設(shè)置為self.mContext.
第四行代碼則是設(shè)置頁面的顏色緩沖區(qū)格式,默認(rèn)的也是GLKViewDrawableColorFormatRGBA8888
,所以不做設(shè)置也可以.
第五行代碼則是將此“EAGLContext”實(shí)例設(shè)置為OpenGL的“當(dāng)前激活”的“Context”徘钥。這樣,以后所有“GL”的指令均作用在這個(gè)“Context”上肢娘。
2呈础、配置繪制矩陣數(shù)組信息
OpenGL ES的坐標(biāo)系是和iOS常用的Quartz 2D坐標(biāo)系是不一樣的.OpenGL ES是左手坐標(biāo)系(待議~),Quartz 2D坐標(biāo)系則是右手坐標(biāo)系.OpenGL ES的坐標(biāo)系是以中心為原點(diǎn),原點(diǎn)到屏幕的邊緣為單位1(不管屏幕尺寸如何變化,都是單位1).OpenGL ES的坐標(biāo)系如下所示.
接下來,我們創(chuàng)建頂點(diǎn)數(shù)組,數(shù)組中的元素類型為GLfloat類型.數(shù)組中包含了兩個(gè)坐標(biāo)一個(gè)是頂點(diǎn)坐標(biāo)(x,y,z軸信息),一個(gè)是紋理坐標(biāo)(x,y信息),注意,頂點(diǎn)要與紋理的點(diǎn)一一對應(yīng).關(guān)于紋理相關(guān)只是可以參考我在SpriteKit的文集中的SpriteKit框架之SKTextureAtlas第一部分內(nèi)容.代碼如下所示.
(PS:問什么要這么創(chuàng)建數(shù)組呢?難道是系統(tǒng)規(guī)定的?回答:并不是,數(shù)組的形式規(guī)則定好之后,我們可以按照一定的規(guī)律取出對應(yīng)的數(shù)據(jù)元素.然后進(jìn)行操作.)
//頂點(diǎn)數(shù)據(jù),前三個(gè)是頂點(diǎn)坐標(biāo)橱健,后面兩個(gè)是紋理坐標(biāo)
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, //右上
};
上面的頂點(diǎn)坐標(biāo)組成看似是一個(gè)正方形,但是實(shí)際上真的如此嗎?NONONO,事實(shí)上,由于屏幕的寬高不相同的原因.所選擇區(qū)域會是下面的這個(gè)樣子的.
</b>
對于初學(xué)者還有個(gè)容易忽視的技術(shù)點(diǎn),那就是 在OpenGL ES只能繪制三角形,不能繪制多邊形,但是在OpenGL中確實(shí)可以直接繪制多邊形.
那么我們改如何繪制一個(gè)矩形呢?我們可以認(rèn)為一個(gè)矩形是兩個(gè)三角形拼接而成的.這時(shí)候,我們就需要整出另外一個(gè)東西:那就是頂點(diǎn)索引數(shù)組.有了它就可以規(guī)定繪制的順序.如下所示.
//頂點(diǎn)索引
GLuint indices[] ={
0, 1, 2,
1, 3, 0
};
繪制過程如圖所示.先是做下三角形,再是右上三角形.
3.將頂點(diǎn)數(shù)據(jù)和頂點(diǎn)索引數(shù)據(jù)寫入通用的頂點(diǎn)屬性存儲區(qū) (重點(diǎn)核心部分??????)
其實(shí)我一直沒有理解"通用"這個(gè)詞(寫這篇博客寫到最后竟然理解了,因?yàn)轫旤c(diǎn)屬性集中包含五種屬性:位置而钞、法線、顏色拘荡、紋理0臼节,紋理1,所以只能用"通用"這個(gè)詞了).那么把頂點(diǎn)數(shù)據(jù)和頂點(diǎn)索引數(shù)據(jù)寫入通用的頂點(diǎn)屬性存儲區(qū),是怎么樣的過程呢?首先將頂點(diǎn)數(shù)據(jù)和頂點(diǎn)索引數(shù)據(jù)保存進(jìn)GUP的一個(gè)緩沖區(qū)中,然后再按一定規(guī)則珊皿,將數(shù)據(jù)取出网缝,復(fù)制到各個(gè)通用頂點(diǎn)屬性中.
那么接下來,我們先將頂點(diǎn)數(shù)組保存進(jìn)GPU緩沖區(qū)中.代碼如下所示.
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);
然后就是把頂點(diǎn)索引數(shù)組寫進(jìn)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ū)對象的標(biāo)識符使用。這個(gè)函數(shù)的作用就是向系統(tǒng)申請n個(gè)緩沖區(qū)蟋定,系統(tǒng)把這n個(gè)緩沖區(qū)的標(biāo)識符都放進(jìn)buffers數(shù)組中粉臊。還可以調(diào)用glIsBuffer()函數(shù)判斷一個(gè)標(biāo)識符是否正被使用。
例如,glGenBuffers(1, &index);
這是是向系統(tǒng)申請1個(gè)緩沖區(qū),標(biāo)識符為index.glBindBuffer(GLenum target, GLuint buffer)
:把這個(gè)緩沖區(qū)綁定給頂點(diǎn)還是索引.通俗點(diǎn),也就是定義了這個(gè)緩沖區(qū)存儲的是什么.target用于決定綁定的是頂點(diǎn)數(shù)據(jù)(GL_ARRAY_BUFFER)還是索引數(shù)據(jù)(GL_ELEMENT_ARRAY_BUFFER).glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
:把CPU中的內(nèi)存中的數(shù)組復(fù)制到GPU的內(nèi)存中.target用于決定綁定的是頂點(diǎn)數(shù)據(jù)(GL_ARRAY_BUFFER)還是索引數(shù)據(jù)(GL_ELEMENT_ARRAY_BUFFER).size決定數(shù)據(jù)的存儲長度.data則是數(shù)據(jù)信息.usage表示數(shù)據(jù)的讀寫方式,是一個(gè)枚舉類型,這里使用的是GL_STATIC_DRAW
,它表示此緩沖區(qū)內(nèi)容只能被修改一次驶兜,但可以無限次讀取扼仲。
</b>
然后,將GPU緩沖區(qū)的頂點(diǎn)數(shù)據(jù)復(fù)制進(jìn)通用頂點(diǎn)屬性中.注意:索引數(shù)據(jù)不需要復(fù)制到通用頂點(diǎn)屬性中.具體代碼如下.
//頂點(diǎn)數(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)
: 激活頂點(diǎn)屬性(默認(rèn)它的關(guān)閉的).在剛開始,我們就說到頂點(diǎn)屬性集中包含五種屬性:位置、法線抄淑、顏色屠凶、紋理0,紋理1.頂點(diǎn)屬性集是一個(gè)枚舉值.這里我們只用到了位置和紋理這兩個(gè)屬性.
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)
: 往對應(yīng)的頂點(diǎn)屬性中添加數(shù)據(jù).indx為頂點(diǎn)屬性類型.size為每個(gè)數(shù)據(jù)中的數(shù)據(jù)長度.type為元素?cái)?shù)據(jù)類型,normalized填充時(shí)需不需要單位化.stride需要填寫的是在數(shù)據(jù)數(shù)組中每行的跨度,最后一個(gè)ptr指針是說的是每一個(gè)數(shù)據(jù)的起始位置將從內(nèi)存數(shù)據(jù)塊的什么地方開始肆资。例如頂點(diǎn)屬性的數(shù)據(jù)填充示意圖如下所示.
4.將圖片紋理賦值給GLKBaseEffect對象
本文的前面我就提到了圖片紋理和紋理集,紋理集最好的好處就是節(jié)省內(nèi)存,具體看我以前寫的博客,上面有提到,這里就不啰嗦了.在OpenGL ES也是有紋理(GLKTextureInfo)這一概念,應(yīng)該說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),你可以理解為是繪制圖層,當(dāng)然了,這個(gè)繪制的頻率是跟屏幕刷新頻率是一致的(默認(rèn)的).在GLKit框架中,GLKView對象是完全不需要做任何操作的,只要在控制器中執(zhí)行- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
和-(void)update
這兩個(gè)方法就可以在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);
//啟動(dòng)著色器
[self.mEffect prepareToDraw];
glDrawElements(GL_TRIANGLES, self.mCount, GL_UNSIGNED_INT, 0);
}
函數(shù)說明:
glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
: 渲染前的“清除”操作,指定在清除屏幕之后填充什么樣的顏色.四個(gè)參數(shù)就是RGB值.glClear (GLbitfield mask)
:指定需要清除的緩沖.mask指定緩沖的類型.可以使用 | 運(yùn)算符組合不同的緩沖標(biāo)志位迅耘,表明需要清除的緩沖.可以使用以下標(biāo)識符.
GL_COLOR_BUFFER_BIT: 當(dāng)前可寫的顏色緩沖
GL_DEPTH_BUFFER_BIT: 深度緩沖
GL_ACCUM_BUFFER_BIT: 累積緩沖
GL_STENCIL_BUFFER_BIT: 模板緩沖
[self.mEffect prepareToDraw];
這個(gè)就是啟動(dòng)當(dāng)前GLKBaseEffect對象.
-
glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
: 通過頂點(diǎn)索引繪制圖像.mode指定的繪制的類型.類型展示如下,這里使用的是GL_TRIANGLES
,count指定的頂點(diǎn)索引數(shù)組中元素的個(gè)數(shù),type 為索引數(shù)組(indices)中元素的類型贱枣,只能是下列值之一:GL_UNSIGNED_BYTE
,GL_UNSIGNED_SHORT
,GL_UNSIGNED_INT
. indices指向索引數(shù)組的指針。
GL_POINTS: 單獨(dú)的將頂點(diǎn)畫出來颤专。
GL_LINES: 單獨(dú)地將直線畫出來纽哥。
GL_LINE_STRIP: 連貫地將直線畫出來。
GL_LINE_LOOP: 連貫地將直線畫出來栖秕。行為和GL_LINE_STRIP類似春塌,但是會自動(dòng)將最后一個(gè)頂點(diǎn)和第一個(gè)頂點(diǎn)通過直線連接起來。
GL_TRIANGLES:這個(gè)參數(shù)意味著OpenGL使用三個(gè)頂點(diǎn)來組成圖形。所以只壳,在開始的三個(gè)頂點(diǎn)俏拱,將用頂點(diǎn)1,頂點(diǎn)2吼句,頂點(diǎn)3來組成一個(gè)三角形锅必。完成后,在用下一組的三個(gè)頂點(diǎn)(頂點(diǎn)4惕艳,5搞隐,6)來組成三角形,直到數(shù)組結(jié)束远搪。
GL_TRIANGLE_STRIP: OpenGL的使用將最開始的兩個(gè)頂點(diǎn)出發(fā)劣纲,然后遍歷每個(gè)頂點(diǎn),這些頂點(diǎn)將使用前2個(gè)頂點(diǎn)一起組成一個(gè)三角形谁鳍。
GL_TRIANGLE_FAN: 在跳過開始的2個(gè)頂點(diǎn)癞季,然后遍歷每個(gè)頂點(diǎn),讓OpenGL將這些頂點(diǎn)于它們前一個(gè)倘潜,以及數(shù)組的第一個(gè)頂點(diǎn)一起組成一個(gè)三角形绷柒。
HelloWorld之路的結(jié)束
如果沒有太大的問題的話,那么我們就會出現(xiàn)一開始的模擬器效果了.想想一張圖片展示底層顯示代碼是這么的多,想哭有木有??.其實(shí)這只是OpenGL ES的冰山一角.接下來,我將對著色器相關(guān)的部分進(jìn)行研究講解,如果有任何疑問,可以在評論區(qū)回復(fù),共同討論進(jìn)步.最后附上HelloWorld的實(shí)現(xiàn)Demo.