簡介
OpenGL ES (OpenGL for Embedded Systems) 是以手持和嵌入式為目標(biāo)的?級3D圖形應(yīng) ?程序編程接口(API)。OpenGL ES 是?前智能手機(jī)中占據(jù)統(tǒng)治地位的圖形API,其作用范圍已經(jīng)擴(kuò)展到桌面。OpenGL ES支持的平臺包括iOS, Andriod , BlackBerry ,bada ,Linux 和Windows,它還是基于瀏覽器的3D圖形Web標(biāo)準(zhǔn)WebGL的基礎(chǔ)。
OpenGL ES是OpenGL的簡化版,其是為創(chuàng)建適合于受限設(shè)備(手持設(shè)備和嵌入式設(shè)備等)使用的API而產(chǎn)生的。
OpenGL ES的渲染流程
OpenGL ES 2.0規(guī)范之后采用了可編程圖形管線。下面概述OpenGL ES圖形管線的各個階段,其中灰色部分為可編程階段。(一些教程中又將片段著色器稱為片元著色器)

緩存
在上圖的圖形管線中可分為兩部分,API部分為客戶機(jī),其余部分為服務(wù)器。在圖形管線的整個流程中,OpenGL ES橫跨在GPU和CPU兩個處理器之間,協(xié)調(diào)兩個內(nèi)存區(qū)域之間的數(shù)據(jù)交換。程序會保存3D場景數(shù)據(jù)到硬件隨機(jī)存取存儲器(RAM)中。嵌入式系統(tǒng)的中央處理單元有專門為其分配的RAM。在圖形處理的過程中,GPU也有專門為其分配的RAM。由于CUP和GPU的計(jì)算速度遠(yuǎn)遠(yuǎn)大于讀寫內(nèi)存的速度,為了打破因?yàn)閿?shù)據(jù)交換而產(chǎn)生的性能瓶頸,OpenGL ES拋棄了OpenGL中低效的內(nèi)存復(fù)制操作,轉(zhuǎn)而通過緩存(buffers)的方式來降低數(shù)據(jù)交換帶來的影響。
緩存是指圖形處理器能夠控制和管理的連續(xù)RAM。程序從CPU的內(nèi)存復(fù)制數(shù)據(jù)到OpenGL ES的緩存,在GPU去的一個緩存的所有權(quán)后,運(yùn)行在CPU中的程序理想情況下將不再接觸這個緩存。通過獨(dú)占的緩存,GPU就能夠盡可能以最有效的方式讀寫內(nèi)存。為緩存提供數(shù)據(jù)又如下7個步驟:
- 1、生成glGenBuffers()——請求OpenGL ES為圖形處理器控制的緩存生成一個獨(dú)一無二的標(biāo)識符。
- 2、綁定glBindBuffer()——告訴OpenGL ES為接下來的運(yùn)算使用一個緩存。
- 3、緩沖數(shù)據(jù)glBufferData()或glBufferSubData()——讓OpenGL ES為當(dāng)前綁定的緩存分配并初始化足夠的連續(xù)內(nèi)存(通常是從CPU控制的內(nèi)存復(fù)制數(shù)據(jù)到分配的內(nèi)存)。
- 4、啟用glEnableVertexAttribArray()或禁止glDisVertexAttribArray()——告訴OpenGL ES在接下來的渲染中是否使用緩存中的數(shù)據(jù)。
- 5、設(shè)置指針glVertexAttribPointer()——告訴OpenGL ES在緩存中的數(shù)據(jù)的類型和所有需要訪問的數(shù)據(jù)的內(nèi)存偏移值。
- 6、繪制glDrawArrays()或glDrawElements()——告訴OpenGL ES使用當(dāng)前綁定并啟用的緩沖中的數(shù)據(jù)渲染整個場景或者某個場景的一部分。
- 7、刪除glDeleteBuffers()——告訴OpenGL ES刪除以前生成的緩存并釋放相關(guān)的資源。
生成、初始化、和刪除緩存有時需要消耗時間來同步圖形處理器和CPU,存在這個延遲是因?yàn)镚PU在刪除一個緩存之前必須完所有與該緩存相關(guān)的等待中的運(yùn)算。

