這次的任務是把一張圖片用OpenGL ES
的方式顯示到屏幕上涎才,部分功能使用了GLKit
庫。
渲染上下文
先來看一下程序中定義的屬性:
EGL
連接了OpenGL ES
與本地原生窗口(例如iOS系統(tǒng))贮配。Apple提供了自己的EGL的API谍倦,就是EGAL,EAGLContext
就是屬于EGAL
泪勒。EAGLContext
是渲染上下文昼蛀,OpenGL ES 必須有一個可用的上下文才能繪圖,因為一個應用程序可能創(chuàng)建多個EAGLContext圆存,所以我們需要關聯(lián)特定的EAGLContext
和渲染表面
叼旋,這一步稱為“設置當前上下文
”
創(chuàng)建EAGLContext,創(chuàng)建的時候需要指定EAGLRenderingAPI
類型沦辙,EAGLRenderingAPI
取值類型有:kEAGLRenderingAPIOpenGLES1
夫植,kEAGLRenderingAPIOpenGLES2
,kEAGLRenderingAPIOpenGLES3
油讯,分別對應著OpenGL ES 1.0
详民,OpenGL ES 2.0
,OpenGL ES 3.0
撞羽,這里我們使用2.0阐斜。
設置controller的當前上下文是剛才創(chuàng)建的EAGLContext,設置顏色緩沖區(qū)的格式為
RGBA8888
的格式“頂點坐標” 和 “頂點屬性”
定義要繪制圖片的頂點坐標
和紋理坐標
OpenGL ES中坐標系是和iOS常用的Quartz 2D坐標系
是不一樣的诀紊,Quartz 2D坐標系屬于右手坐標系
谒出,OpenGL ES屬于右手坐標系
隅俘。先說一下左手坐標系和右手坐標系。
伸出左手笤喳,讓拇指和食指成“L”形狀为居,拇指向右,食指向上杀狡,中指指向起那方蒙畴,這時就建立了一個“左手坐標系
”,拇指呜象、食指和中指分表代表x膳凝、y、z軸的正方向恭陡〉乓簦“右手坐標系
”就是用右手,如下圖所示:
在iOS開發(fā)中休玩,屏幕左上角是坐標原點著淆,往右是x軸正方向,往下是y軸正方向拴疤。而在OpenGL ES中永部,屏幕的中點是坐標原點,往右是x軸正方向呐矾,往下是y軸正方向苔埋,其中z軸的正方向是從屏幕往外的方向,如下圖所示:
根據(jù)OpenGL ES的坐標系蜒犯,我們定義一下要繪制的圖片的幾個頂點讲坎,頂點坐標和紋理坐標是放在一個GLfloat數(shù)組
中管理的,定義一組頂點數(shù)據(jù)的跨度為5愧薛,其中前三個存儲頂點坐標
晨炕,后兩個存儲紋理坐標
,下圖一共定義了4個頂點毫炉,就是矩形的四個頂點瓮栗,需要注意的是,雖然坐標都是0.5瞄勾,但是繪制出來的圖形并不是正方形费奸,因為我們用來最終顯示的是iPhone屏幕,手機的長和寬并不相等进陡。
OpenGL ES不能繪制多邊形愿阐,只能繪制點
,線
趾疚,三角形
缨历,OpenGL可以繪制多邊形以蕴,由于我們繪制的圖片是一個矩形,又兩個三角形構(gòu)成辛孵,就是下圖中的兩個頂點索引
(0丛肮,1,3)和(1魄缚,2宝与,3)組成的三角形拼成一個矩形
根據(jù)頂點索引的個數(shù),計算要繪制的頂點的個數(shù)
“頂點緩存” 和 “頂點索引緩存”
程序會保存3D場景數(shù)據(jù)到RAM中冶匹,CPU有專門為其分配的RAM习劫,在圖形處理的過程中,GPU也會專門為其分配RAM嚼隘。使用硬件渲染3D圖形的速度幾乎完全取決于不同的內(nèi)存區(qū)域的訪問的方式榜聂。
OpenGL ES部分運行在CPU上,部分運行在GPU上嗓蘑。OpenGL ES橫跨在兩個處理器之間,協(xié)調(diào)兩個內(nèi)存區(qū)域之間的數(shù)據(jù)交換匿乃,下圖表示3D渲染相關的組件之間的數(shù)據(jù)交換桩皿,每個箭頭都代表著一個渲染性能的瓶頸。
OpenGL ES通常會高效地協(xié)調(diào)數(shù)據(jù)交換幢炸,但是程序與OpenGL ES的交互方式會增加交換的數(shù)量和類型泄隔,從一個內(nèi)存區(qū)域復制數(shù)據(jù)到內(nèi)外一個內(nèi)存區(qū)域,速度是相對比較慢的宛徊,另外佛嬉,在發(fā)生內(nèi)存復制的時候,這兩塊內(nèi)存都不能用作它用闸天,因此內(nèi)存區(qū)域之間的數(shù)據(jù)交換盡量避免暖呕。最新的嵌入式CPU可以很容易的完成以億為單位的運算,但是內(nèi)存讀寫只能在百萬單位苞氮,這意味著湾揽,除非CPU能夠在每次從內(nèi)存讀取一塊數(shù)據(jù)后有效的運行5個或者更多的運算,否則處理器的性能就處于次優(yōu)的狀態(tài)笼吟,也叫數(shù)據(jù)饑餓
库物,這種情況對于GPU來說更明顯。
OpenGL ES為了解決這個問題贷帮,定義了緩存(buffer)
戚揭,為緩存提供數(shù)據(jù)需要以下幾個步驟:
生成 (Generate)
:請求OpenGL ES為buffer生成一個獨一無二的標識符
-
綁定 (Bind)
:告訴OpenGL ES為接下來的運算使用一個buffer -
緩存數(shù)據(jù) (Buffer Data)
:為當前綁定的buffer分配并初始化足夠的內(nèi)存,從cpu控制的內(nèi)存復制數(shù)據(jù)到buffer -
啟用 (Enable) 或者禁止 (Disable)
:告訴OpenGL ES在接下來的渲染中是否使用緩存中的數(shù)據(jù) -
設置指針 (Pointer)
:告訴OpenGL ES在buffer中的數(shù)據(jù)的類型和所需要訪問的數(shù)據(jù)的內(nèi)存偏移值撵枢。 -
繪圖 (Draw)
:告訴OpenGL ES使用當前綁定并啟用的buffer中的數(shù)據(jù)民晒,來渲染場景 -
刪除 (Delete)
:告訴OpenGL ES刪除以前生成的buffer并釋放相關的buffer
上面的幾個步驟分別對應著下面的幾個OpenGL ES的API:
/* n: 要申請的緩沖區(qū)對象數(shù)量
buffer: 指向n個緩沖區(qū)的數(shù)組指針膛壹,該數(shù)組存放的是緩沖區(qū)的名稱
返回的緩沖區(qū)對象名稱是0以外的無符號整數(shù)粤策,0是OpenGL ES的保留值,不表示具體的緩沖區(qū)對象,修改或者查詢0的緩沖區(qū)狀態(tài)產(chǎn)生錯誤
*/
void glGenBuffers(GLsizei n, GLuint *buffer);
target:用于指定當前的緩沖區(qū)對象的"類型"
GL_ARRAY_BUFFER:數(shù)組緩沖區(qū)
GL_ELEMENT_ARRAY_BUFFER:元素數(shù)組緩沖區(qū)
GL_COPY_READ_BUFFER:復制讀緩沖區(qū)
GL_COPY_WRITE_BUFFER:復制寫緩沖區(qū)
GL_PIXEL_PACK_BUFFER:像素包裝緩沖區(qū)
GL_PIXEL_UNPACK_BUFFER:像素解包緩沖區(qū)
GL_TRANSFORM_FEEDBACK_BUFFER:變換反饋緩沖區(qū)
GL_UNIFORM_BUFFER:統(tǒng)一變量緩沖區(qū)
buffer: 緩沖區(qū)的名稱
void glBindBuffer(GLenum target, GLuint buffer);
OpenGL ES使用數(shù)組緩沖區(qū)
和元素數(shù)組緩沖區(qū)
兩種緩沖區(qū)類型分別指定頂點
和圖元數(shù)據(jù)
塔逃,GL_ARRAY_BUFFER
類型用于創(chuàng)建保存頂點數(shù)據(jù)的緩沖區(qū)對象,GL_ELEMENT_ARRAY_BUFFER
用于創(chuàng)建保存圖元索引的緩沖區(qū)對象讹蘑。
需要注意的是蓄诽,在用glBindBuffer
綁定之前,分配緩沖區(qū)并不一定非得用glGenBuffers恨旱,可以指定一個未使用的緩沖區(qū)對象辈毯。但是為了避免不必要的錯誤,還是建議使用glGenBuffers讓系統(tǒng)給我們分配未使用的緩沖區(qū)對象的名稱
/* target: 用于指定當前的緩沖區(qū)對象的"類型"
size: 緩沖區(qū)數(shù)據(jù)存儲大小搜贤,以字節(jié)表示
data: 緩沖區(qū)數(shù)據(jù)的指針
usage: 應用程序?qū)⑷绾问褂镁彌_區(qū)對象中存儲的數(shù)據(jù)的提示谆沃,也就是緩沖區(qū)的使用方法,初始值為 GL_STATIC_DRAW
*/
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
緩沖區(qū)的使用方法取值有很多種仪芒,這里我們使用GL_STATIC_DRAW唁影,這個值的意思是緩沖區(qū)對象將被修改一次,使用多次掂名,以繪制圖元或指定的圖像据沈。因為我們之后的操作不對其進行修改,只是初始化的時候賦值一次饺蔑。這個取值可以幫助OpenGL ES優(yōu)化內(nèi)存的使用
如果使用GL_DYNAMIC_DRAW锌介,意義是緩沖區(qū)對象將被重復修改,使用多次猾警。這會提示上下文孔祸,緩存內(nèi)的數(shù)據(jù)會頻繁改變,OpenGL ES就會以不同的方式來處理緩存的存儲发皿。
回到當前的例子中崔慧,具體代碼如下:
“啟用頂點數(shù)組” 和 “指定頂點屬性”
先來說一下頂點屬性,頂點數(shù)據(jù)也叫頂點屬性穴墅,指定每個頂點的數(shù)據(jù)尊浪,每個頂點的數(shù)據(jù)可以每個頂點挨個設置,就是頂點數(shù)組
封救,也可以用一個常量設置于所有的頂點拇涤,就是常量頂點屬性
。例如誉结,繪制一個紅色的三角形鹅士,可以指定一個常量頂點屬性來設置三角形的全部3個頂點,但是組成三角形的3個頂點的位置坐標不同惩坑,可以使用頂點數(shù)組來指定掉盅。
我們上面已經(jīng)定義了頂點數(shù)組:
//啟用頂點位置(坐標)數(shù)組也拜,之前說過opengl是狀態(tài)機,需要什么狀態(tài)就啟動什么狀態(tài)
glEnableVertexAttribArray(GLKVertexAttribPosition);
GLfloat vertexs[] = {
-0.5, -0.5, 0, 0.0, 0.0, //左下
-0.5, 0.5, 0, 0.0, 1.0, //左上
0.5, 0.5, 0, 1.0, 1.0, //右上
0.5, -0.5, 0, 1.0, 0.0, //右下
};
<a name="fenced-code-block">啟用通用頂點屬性</a>
/*
index:指定通用頂點數(shù)據(jù)的索引趾痘,這個值的范圍從0到支持的最大頂點屬性數(shù)量減1
功能:用于啟用通用頂點屬性
*/
void glEnableVertexAttribArray(GLuint index);
<a name="fenced-code-block">禁止通用頂點屬性</a>
/*
index:指定通用頂點數(shù)據(jù)的索引慢哈,這個值的范圍從0到支持的最大頂點屬性數(shù)量減1
*/
void glDisableVertexAttribArray(GLuint index);
<a name="fenced-code-block">常量頂點屬性設置值</a>
// 加載index指定的通用頂點屬性。
// 下面的API中沒有的值默認為1.0永票,比如glVertexAttrib1f/v設置的值為(x, 1.0, 1.0, 1.0)
void glVertexAttrib1f(GLuint index, GLfloat x);
void glVertexAttrib2f(GLuint index, GLfloat x, GLfloat y);
void glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);
void glVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
void glVertexAttrib1fv(GLuint index, const GLfloat *values);
void glVertexAttrib2fv(GLuint index, const GLfloat *values);
void glVertexAttrib3fv(GLuint index, const GLfloat *values);
void glVertexAttrib4fv(GLuint index, const GLfloat *values);
<a name="fenced-code-block">頂點數(shù)組設置值</a>
index: 通用頂點屬性索引
size: 頂點數(shù)組中為頂點屬性指定的分量數(shù)量卵贱,取值范圍1~4
type: 數(shù)據(jù)格式 ,兩個函數(shù)都包括的有效值是
GL_BYTE GL_UNSIGNED_BYTE GL_SHORT GL_UNSIGNED_SHORT GL_INT GL_UNSIGNED_INT
glVertexAttribPointer還包括的值為:GL_HALF_FLOAT GL_FLOAT 等
normalized: 僅glVertexAttribPointer使用侣集,表示非浮點數(shù)據(jù)類型轉(zhuǎn)換成浮點值時是否應該規(guī)范化
stride: 每個頂點由size指定的頂點屬性分量順序存儲键俱。stride指定頂點索引i和i+1表示的頂點之間的偏移。
如果為0世分,表示順序存儲编振。如果不為0,在取下一個頂點的同類數(shù)據(jù)時臭埋,需要加上偏移踪央。
ptr: 如果使用“頂點緩沖區(qū)對象”,表示的是該緩沖區(qū)內(nèi)的偏移量瓢阴。
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
// 取值為“整數(shù)”版本
void glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
需要注意的是:上面的幾個API中的index參數(shù)畅蹂,對應著頂點著色器中的響應變量的位置,可以使用著色器語言GLSL的修飾符來表示:
layout(location = 0) in vec4 a_color; layout(location = 1) in vec4 a_position;
設置顏色的屬性的時候炫掐,可以使用index=0;使用頂點坐標的時候可以設置index=1
或者
使用glBindAttribLocation
函數(shù)來設置睬涧,具體的以后再講募胃。
這里由于我們使用iOS封裝好的GLkit框架,不需要我們設置著色器程序畦浓,里面有內(nèi)置的設置好的index位置痹束,就是下面的變量:
typedef NS_ENUM(GLint, GLKVertexAttrib)
{
GLKVertexAttribPosition,
GLKVertexAttribNormal,
GLKVertexAttribColor,
GLKVertexAttribTexCoord0,
GLKVertexAttribTexCoord1
} NS_ENUM_AVAILABLE(10_8, 5_0);
在回到我們的程序,我們使用GLKit里面的GLKVertexAttribPosition
和GLKVertexAttribTexCoord0
分別表示頂點坐標
和紋理坐標
兩個變量的屬性索引讶请。(頂點索引不是頂點數(shù)據(jù)祷嘶,這里我們不需要管這個值)
- 上面的程序最需要注意的是變量的偏移很重要,我由于錯把GLfloat寫成CGFloat夺溢,導致圖片怎么都渲染不出來论巍。
設置紋理貼圖
我們把一張圖片加載成為要渲染的紋理,由于紋理坐標系是跟手機顯示的Quartz 2D坐標系的y軸正好相反风响,紋理坐標系使用左下角為原點嘉汰,往上為y軸的正值,往右是x軸的正值状勤,所以需要設置一下GLKTextureLoaderOriginBottomLeft
鞋怀。
GLKit中使用GLKTextureInfo
表示紋理對象双泪。
著色器
GLKit提供的GLKBaseEffect
是對OpenGL ES中的著色器的封裝。
下面的代碼創(chuàng)建GLKBaseEffect密似,并且把GLKBaseEffect的紋理功能打開焙矛,然后將GLKTextureInfo
賦值給GLKBaseEffect
的紋理
渲染
GLKit提供了GLKViewDelegate
,GLKView
里面有個delegate屬性残腌,我們需要實現(xiàn)這個協(xié)議村斟。這個協(xié)議的方法的刷新頻率和屏幕的刷新頻率是一致的,在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect這個方法中進行渲染操作.
在渲染操作中废累,我們來看幾個API:
OpenGL ES是一個交互式的渲染系統(tǒng)邓梅,在每一幀的開始,將緩沖區(qū)的所有內(nèi)容初始化為默認鎮(zhèn)邑滨。如果想把值一開始統(tǒng)一設置為一個值日缨,緩沖區(qū)可以通過glClear
函數(shù)清除,用一個掩碼位來表示應該清除為其指定值的各個緩沖區(qū)
// mask: 指定要清除的緩沖區(qū)掖看,由下面幾個表示各種OpenGL ES緩沖區(qū)的位掩碼聯(lián)合組成:
GL_COLOR_BUFFER_BIT
GL_DEPTH_BUFFER_BIT
GL_STENCIL_BUFFER_BIT
void glClear(GLbitfield mask);
設置清除為哪個默認值匣距,可以通過下面的函數(shù)來設置,下面的幾個函數(shù)分別對應著上面的幾個掩碼
// GL_COLOR_BUFFER_BIT 顏色
void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
// GL_DEPTH_BUFFER_BIT 深度
void glClearDepthf(GLfloat depthf);
// GL_STENCIL_BUFFER_BIT 模版
void glClearStencil(GLint s);
/* mode: 指定要繪制的圖元哎壳,我們繪制兩個三角形毅待,這里用GL_TRIANGLES
count: 要繪制的“頂點數(shù)量”
type:指定的頂點索引的存儲的值的類型
indices: 指向頂點索引的數(shù)組指針。*/
void glDrawElements(GLenum mode, GLsizei count, GLEnum type, const GLvoid *indices);
圖片顯示
不容易归榕,經(jīng)過上面這么多步驟尸红,我們終于把一張圖片顯示到了手機屏幕上,來看一下結(jié)果刹泄。然而外里,這僅僅是OpenGL ES萬里長征的第一步,繼續(xù)加油吧特石!