注意:本系列不會(huì)就OpenGL/OpenGL ES的專業(yè)名詞和概念以及渲染流程做過(guò)多闡述,有還未清楚的或者剛接觸OpenGL/OpenGL ES的請(qǐng)參考[OpenGL入門(mén)系列]
(http://www.itdecent.cn/nb/36794104)
提示: 希望從零基礎(chǔ)學(xué)習(xí)OpenGL ES的同學(xué)個(gè)人建議先去了解OpenGL(因?yàn)楣潭ü芫€更方便初學(xué)者入門(mén))
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對(duì)手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì)。該API由Khronos集團(tuán)定義推廣,Khronos是一個(gè)圖形軟硬件行業(yè)協(xié)會(huì),該協(xié)會(huì)主要關(guān)注圖形和多媒體方面的開(kāi)放標(biāo)準(zhǔn)。
OpenGL ES是以手持和嵌入式為目標(biāo)的高級(jí)3D圖形應(yīng)用程序編程接口(API).是目前智能手機(jī)中占據(jù)統(tǒng)治地位的圖形API.
OpenGL ES是OpenGL的簡(jiǎn)化版本,它消除了冗余功能,提供了一個(gè)即易于學(xué)習(xí)又更易于在移動(dòng)圖形硬件中實(shí)現(xiàn)的庫(kù)。
支持平臺(tái)、機(jī)型
支持iPad, iPhone3GS 和后續(xù)版本,以及iPodTouch3代和后續(xù)版本。
支持Android平臺(tái)從Android 2.2版本開(kāi)始。
支持Android NDK從Android 2.0版本開(kāi)始。
支持BlackBerryPlayBook黑莓。
支持Pandora潘多拉控制臺(tái)的3D庫(kù)。
被WebGL支持:瀏覽器支持OpenGL
支持少數(shù)新款Nokia諾基亞手機(jī),比如N900上的Maemo和N8上的Symbian3塞班3系統(tǒng)。
支持多款三星手機(jī),包括Galaxy S和Wave。
使用開(kāi)發(fā)插件可以支持Palm webOS。
支持Archos 愛(ài)可視上網(wǎng)本:70 IT, 101 IT
有關(guān)OpenGL ES API和OpenGL ES著色語(yǔ)言的完整參考,請(qǐng)參閱計(jì)劃使用的OpenGL ES版本集合:
1. OpenGL ES as a Client-Server Architecture - OpenGL ES作為客戶端 - 服務(wù)器架構(gòu)
OpenGL ES和OpenGL相同,架構(gòu)上分為服務(wù)端和客戶端。應(yīng)用程序?qū)顟B(tài)更改,紋理和頂點(diǎn)數(shù)據(jù)以及渲染命令傳達(dá)給OpenGL ES客戶端。 客戶端將這些數(shù)據(jù)轉(zhuǎn)換成圖形硬件理解的格式,并將其轉(zhuǎn)發(fā)到GPU。
簡(jiǎn)單理解:
我們編寫(xiě)的一系列代碼例如調(diào)用OpenGL/OpenGL ES提供的各種API都可視為客戶端部分,和其內(nèi)部核心實(shí)現(xiàn)以及具體個(gè)GPU進(jìn)行調(diào)度交互的功能部分屬于服務(wù)端部分。

2. OpenGL ES as a Graphics Pipeline - OpenGL ES作為圖形流水線
下圖可視化OpenGL ES作為圖形管道。 您的應(yīng)用程序配置圖形流水線,然后執(zhí)行繪圖命令以將頂點(diǎn)數(shù)據(jù)發(fā)送到流水線。 管道的連續(xù)階段運(yùn)行一個(gè)頂點(diǎn)著色器來(lái)處理頂點(diǎn)數(shù)據(jù),將頂點(diǎn)組合成圖元,將原始圖元柵格化成片段,運(yùn)行片段著色器來(lái)計(jì)算每個(gè)片段的顏色和深度值,并將片段混合到一個(gè)幀緩沖區(qū)中進(jìn)行顯示。
而OpenGL中的固定管線則是把該流程封裝好,作為固定流水線,暴漏出API以供客戶端調(diào)用,在OpenGL ES3.0棄用。使用GLSL(OpenGL著色語(yǔ)言O(shè)penGL Shading Language)可編程管線,iOS系統(tǒng)中蘋(píng)果原生提供了GLKit封裝了簡(jiǎn)單圖形繪制庫(kù)(后續(xù)會(huì)專門(mén)寫(xiě)篇文章介紹)。

