一、說明:把一張圖片用OpenGL ES的方式顯示到屏幕上涨冀,部分功能使用了GLKit庫勺像。
二:一些相關(guān)知識的補(bǔ)充和學(xué)習(xí)
1滔驾、上下文的定義
EGL連接了OpenGL ES與本地原生窗口(例如iOS系統(tǒng))。Apple提供了自己的EGL的API峻仇,就是EGAL公黑,EAGLContext就是屬于EGAL。
EAGLContext是渲染上下文摄咆,OpenGL ES 必須有一個(gè)可用的上下文才能繪圖帆调,因?yàn)橐粋€(gè)應(yīng)用程序可能創(chuàng)建多個(gè)EAGLContext,所以我們需要關(guān)聯(lián)特定的EAGLContext和渲染表面豆同,這一步稱為“設(shè)置當(dāng)前上下文”
創(chuàng)建EAGLContext番刊,創(chuàng)建的時(shí)候需要指定EAGLRenderingAPI類型,EAGLRenderingAPI取值類型有:kEAGLRenderingAPIOpenGLES1影锈,kEAGLRenderingAPIOpenGLES2芹务,kEAGLRenderingAPIOpenGLES3蝉绷,分別對應(yīng)著OpenGL ES 1.0,OpenGL ES 2.0枣抱,OpenGL ES 3.0熔吗,這里我們使用2.0。
設(shè)置controller的當(dāng)前上下文是剛才創(chuàng)建的EAGLContext佳晶,設(shè)置顏色緩沖區(qū)的格式為RGBA8888的格式
二:“頂點(diǎn)坐標(biāo)” 和 “頂點(diǎn)屬性”
定義要繪制圖片的頂點(diǎn)坐標(biāo)和紋理坐標(biāo)
OpenGL ES中坐標(biāo)系是和iOS常用的Quartz 2D坐標(biāo)系是不一樣的桅狠,Quartz 2D坐標(biāo)系屬于右手坐標(biāo)系,OpenGL ES屬于左手坐標(biāo)系轿秧。先說一下左手坐標(biāo)系和右手坐標(biāo)系中跌。
伸出左手,讓拇指和食指成“L”形狀菇篡,拇指向右漩符,食指向上,中指指向起那方驱还,這時(shí)就建立了一個(gè)“左手坐標(biāo)系”嗜暴,拇指、食指和中指分表代表x议蟆、y闷沥、z軸的正方向「廊荩“右手坐標(biāo)系”就是用右手狐赡,如下圖所示:
在iOS開發(fā)中,屏幕左上角是坐標(biāo)原點(diǎn)疟丙,往右是x軸正方向颖侄,往下是y軸正方向。而在OpenGL ES中享郊,屏幕的中點(diǎn)是坐標(biāo)原點(diǎn)薪丁,往右是x軸正方向羡玛,往下是y軸正方向讼油,其中z軸的正方向是從屏幕往外的方向厅翔,如下圖所示:
根據(jù)OpenGL ES的坐標(biāo)系,我們定義一下要繪制的圖片的幾個(gè)頂點(diǎn)苔咪,頂點(diǎn)坐標(biāo)和紋理坐標(biāo)是放在一個(gè)GLfloat數(shù)組中管理的锰悼,定義一組頂點(diǎn)數(shù)據(jù)的跨度為5,其中前三個(gè)存儲頂點(diǎn)坐標(biāo)团赏,后兩個(gè)存儲紋理坐標(biāo)箕般,下圖一共定義了4個(gè)頂點(diǎn),就是矩形的四個(gè)頂點(diǎn)舔清,需要注意的是丝里,雖然坐標(biāo)都是0.5曲初,但是繪制出來的圖形并不是正方形,因?yàn)槲覀冇脕碜罱K顯示的是iPhone屏幕杯聚,手機(jī)的長和寬并不相等臼婆。
OpenGL ES不能繪制多邊形,只能繪制點(diǎn)幌绍,線颁褂,三角形,OpenGL可以繪制多邊形傀广,由于我們繪制的圖片是一個(gè)矩形颁独,又兩個(gè)三角形構(gòu)成,就是下圖中的兩個(gè)頂點(diǎn)索引(0主儡,1奖唯,3)和(1惨缆,2糜值,3)組成的三角形拼成一個(gè)矩形
三:“頂點(diǎn)緩存” 和 “頂點(diǎn)索引緩存”
程序會(huì)保存3D場景數(shù)據(jù)到RAM中,CPU有專門為其分配的RAM坯墨,在圖形處理的過程中寂汇,GPU也會(huì)專門為其分配RAM。使用硬件渲染3D圖形的速度幾乎完全取決于不同的內(nèi)存區(qū)域的訪問的方式捣染。OpenGL ES部分運(yùn)行在CPU上骄瓣,部分運(yùn)行在GPU上。OpenGL ES橫跨在兩個(gè)處理器之間耍攘,協(xié)調(diào)兩個(gè)內(nèi)存區(qū)域之間的數(shù)據(jù)交換榕栏,下圖表示3D渲染相關(guān)的組件之間的數(shù)據(jù)交換,每個(gè)箭頭都代表著一個(gè)渲染性能的瓶頸蕾各。
硬件組件和OpenGL ES之間的關(guān)系
OpenGL ES通常會(huì)高效地協(xié)調(diào)數(shù)據(jù)交換扒磁,但是程序與OpenGL ES的交互方式會(huì)增加交換的數(shù)量和類型,從一個(gè)內(nèi)存區(qū)域復(fù)制數(shù)據(jù)到另外一個(gè)內(nèi)存區(qū)域式曲,速度是相對比較慢的妨托,另外,在發(fā)生內(nèi)存復(fù)制的時(shí)候吝羞,這兩塊內(nèi)存都不能用作它用兰伤,因此內(nèi)存區(qū)域之間的數(shù)據(jù)交換盡量避免。最新的嵌入式CPU可以很容易的完成以億為單位的運(yùn)算钧排,但是內(nèi)存讀寫只能在百萬單位敦腔,這意味著,除非CPU能夠在每次從內(nèi)存讀取一塊數(shù)據(jù)后有效的運(yùn)行5個(gè)或者更多的運(yùn)算恨溜,否則處理器的性能就處于次優(yōu)的狀態(tài)会烙,也叫數(shù)據(jù)饑餓负懦,這種情況對于GPU來說更明顯。
OpenGL ES為了解決這個(gè)問題柏腻,定義了緩存(buffer)纸厉,為緩存提供數(shù)據(jù)需要以下幾個(gè)步驟:
生成 (Generate):請求OpenGL ES為buffer生成一個(gè)獨(dú)一無二的標(biāo)識符
綁定 (Bind):告訴OpenGL ES為接下來的運(yùn)算使用一個(gè)buffer
緩存數(shù)據(jù) (Buffer Data):為當(dāng)前綁定的buffer分配并初始化足夠的內(nèi)存,從cpu控制的內(nèi)存復(fù)制數(shù)據(jù)到buffer
啟用 (Enable) 或者禁止 (Disable):告訴OpenGL ES在接下來的渲染中是否使用緩存中的數(shù)據(jù)
設(shè)置指針 (Pointer):告訴OpenGL ES在buffer中的數(shù)據(jù)的類型和所需要訪問的數(shù)據(jù)的內(nèi)存偏移值五嫂。
繪圖 (Draw):告訴OpenGL ES使用當(dāng)前綁定并啟用的buffer中的數(shù)據(jù)颗品,來渲染場景
刪除 (Delete):告訴OpenGL ES刪除以前生成的buffer并釋放相關(guān)的buffer
上面的幾個(gè)步驟分別對應(yīng)著下面的幾個(gè)OpenGL ES的API:
/* n: 要申請的緩沖區(qū)對象數(shù)量
buffer: 指向n個(gè)緩沖區(qū)的數(shù)組指針,該數(shù)組存放的是緩沖區(qū)的名稱
返回的緩沖區(qū)對象名稱是0以外的無符號整數(shù)沃缘,0是OpenGL ES的保留值躯枢,不表示具體的緩沖區(qū)對象,修改或者查詢0的緩沖區(qū)狀態(tài)產(chǎn)生錯(cuò)誤
*/voidglGenBuffers(GLsizei n, GLuint *buffer);
target:用于指定當(dāng)前的緩沖區(qū)對象的
? "類型"GL_ARRAY_BUFFER:數(shù)組緩沖區(qū)? ? ? ?
?? GL_ELEMENT_ARRAY_BUFFER:元素?cái)?shù)組緩沖區(qū)? ? ? ?
?? GL_COPY_READ_BUFFER:復(fù)制讀緩沖區(qū)? ? ? ?
?? GL_COPY_WRITE_BUFFER:復(fù)制寫緩沖區(qū)? ? ? ? ?
? ?GL_PIXEL_PACK_BUFFER:像素包裝緩沖區(qū)? ? ?
? GL_PIXEL_UNPACK_BUFFER:像素解包緩沖區(qū)? ? ? ? ? GL_TRANSFORM_FEEDBACK_BUFFER:變換反饋緩沖區(qū) ? ??
? ? ? GL_UNIFORM_BUFFER:統(tǒng)一變量緩沖區(qū) buffer: 緩沖區(qū)的名稱 ? voidglBindBuffer(GLenumtarget, GLuint buffer);
OpenGL ES使用數(shù)組緩沖區(qū)和元素?cái)?shù)組緩沖區(qū)兩種緩沖區(qū)類型分別指定頂點(diǎn)和圖元數(shù)據(jù)槐臀,GL_ARRAY_BUFFER類型用于創(chuàng)建保存頂點(diǎn)數(shù)據(jù)的緩沖區(qū)對象锄蹂,GL_ELEMENT_ARRAY_BUFFER用于創(chuàng)建保存圖元索引的緩沖區(qū)對象。
需要注意的是水慨,在用glBindBuffer綁定之前得糜,分配緩沖區(qū)并不一定非得用glGenBuffers,可以指定一個(gè)未使用的緩沖區(qū)對象晰洒。但是為了避免不必要的錯(cuò)誤朝抖,還是建議使用glGenBuffers讓系統(tǒng)給我們分配未使用的緩沖區(qū)對象的名稱
/* target: 用于指定當(dāng)前的緩沖區(qū)對象的"類型"
size: 緩沖區(qū)數(shù)據(jù)存儲大小,以字節(jié)表示
data: 緩沖區(qū)數(shù)據(jù)的指針
usage: 應(yīng)用程序?qū)⑷绾问褂镁彌_區(qū)對象中存儲的數(shù)據(jù)的提示谍珊,也就是緩沖區(qū)的使用方法治宣,初始值為 GL_STATIC_DRAW
*/voidglBufferData(GLenumtarget, GLsizeiptr size,constvoid*data, GLenum usage);
緩沖區(qū)的使用方法取值有很多種,這里我們使用GL_STATIC_DRAW砌滞,這個(gè)值的意思是緩沖區(qū)對象將被修改一次侮邀,使用多次,以繪制圖元或指定的圖像贝润。因?yàn)槲覀冎蟮牟僮鞑粚ζ溥M(jìn)行修改绊茧,只是初始化的時(shí)候賦值一次。這個(gè)取值可以幫助OpenGL ES優(yōu)化內(nèi)存的使用
如果使用GL_DYNAMIC_DRAW题暖,意義是緩沖區(qū)對象將被重復(fù)修改按傅,使用多次。這會(huì)提示上下文胧卤,緩存內(nèi)的數(shù)據(jù)會(huì)頻繁改變唯绍,OpenGL ES就會(huì)以不同的方式來處理緩存的存儲。
回到當(dāng)前的例子中枝誊,具體代碼如下:
頂點(diǎn)數(shù)據(jù)和頂點(diǎn)索引的緩存
“啟用頂點(diǎn)數(shù)組” 和 “指定頂點(diǎn)屬性”
先來說一下頂點(diǎn)屬性况芒,頂點(diǎn)數(shù)據(jù)也叫頂點(diǎn)屬性,指定每個(gè)頂點(diǎn)的數(shù)據(jù)叶撒,每個(gè)頂點(diǎn)的數(shù)據(jù)可以每個(gè)頂點(diǎn)挨個(gè)設(shè)置绝骚,就是頂點(diǎn)數(shù)組耐版,也可以用一個(gè)常量設(shè)置于所有的頂點(diǎn),就是常量頂點(diǎn)屬性压汪。例如粪牲,繪制一個(gè)紅色的三角形,可以指定一個(gè)常量頂點(diǎn)屬性來設(shè)置三角形的全部3個(gè)頂點(diǎn)止剖,但是組成三角形的3個(gè)頂點(diǎn)的位置坐標(biāo)不同腺阳,可以使用頂點(diǎn)數(shù)組來指定。
我們上面已經(jīng)定義了頂點(diǎn)數(shù)組:
//啟用頂點(diǎn)位置(坐標(biāo))數(shù)組穿香,之前說過opengl是狀態(tài)機(jī)亭引,需要什么狀態(tài)就啟動(dòng)什么狀態(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,//右下};
/*
index:指定通用頂點(diǎn)數(shù)據(jù)的索引,這個(gè)值的范圍從0到支持的最大頂點(diǎn)屬性數(shù)量減1
功能:用于啟用通用頂點(diǎn)屬性
*/void ?glEnableVertexAttribArray(GLuint index);
/*
index:指定通用頂點(diǎn)數(shù)據(jù)的索引皮获,這個(gè)值的范圍從0到支持的最大頂點(diǎn)屬性數(shù)量減1
*/void ? glDisableVertexAttribArray(GLuint index);
// 加載index指定的通用頂點(diǎn)屬性焙蚓。// 下面的API中沒有的值默認(rèn)為1.0,比如glVertexAttrib1f/v設(shè)置的值為(x, 1.0, 1.0, 1.0)voidglVertexAttrib1f(GLuint index, GLfloat x);voidglVertexAttrib2f(GLuint index, GLfloat x, GLfloat y);voidglVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);voidglVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);voidglVertexAttrib1fv(GLuint index,constGLfloat *values);voidglVertexAttrib2fv(GLuint index,constGLfloat *values);voidglVertexAttrib3fv(GLuint index,constGLfloat *values);voidglVertexAttrib4fv(GLuint index,constGLfloat *values);
index: 通用頂點(diǎn)屬性索引size: 頂點(diǎn)數(shù)組中為頂點(diǎn)屬性指定的分量數(shù)量洒宝,取值范圍1~4type: 數(shù)據(jù)格式 购公,兩個(gè)函數(shù)都包括的有效值是GL_BYTEGL_UNSIGNED_BYTEGL_SHORTGL_UNSIGNED_SHORTGL_INTGL_UNSIGNED_INTglVertexAttribPointer還包括的值為:GL_HALF_FLOATGL_FLOAT等normalized: 僅glVertexAttribPointer使用,表示非浮點(diǎn)數(shù)據(jù)類型轉(zhuǎn)換成浮點(diǎn)值時(shí)是否應(yīng)該規(guī)范化stride: 每個(gè)頂點(diǎn)由size指定的頂點(diǎn)屬性分量順序存儲待德。stride指定頂點(diǎn)索引i和i+1表示的頂點(diǎn)之間的偏移君丁。? ? 如果為0枫夺,表示順序存儲将宪。如果不為0,在取下一個(gè)頂點(diǎn)的同類數(shù)據(jù)時(shí)橡庞,需要加上偏移较坛。ptr: 如果使用“頂點(diǎn)緩沖區(qū)對象”,表示的是該緩沖區(qū)內(nèi)的偏移量扒最。voidglVertexAttribPointer(GLuintindex,GLintsize,GLenumtype,GLbooleannormalized,GLsizeistride,constvoid*ptr);// 取值為“整數(shù)”版本voidglVertexAttribIPointer(GLuintindex,GLintsize,GLenumtype,GLbooleannormalized,GLsizeistride,constvoid*ptr);
需要注意的是:上面的幾個(gè)API中的index參數(shù)丑勤,對應(yīng)著頂點(diǎn)著色器中的響應(yīng)變量的位置,可以使用著色器語言GLSL的修飾符來表示:
layout(location = 0) in vec4 a_color;
layout(location = 1) in vec4 a_position;
設(shè)置顏色的屬性的時(shí)候吧趣,可以使用index=0法竞;使用頂點(diǎn)坐標(biāo)的時(shí)候可以設(shè)置index=1
或者
使用glBindAttribLocation函數(shù)來設(shè)置,具體的以后再講强挫。
這里由于我們使用iOS封裝好的GLkit框架岔霸,不需要我們設(shè)置著色器程序,里面有內(nèi)置的設(shè)置好的index位置俯渤,就是下面的變量:
typedefNS_ENUM(GLint,GLKVertexAttrib){GLKVertexAttribPosition,GLKVertexAttribNormal,GLKVertexAttribColor,GLKVertexAttribTexCoord0,GLKVertexAttribTexCoord1}NS_ENUM_AVAILABLE(10_8,5_0);
在回到我們的程序呆细,我們使用GLKit里面的GLKVertexAttribPosition和GLKVertexAttribTexCoord0分別表示頂點(diǎn)坐標(biāo)和紋理坐標(biāo)兩個(gè)變量的屬性索引。(頂點(diǎn)索引不是頂點(diǎn)數(shù)據(jù)八匠,這里我們不需要管這個(gè)值)
啟用頂點(diǎn)數(shù)組和指定頂點(diǎn)屬性
上面的程序最需要注意的是變量的偏移很重要絮爷,我由于錯(cuò)把GLfloat寫成CGFloat趴酣,導(dǎo)致圖片怎么都渲染不出來。
四:設(shè)置紋理貼圖
我們把一張圖片加載成為要渲染的紋理坑夯,由于紋理坐標(biāo)系是跟手機(jī)顯示的Quartz 2D坐標(biāo)系的y軸正好相反岖寞,紋理坐標(biāo)系使用左下角為原點(diǎn),往上為y軸的正值柜蜈,往右是x軸的正值慎璧,所以需要設(shè)置一下GLKTextureLoaderOriginBottomLeft。
GLKit中使用GLKTextureInfo表示紋理對象跨释。
五:著色器
GLKit提供的GLKBaseEffect是對OpenGL ES中的著色器的封裝胸私。
下面的代碼創(chuàng)建GLKBaseEffect,并且把GLKBaseEffect的紋理功能打開鳖谈,然后將GLKTextureInfo賦值給GLKBaseEffect的紋理
GLKBaseEffect
六:渲染
GLKit提供了GLKViewDelegate岁疼,GLKView里面有個(gè)delegate屬性,我們需要實(shí)現(xiàn)這個(gè)協(xié)議缆娃。這個(gè)協(xié)議的方法的刷新頻率和屏幕的刷新頻率是一致的捷绒,在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect這個(gè)方法中進(jìn)行渲染操作.
GLKViewDelegate
在渲染操作中,我們來看幾個(gè)API:
OpenGL ES是一個(gè)交互式的渲染系統(tǒng)贯要,在每一幀的開始暖侨,將緩沖區(qū)的所有內(nèi)容初始化為默認(rèn)幀。如果想把值一開始統(tǒng)一設(shè)置為一個(gè)值崇渗,緩沖區(qū)可以通過glClear函數(shù)清除字逗,用一個(gè)掩碼位來表示應(yīng)該清除為其指定值的各個(gè)緩沖區(qū)
// mask: 指定要清除的緩沖區(qū),由下面幾個(gè)表示各種OpenGL ES緩沖區(qū)的位掩碼聯(lián)合組成:GL_COLOR_BUFFER_BIT? ? GL_DEPTH_BUFFER_BIT? ? GL_STENCIL_BUFFER_BITvoidglClear(GLbitfield mask);
設(shè)置清除為哪個(gè)默認(rèn)值宅广,可以通過下面的函數(shù)來設(shè)置葫掉,下面的幾個(gè)函數(shù)分別對應(yīng)著上面的幾個(gè)掩碼
// GL_COLOR_BUFFER_BIT 顏色voidglClearColor(GLfloatred, GLfloatgreen, GLfloatblue, GLfloatalpha);
// GL_DEPTH_BUFFER_BIT 深度voidglClearDepthf(GLfloat depthf);// GL_STENCIL_BUFFER_BIT 模版voidglClearStencil(GLint s);
?mode: 指定要繪制的圖元,我們繪制兩個(gè)三角形跟狱,這里用GL_TRIANGLES
count: 要繪制的“頂點(diǎn)數(shù)量”
type:指定的頂點(diǎn)索引的存儲的值的類型
indices: 指向頂點(diǎn)索引的數(shù)組指針俭厚。
七、繪制圖
void glDrawElements(GLenum mode, GLsizei count, GLEnum type,constGLvoid *indices);
八驶臊、總結(jié)
1:上面的部分知識總結(jié)參考這位學(xué)者的筆記挪挤,講解的比較好比較詳細(xì),我本來不打算重新拷貝下這些總結(jié)的关翎,但是我怕這么好總結(jié)被刪除扛门,這里重新記錄一下,理論知識作者鏈接地址點(diǎn)
2:自己寫的demo學(xué)習(xí)下載git點(diǎn)擊:點(diǎn)我下載?這些知識我都驗(yàn)證過了笤休,可能有些專業(yè)術(shù)語不太明白尖飞,代碼里面也有解釋,直接看代碼,比什么都好
3:學(xué)習(xí)的路上政基,謝謝大家支持贞铣,和大家一起進(jìn)步