幀緩存
GPU需要知道應(yīng)該在內(nèi)存中的哪個位置存儲渲染出來的2D圖像像素?cái)?shù)據(jù)。就像GPU提供數(shù)據(jù)的緩存一樣,接收渲染結(jié)果的緩沖區(qū)叫做幀緩存(frame buffer)。程序會像任何其它種類的緩存一樣生成、綁定、刪除幀緩存。但是幀緩存不需要初始化,因?yàn)殇秩局噶顣谶m當(dāng)?shù)臅r候交替緩存的內(nèi)容。幀緩存會在綁定時隱式開啟,同時OpenGL ES會自動地根據(jù)平臺硬件配置和功能來設(shè)置數(shù)據(jù)的類型和偏移。幀緩存可以同時存在很多個,并且可以通過OpenGL ES讓GPU把渲染結(jié)果存儲到任意數(shù)量的幀緩存中。屏幕顯示像素受到保存在前幀緩存(front frame buffer)的特定幀緩存中的像素顏色元素控制。程序和操作系統(tǒng)很少會直接渲染到前幀緩存中,因?yàn)槟菢訒層脩艨吹秸阡秩局羞€誒與渲染完成的圖像。相反,會把渲染結(jié)果保存到包括后幀緩存(back frame buffer)在內(nèi)的其他幀緩存中。當(dāng)渲染后的后幀緩存包含一個完成的圖像時,前、后幀緩存幾乎會瞬間切換。后幀緩存變成新的前幀緩存,同事舊的前幀緩存會變成后幀緩存。
OpenGL ES的上下文
OpenGL ES是一個狀態(tài)機(jī)器,即在一個程序中設(shè)置了一個配置值只有,這個值會一直保持,知道程序修改了這個值。用于配置OpenGL ES的信息會被封裝到一個上下文(context)中,上下文中的信息可能會被保存在CPU所控制的內(nèi)存中,也可能被保存在GPU所控制的內(nèi)存中。OpenGL ES會按需在兩個內(nèi)存區(qū)域間復(fù)制信息。
頂點(diǎn)著色器
頂點(diǎn)著色器的輸入包括:
- 著色器程序(Shader Program) —— 描述頂點(diǎn)上執(zhí)行操作的頂點(diǎn)著色器程序源代碼或者可執(zhí)行文件
- 頂點(diǎn)著色器輸入(或者屬性) —— 用頂點(diǎn)數(shù)組提供的每個頂點(diǎn)的數(shù)據(jù)
- 統(tǒng)一變量(uniform) —— 頂點(diǎn)(或片段)著色器使用的不變數(shù)據(jù)
- 采樣器 —— 代表頂點(diǎn)著色器使用紋理的特殊統(tǒng)一變量類型
頂點(diǎn)著色器的輸出在OpenGL ES 2.0中稱作可變(varying)變量,但是在OpenGL ES 3.0中改名為頂點(diǎn)著色器輸出變量。在圖圓光柵化階段,為每個生成的片元計(jì)算頂點(diǎn)著色器輸出值,并作為輸入傳遞給片元著色器。用于從分配給每個片元頂點(diǎn)的頂點(diǎn)著色器輸出生成每個片段值的機(jī)制稱為插值(Interpolation)。頂點(diǎn)著色器可以用于通過矩陣變換位置、計(jì)算照明公式來生成逐頂點(diǎn)顏色以及生成或者變換紋理坐標(biāo)等基于頂點(diǎn)的傳統(tǒng)操作。

圖元裝配
頂點(diǎn)著色器之后,OpenGL ES 3.0圖形管線的下一階段是圖元裝配。圖元(Primitive)是三角形、直線或者店精靈等集合對象。圖元的每個頂點(diǎn)被發(fā)送到頂點(diǎn)著色器的不同拷貝。在圖元裝配期間,這些頂點(diǎn)被組合成圖元。
對于每個圖元必須確定圖元是否位于視錐體內(nèi),如果圖元沒有完全在視錐體內(nèi),則可能需要進(jìn)行裁剪。如果圖元完全處于該區(qū)域之外,它會被拋棄。裁剪之后,頂點(diǎn)位置被轉(zhuǎn)換為屏幕坐標(biāo)。也可以執(zhí)行一次淘汰操作,根據(jù)圖元面向前方或者后放拋棄他們(深度測試)。裁剪和淘汰之后,圖元便準(zhǔn)備傳遞給管線的下一階段——光柵化階段。
光柵化
在光柵化階段需要繪制對應(yīng)的圖元(點(diǎn)精靈、直線或者三角形)。光柵化是將圖元轉(zhuǎn)化為一組二維片段的過程,然后這些片段由片元著色器處理。這些二維片段代表著可在屏幕上繪制的像素。