3. Use Double Buffering to Avoid Resource Conflicts - 使用雙緩沖來(lái)避免資源沖突
當(dāng)您的應(yīng)用程序和OpenGL ES同時(shí)訪問(wèn)OpenGL ES對(duì)象時(shí),會(huì)發(fā)生資源沖突。 當(dāng)一個(gè)參與者嘗試修改由另一個(gè)使用的OpenGL ES對(duì)象時(shí),它們可能會(huì)阻塞,直到對(duì)象不再使用。 一旦他們開(kāi)始修改對(duì)象,其他參與者可能不會(huì)訪問(wèn)對(duì)象,直到修改完成。 或者,OpenGL ES可能會(huì)隱式復(fù)制對(duì)象,以便兩個(gè)參與者可以繼續(xù)執(zhí)行命令。 這兩個(gè)選項(xiàng)都是安全的,但每個(gè)選項(xiàng)都可能會(huì)成為您應(yīng)用程序中的瓶頸。 下圖顯示了這個(gè)問(wèn)題。 在這個(gè)例子中,有一個(gè)單一的紋理對(duì)象,OpenGL ES和你的應(yīng)用都要使用。 當(dāng)應(yīng)用程序嘗試更改紋理時(shí),必須等到之前提交的繪圖命令完成 - CPU才能與GPU同步。

要解決這個(gè)問(wèn)題,您的應(yīng)用程序可以在更改對(duì)象和繪圖之間執(zhí)行其他工作。 但是,如果您的應(yīng)用程序沒(méi)有可以執(zhí)行的其他工作,它應(yīng)該顯式地創(chuàng)建兩個(gè)相同大小的對(duì)象; 而一個(gè)參與者讀取一個(gè)對(duì)象,另一個(gè)參與者修改另一個(gè)對(duì)象。 下圖說(shuō)明了雙緩沖方式。 當(dāng)GPU在一個(gè)紋理上運(yùn)行時(shí),CPU會(huì)修改另一個(gè)紋理。 初始啟動(dòng)后,CPU或GPU都不空閑。 雖然為紋理顯示,此解決方案適用于幾乎任何類型的OpenGL ES對(duì)象。

