我的OpenGL ES學習之路(三):圖片顯示

這次的任務是把一張圖片用OpenGL ES的方式顯示到屏幕上涎才,部分功能使用了GLKit庫。

渲染上下文

先來看一下程序中定義的屬性:

定義的屬性.png

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夫植,kEAGLRenderingAPIOpenGLES2kEAGLRenderingAPIOpenGLES3油讯,分別對應著OpenGL ES 1.0详民,OpenGL ES 2.0OpenGL ES 3.0撞羽,這里我們使用2.0阐斜。

創(chuàng)建EAGLContext

設置當前上下文

設置controller的當前上下文是剛才創(chuàng)建的EAGLContext,設置顏色緩沖區(qū)的格式為RGBA8888的格式
設置當前view的上下文和顏色緩沖區(qū)格式

“頂點坐標” 和 “頂點屬性”

定義要繪制圖片的頂點坐標紋理坐標
OpenGL ES中坐標系是和iOS常用的Quartz 2D坐標系是不一樣的诀紊,Quartz 2D坐標系屬于右手坐標系谒出,OpenGL ES屬于右手坐標系隅俘。先說一下左手坐標系和右手坐標系。
伸出左手笤喳,讓拇指和食指成“L”形狀为居,拇指向右,食指向上杀狡,中指指向起那方蒙畴,這時就建立了一個“左手坐標系”,拇指呜象、食指和中指分表代表x膳凝、y、z軸的正方向恭陡〉乓簦“右手坐標系”就是用右手,如下圖所示:

“左手坐標系”和“右手坐標系”

在iOS開發(fā)中休玩,屏幕左上角是坐標原點著淆,往右是x軸正方向,往下是y軸正方向拴疤。而在OpenGL ES中永部,屏幕的中點是坐標原點,往右是x軸正方向呐矾,往下是y軸正方向苔埋,其中z軸的正方向是從屏幕往外的方向,如下圖所示:

OpenGL ES坐標系

根據(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ù)


頂點數(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之間的關系

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ù)據(jù)和頂點索引的緩存

“啟用頂點數(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里面的GLKVertexAttribPositionGLKVertexAttribTexCoord0分別表示頂點坐標紋理坐標兩個變量的屬性索引讶请。(頂點索引不是頂點數(shù)據(jù)祷嘶,這里我們不需要管這個值)

啟用頂點數(shù)組和指定頂點屬性
  • 上面的程序最需要注意的是變量的偏移很重要,我由于錯把GLfloat寫成CGFloat夺溢,導致圖片怎么都渲染不出來论巍。

設置紋理貼圖

我們把一張圖片加載成為要渲染的紋理,由于紋理坐標系是跟手機顯示的Quartz 2D坐標系的y軸正好相反风响,紋理坐標系使用左下角為原點嘉汰,往上為y軸的正值,往右是x軸的正值状勤,所以需要設置一下GLKTextureLoaderOriginBottomLeft鞋怀。
GLKit中使用GLKTextureInfo表示紋理對象双泪。

紋理

著色器

GLKit提供的GLKBaseEffect是對OpenGL ES中的著色器的封裝。
下面的代碼創(chuàng)建GLKBaseEffect密似,并且把GLKBaseEffect的紋理功能打開焙矛,然后將GLKTextureInfo賦值給GLKBaseEffect的紋理

GLKBaseEffect

渲染

GLKit提供了GLKViewDelegateGLKView里面有個delegate屬性残腌,我們需要實現(xiàn)這個協(xié)議村斟。這個協(xié)議的方法的刷新頻率和屏幕的刷新頻率是一致的,在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect這個方法中進行渲染操作.

GLKViewDelegate

在渲染操作中废累,我們來看幾個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ù)加油吧特石!

渲染結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盅蝗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姆蘸,更是在濱河造成了極大的恐慌墩莫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逞敷,死亡現(xiàn)場離奇詭異狂秦,居然都是意外死亡,警方通過查閱死者的電腦和手機推捐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門故痊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事愕秫】猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵戴甩,是天一觀的道長符喝。 經(jīng)常有香客問我,道長甜孤,這世上最難降的妖魔是什么协饲? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮缴川,結(jié)果婚禮上茉稠,老公的妹妹穿的比我還像新娘。我一直安慰自己把夸,他們只是感情好而线,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恋日,像睡著了一般膀篮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岂膳,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天誓竿,我揣著相機與錄音,去河邊找鬼谈截。 笑死筷屡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的簸喂。 我是一名探鬼主播毙死,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娘赴!你這毒婦竟也來了规哲?” 一聲冷哼從身側(cè)響起跟啤,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤诽表,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后隅肥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竿奏,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年腥放,在試婚紗的時候發(fā)現(xiàn)自己被綠了泛啸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秃症,死狀恐怖候址,靈堂內(nèi)的尸體忽然破棺而出吕粹,到底是詐尸還是另有隱情,我是刑警寧澤岗仑,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布匹耕,位于F島的核電站,受9級特大地震影響荠雕,放射性物質(zhì)發(fā)生泄漏稳其。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一炸卑、第九天 我趴在偏房一處隱蔽的房頂上張望既鞠。 院中可真熱鬧,春花似錦盖文、人聲如沸嘱蛋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浑槽。三九已至,卻和暖如春返帕,著一層夾襖步出監(jiān)牢的瞬間桐玻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工荆萤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镊靴,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓链韭,卻偏偏與公主長得像偏竟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敞峭,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容