片元著色器(片段著色器)
片元著色器為片元上的操作實(shí)現(xiàn)了通用的可編程方法。它可以用于圖片/視頻/圖形中每個像素的顏色填充(像視頻/圖片添加濾鏡實(shí)際上就是將視頻中每個圖片的像素點(diǎn)顏色填充進(jìn)行修改)。如圖1-4所示, 對光柵化階段生成的每個片元執(zhí)行這個著色器,輸入方式如下:
- 著色器程序 ——描述片段上所執(zhí)行操作的片段著色器程序源代碼或者可執(zhí)行文件。
- 輸入變量——光柵化單元用插值為每個片元生成的頂點(diǎn)著色器輸出。
- 統(tǒng)一變量——片段(或者頂點(diǎn))著色器使用的不變數(shù)據(jù)。
- 采樣器——代表片元著色器所用紋理的特殊統(tǒng)一變量類型。

片元著色器可以拋棄片元,也可以生成一個或者多個顏色值作為輸出。一般來說,除了渲染到多重渲染目標(biāo)之外,片元著色器只輸出一個顏色值;在多重渲染目標(biāo)的情況下,為每個渲染目標(biāo)輸出一個顏色值。
由于參考資料不同,片元著色器和片段著色器是同一概念。懶得改了
逐片段操作
在片段著色器之后,下一階段是逐片段操作。光柵化生成的屏幕坐標(biāo)為(xw, yw) 的片段只能修改幀緩沖區(qū)中位置為(xw, yw)的像素。圖1-5描述了OpenGL ES 3.0逐片段操作階段。