雙重緩沖對(duì)于大多數(shù)應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠了,但是要求兩位參與者在大致相同的時(shí)間內(nèi)完成處理命令。 為了避免阻塞,您可以添加更多緩沖區(qū); 這實(shí)現(xiàn)了傳統(tǒng)的生產(chǎn)者 - 消費(fèi)者模式。 如果生產(chǎn)者在消費(fèi)者完成處理命令之前完成,則它需要空閑緩沖區(qū)并繼續(xù)處理命令。 在這種情況下,生產(chǎn)者只有在消費(fèi)者落后的情況下才會(huì)閑置。
雙重和三重緩沖器消除額外的內(nèi)存,以防止管道停滯。 額外使用內(nèi)存可能會(huì)對(duì)應(yīng)用程序的其他部分造成壓力。 在iOS設(shè)備上,內(nèi)存可能很少; 您的設(shè)計(jì)可能需要平衡使用更多的內(nèi)存與其他應(yīng)用程序優(yōu)化。
OpenGL ES 定義了跨平臺(tái)的接口來(lái)使用 GPU 的硬件性能加速圖形的渲染,其中由渲染上下文來(lái)執(zhí)行渲染命令,幀緩存存儲(chǔ)渲染結(jié)果,渲染目標(biāo)顯示幀緩存中結(jié)果。對(duì)應(yīng)到 iOS 中,EAGLContext 是實(shí)現(xiàn)上下文的類,GLKView 和 CAEAGLLayer 則用來(lái)顯示最終的渲染結(jié)果。
入門(mén)案例:OpenGL ES的‘hello word’
這里我們使用GLkit
1.新建工程 ,導(dǎo)入GLKit, ViewController.h中類別修改為繼承自GLKViewController
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
2.viewController.m中導(dǎo)入
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
- OpenGL ES 相關(guān)初始化
-(void)setUpConfig
{
//1.初始化上下文&設(shè)置當(dāng)前上下文
/*
EAGLContext 是蘋(píng)果iOS平臺(tái)下實(shí)現(xiàn)OpenGLES 渲染層.
kEAGLRenderingAPIOpenGLES1 = 1, 固定管線
kEAGLRenderingAPIOpenGLES2 = 2,
kEAGLRenderingAPIOpenGLES3 = 3,
*/
context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
//判斷context是否創(chuàng)建成功
if (!context) {
NSLog(@"Create ES context Failed");
}
//設(shè)置當(dāng)前上下文
[EAGLContext setCurrentContext:context];
//2.獲取GLKView & 設(shè)置context
GLKView *view =(GLKView *) self.view;
view.context = context;
/*3.配置視圖創(chuàng)建的渲染緩存區(qū).
(1). drawableColorFormat: 顏色緩存區(qū)格式.
簡(jiǎn)介: OpenGL ES 有一個(gè)緩存區(qū),它用以存儲(chǔ)將在屏幕中顯示的顏色。你可以使用其屬性來(lái)設(shè)置緩沖區(qū)中的每個(gè)像素的顏色格式。
GLKViewDrawableColorFormatRGBA8888 = 0,
默認(rèn).緩存區(qū)的每個(gè)像素的最小組成部分(RGBA)使用8個(gè)bit,(所以每個(gè)像素4個(gè)字節(jié),4*8個(gè)bit)。
GLKViewDrawableColorFormatRGB565,
如果你的APP允許更小范圍的顏色,即可設(shè)置這個(gè)。會(huì)讓你的APP消耗更小的資源(內(nèi)存和處理時(shí)間)
(2). drawableDepthFormat: 深度緩存區(qū)格式
GLKViewDrawableDepthFormatNone = 0,意味著完全沒(méi)有深度緩沖區(qū)
GLKViewDrawableDepthFormat16,
GLKViewDrawableDepthFormat24,
如果你要使用這個(gè)屬性(一般用于3D游戲),你應(yīng)該選擇GLKViewDrawableDepthFormat16
或GLKViewDrawableDepthFormat24。這里的差別是使用GLKViewDrawableDepthFormat16
將消耗更少的資源
*/
//3.配置視圖創(chuàng)建的渲染緩存區(qū).
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
//4.設(shè)置背景顏色
glClearColor(0.7, 0.7, 0.7, 1.0);
}
- 加載頂點(diǎn)/紋理坐標(biāo)數(shù)據(jù)
-(void)setUpVertexData
{
//1.設(shè)置頂點(diǎn)數(shù)組(頂點(diǎn)坐標(biāo),紋理坐標(biāo))
/*
紋理坐標(biāo)系取值范圍[0,1];原點(diǎn)是左下角(0,0);
故而(0,0)是紋理圖像的左下角, 點(diǎn)(1,1)是右上角.
*/
GLfloat vertexData[] = {
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
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, //左下
};
/*
頂點(diǎn)數(shù)組: 開(kāi)發(fā)者可以選擇設(shè)定函數(shù)指針,在調(diào)用繪制方法的時(shí)候,直接由內(nèi)存?zhèn)魅腠旤c(diǎn)數(shù)據(jù),也就是說(shuō)這部分?jǐn)?shù)據(jù)之前是存儲(chǔ)在內(nèi)存當(dāng)中的,被稱為頂點(diǎn)數(shù)組
頂點(diǎn)緩存區(qū): 性能更高的做法是,提前分配一塊顯存,將頂點(diǎn)數(shù)據(jù)預(yù)先傳入到顯存當(dāng)中。這部分的顯存,就被稱為頂點(diǎn)緩沖區(qū)
*/
//2.開(kāi)辟頂點(diǎn)緩存區(qū)
//(1).創(chuàng)建頂點(diǎn)緩存區(qū)標(biāo)識(shí)符ID
GLuint bufferID;
glGenBuffers(1, &bufferID);
//(2).綁定頂點(diǎn)緩存區(qū).(明確作用)
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
//(3).將頂點(diǎn)數(shù)組的數(shù)據(jù)copy到頂點(diǎn)緩存區(qū)中(GPU顯存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
//3.打開(kāi)讀取通道.
/*
(1)在iOS中, 默認(rèn)情況下,出于性能考慮,所有頂點(diǎn)著色器的屬性(Attribute)變量都是關(guān)閉的.
意味著,頂點(diǎn)數(shù)據(jù)在著色器端(服務(wù)端)是不可用的. 即使你已經(jīng)使用glBufferData方法,將頂點(diǎn)數(shù)據(jù)從內(nèi)存拷貝到頂點(diǎn)緩存區(qū)中(GPU顯存中).
所以, 必須由glEnableVertexAttribArray 方法打開(kāi)通道.指定訪問(wèn)屬性.才能讓頂點(diǎn)著色器能夠訪問(wèn)到從CPU復(fù)制到GPU的數(shù)據(jù).
注意: 數(shù)據(jù)在GPU端是否可見(jiàn),即,著色器能否讀取到數(shù)據(jù),由是否啟用了對(duì)應(yīng)的屬性決定,這就是glEnableVertexAttribArray的功能,允許頂點(diǎn)著色器讀取GPU(服務(wù)器端)數(shù)據(jù)。
(2)方法簡(jiǎn)介
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
功能: 上傳頂點(diǎn)數(shù)據(jù)到顯存的方法(設(shè)置合適的方式從buffer里面讀取數(shù)據(jù))
參數(shù)列表:
index,指定要修改的頂點(diǎn)屬性的索引值,例如
size, 每次讀取數(shù)量。(如position是由3個(gè)(x,y,z)組成,而顏色是4個(gè)(r,g,b,a),紋理則是2個(gè).)
type,指定數(shù)組中每個(gè)組件的數(shù)據(jù)類型??捎玫姆?hào)常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值為GL_FLOAT。
normalized,指定當(dāng)被訪問(wèn)時(shí),固定點(diǎn)數(shù)據(jù)值是否應(yīng)該被歸一化(GL_TRUE)或者直接轉(zhuǎn)換為固定點(diǎn)值(GL_FALSE)
stride,指定連續(xù)頂點(diǎn)屬性之間的偏移量。如果為0,那么頂點(diǎn)屬性會(huì)被理解為:它們是緊密排列在一起的。初始值為0
ptr指定一個(gè)指針,指向數(shù)組中第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。初始值為0
*/
//頂點(diǎn)坐標(biāo)數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
//紋理坐標(biāo)數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
}
- 加載紋理數(shù)據(jù)(使用GLBaseEffect)
-(void)setUpTexture
{
//1.獲取紋理圖片路徑
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"logOnbg" ofType:@"png"];
//2.設(shè)置紋理參數(shù)
//紋理坐標(biāo)原點(diǎn)是左下角,但是圖片顯示原點(diǎn)應(yīng)該是左上角.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//3.使用蘋(píng)果GLKit 提供GLKBaseEffect 完成著色器工作(頂點(diǎn)/片元)
cEffect = [[GLKBaseEffect alloc]init];
cEffect.texture2d0.enabled = GL_TRUE;
cEffect.texture2d0.name = textureInfo.name;
}
- 繪制視圖的內(nèi)容
GLKViewDelegate
//繪制視圖的內(nèi)容
/*
GLKView對(duì)象使其OpenGL ES上下文成為當(dāng)前上下文,并將其framebuffer綁定為OpenGL ES呈現(xiàn)命令的目標(biāo)。然后,委托方法應(yīng)該繪制視圖的內(nèi)容。
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
//1.
glClear(GL_COLOR_BUFFER_BIT);
//2.準(zhǔn)備繪制
[cEffect prepareToDraw];
//3.開(kāi)始繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
}
- 最后在
viewDidLoad中依次調(diào)用方法就可以了
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//1.OpenGL ES 相關(guān)初始化
[self setUpConfig];
//2.加載頂點(diǎn)/紋理坐標(biāo)數(shù)據(jù)
[self setUpVertexData];
//3.加載紋理數(shù)據(jù)(使用GLBaseEffect)
[self setUpTexture];
}
顯示結(jié)果:

最后一個(gè)小提示OpenGL ES使用GPU的部分在Xcode的模擬器上運(yùn)行是可以使用的,模擬器會(huì)用CPU模擬GPU功能,Metal則不可以。