在逐片段操作階段,在每個片段階段上執(zhí)行如下功能:
- 像素歸屬測試——確定幀緩沖區(qū)中位置(xw, yw)的像素目前是不是歸OpenGL ES所有。該測試使窗口系統(tǒng)能夠控制幀緩沖區(qū)中的哪些像素屬于當(dāng)前OpenGL ES上下文。
- 裁剪測試——確定位置(xw, yw)是否位于作為OpenGL ES狀態(tài)的一部分的裁剪矩形范圍內(nèi)。若該片段位于裁剪區(qū)域之外,則被拋棄。
- 模板和深度測試——這些測試在輸入片段的模板和深度值上進(jìn)行,以確定片段是否應(yīng)該被拒絕。
- 混合——混合將新生成的片段顏色值與保存在幀緩沖區(qū)(xw, yw)位置的顏色值組合起來。
- 抖動——抖動用于最小化因使用有限精度在幀緩沖區(qū)中保存顏色值而產(chǎn)生的偽像。
下面是通過OpenGL ES 2.0繪制三角形的代碼,便于理解記憶:
.h
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
.m
#import "ViewController.h"
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
typedef struct {
GLKVector3 positionCoords;
}SceneVertex;
static const SceneVertex vertices[] = {
{{-0.5f, -0.5f, 0.0}},
{{0.5f, -0.5f, 0.0}},
{{0.5f, 0.5f, 0.0}}
};
static const GLfloat points[] = {
-0.5f, -0.5f, 0.0,
0.5f, -0.5f, 0.0,
0.5f, 0.5f, 0.0
};
@interface ViewController (){
GLKBaseEffect * baseEffect;
GLuint vertexBufferID;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpConfig];
// 記得將storyboard中的view設(shè)置為GLKView
}
- (void)setUpConfig
{
// EAGLContext 上下文,用于配置OpenGL ES在特定平臺的軟件數(shù)據(jù)結(jié)構(gòu)中的信息
EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
GLKView * view = (GLKView *)self.view;
view.context = context;
[EAGLContext setCurrentContext:context];
baseEffect = [[GLKBaseEffect alloc] init];
baseEffect.useConstantColor = GL_TRUE;
baseEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f);
glClearColor(0.2f, 0.7f, 0.4f, 1.0f);
//該函數(shù)的第一個參數(shù)用于指定要生成的緩存標(biāo)識符的數(shù)量,第二個指針參數(shù)指向生成的標(biāo)識符的內(nèi)存保存位
置,當(dāng)前情況下一個標(biāo)識符被生成,保存在vertexBufferID實(shí)例變量中
glGenBuffers(1, &vertexBufferID);
/**
該函數(shù)綁定用于指定標(biāo)識符的緩存到當(dāng)前緩存。在任意時刻每種類型只能綁定一個緩存.
參1是一個常量,用于指定要綁定哪一種類型的緩存。該函數(shù)的實(shí)現(xiàn)只支持兩種緩存類型,
GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER, GL_ARRAY_BUFFER類型用于指定
一個頂點(diǎn)屬性數(shù)組
參2是要綁定的緩存的標(biāo)識符
*/
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
/**
該函數(shù)復(fù)制應(yīng)用的頂點(diǎn)數(shù)據(jù)到當(dāng)前上下文所綁定的頂點(diǎn)緩存中
參1:用于指定要更新當(dāng)前上下文中所綁定的是哪一個(種)緩存
參2:指定要復(fù)制進(jìn)這個緩存的字節(jié)數(shù)量
參3:要復(fù)制的字節(jié)的地址
參4:提示緩存在未來的運(yùn)算中可能將會被怎么使用。GL_STATIC_DRAW提示會告訴上下文,
緩存中的內(nèi)容適合復(fù)制到GPU控制的內(nèi)存,因?yàn)楹苌賹ζ溥M(jìn)行修改,可以幫助GL優(yōu)化內(nèi)存使用。
使用GL_DYNAMIC_DRAW會告訴上下文,緩存內(nèi)的數(shù)據(jù)會頻繁的改變,提示GL以不同的方式來處
理緩存中的存儲
*/
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
// glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
#pragma mark -- GLKViewDelegate
/**
每當(dāng)一個GLKView實(shí)例需要被重繪時,它都會讓保存在視圖的上下文屬性中的GL的上下文成為當(dāng)前上下文。
GLKView實(shí)例會綁定與一個Core Animation層分享的幀緩存,執(zhí)行其他的GL配置,并發(fā)送一個消息來調(diào)用
glkView:drawInRect:方法。
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
[baseEffect prepareToDraw];
//該函數(shù)來設(shè)置當(dāng)前綁定的幀緩存的像素顏色渲染緩存中的每一個像素的顏色為當(dāng)前使用glClearColor()
函數(shù)設(shè)定的值,幀緩存可能有除了像素顏色渲染緩存之外的其它附加緩存,并且如果其它緩存被使用了,它們可以
通過glClear函數(shù)中指定不同的參數(shù)來清除。glClear函數(shù)會有效地設(shè)置幀緩存中的每一個像素的顏色為背景色
glClear(GL_COLOR_BUFFER_BIT);
//出于性能考慮,所有頂點(diǎn)著色器的屬性(Attribute)變量都是關(guān)閉的,
意味著數(shù)據(jù)在著色器端是不可見的,哪怕數(shù)據(jù)已經(jīng)上傳到GPU,由glEnableVertexAttribArray
啟用指定屬性,才可在頂點(diǎn)著色器中訪問逐頂點(diǎn)的屬性數(shù)據(jù)。glVertexAttribPointer或VBO只是建立
CPU和GPU之間的邏輯連接,從而實(shí)現(xiàn)了CPU數(shù)據(jù)上傳至GPU。但是,數(shù)據(jù)在GPU端是否可見,即,著色器能
否讀取到數(shù)據(jù),由是否啟用了對應(yīng)的屬性決定,這就是glEnableVertexAttribArray的功能,允許頂點(diǎn)著
色器讀取GPU(服務(wù)器端)數(shù)據(jù)。
glEnableVertexAttribArray(GLKVertexAttribPosition);
/**
該函數(shù)告訴OpenGL ES頂點(diǎn)數(shù)據(jù)在哪里,以及怎么解釋為每個頂點(diǎn)保存的數(shù)據(jù)
參1:只是當(dāng)前綁定的緩存包含每個頂點(diǎn)的位置信息
參2:只是每個位置有3個部分
參3:告訴GL每個部分都保存為一個浮點(diǎn)類型的值
參4:高速GL小數(shù)點(diǎn)固定數(shù)據(jù)是否可以被改變
參5:該參數(shù)叫做“步幅”,它指定了每個頂點(diǎn)的保存需要多少個字節(jié),即指定了GPU從一個頂點(diǎn)的內(nèi)存開
始位置,轉(zhuǎn)到下一個頂點(diǎn)的內(nèi)存開始位置需要跳過多少字節(jié)
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE,
sizeof(GLfloat) * 3, (GLfloat *)NULL);
// 通過頂點(diǎn)結(jié)構(gòu)體繪制,需要復(fù)合頂點(diǎn)數(shù)組的數(shù)據(jù)
// glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT,GL_FALSE,
sizeof(SceneVertex), NULL);
/**
參1:設(shè)置繪制方式
參2:指定緩存內(nèi)的需要渲染的第一個頂點(diǎn)的位置
參3:緩存內(nèi)需要渲染的頂點(diǎn)的數(shù)量
*/
glDrawArrays(GL_TRIANGLES, 0, 3);
}
@end
// VBO: vertex buffer object 頂點(diǎn)緩沖區(qū)
// VAO: Vertex Array Object 頂點(diǎn)數(shù)組對象
// IBO: Index Buffer Object 頂點(diǎn)索引數(shù)組