使用完CocosCreator做了一個(gè)跑酷類的簡單游戲之后翠订,對圖像渲染之類的比較感興趣傲绣,之前學(xué)習(xí)過CoreAnimation之后顷链,把iOS里面的過程了解了一下煞抬,但是并沒有深入了解到GPU的內(nèi)容残拐。OpenGL ES就是查漏補(bǔ)缺吧错沃。
OpenGL基礎(chǔ)
CPU & GPU
CPU可以完成每秒十億次的運(yùn)算排截,但是它只能夠每秒讀寫內(nèi)存兩億次,所以要在每個(gè)數(shù)據(jù)上執(zhí)行5個(gè)或者更多的運(yùn)算,不然的話處理器的性能會處于次優(yōu)狀態(tài)矾克,這種狀態(tài)叫做“數(shù)據(jù)饑餓”州袒。同樣,GPU上更明顯畦徘,GPU每秒執(zhí)行數(shù)十億次但是每秒只能訪問內(nèi)存2億次蒸播。所以GPU總是受限于內(nèi)存的訪問性能上宿崭,并且通常需要在每塊數(shù)據(jù)上執(zhí)行10~30次運(yùn)算才不會影響整體的圖形輸出厨疙。
OpenGL流程
著色器是獨(dú)立運(yùn)行在GPU上的程序牙肝。
該流程圖展示說了OpenGL運(yùn)行的過程配椭,在我們定義了定點(diǎn)數(shù)據(jù)之后瘾境,首選是定點(diǎn)著色器處理定點(diǎn)數(shù)據(jù)犬绒,我們需要告訴OpenGL如果處理定點(diǎn)慷暂。圖元,是告訴OpenGL渲染怎樣的數(shù)據(jù),這里渲染的是三角形谜慌。幾何著色器生成輔助的幾何線段。光柵化是將幾何形狀轉(zhuǎn)換成像素莺奔,片段著色器決定最后每個(gè)像素的顏色(片段著色器包含3D場景的數(shù)據(jù)欣范,比如光照、陰影、光的顏色等等)熙卡,混合是混合不同層次的片段杖刷,因?yàn)樵谶@個(gè)階段要混合alpha值,所以就算片段著色器確定了最終的顏色驳癌,但是混合之后也可能不同。
著色器
OpenGL ES在ios上的使用過程封裝了著色器的使用過程役听,不需要我們自己編譯和鏈接著色器颓鲜。
紋理
2D紋理的坐標(biāo)是[0,1],將定點(diǎn)的坐標(biāo)和紋理的坐標(biāo)進(jìn)行點(diǎn)對點(diǎn)的綁定之后典予,片段著色器會對紋理進(jìn)行插值
我的理解甜滨,插值就是將紋理和頂點(diǎn)組成的幾何圖形之間進(jìn)行映射,從而判斷每個(gè)像素的顏色瘤袖。
MipMap(多級漸遠(yuǎn)紋理)是用來解決遠(yuǎn)處同一個(gè)物體的紋理問題衣摩,因?yàn)槿绻沁h(yuǎn)處的小的相同物體,如果使用大的貼圖會有浪費(fèi)內(nèi)存的問題捂敌,所以需要傳入小的貼圖艾扮。MipMap像下面這個(gè)樣子(后一個(gè)比前一個(gè)小1/4,超過一定的閾值就會使用相應(yīng)的圖片):
CPU & GPU
CPU可以完成每秒十億次的運(yùn)算占婉,但是它只能夠每秒讀寫內(nèi)存兩億次泡嘴,所以要在每個(gè)數(shù)據(jù)上執(zhí)行5個(gè)或者更多的運(yùn)算,不然的話處理器的性能會處于次優(yōu)狀態(tài)逆济,這種狀態(tài)叫做“數(shù)據(jù)饑餓”酌予。同樣,GPU上更明顯奖慌,GPU每秒執(zhí)行數(shù)十億次但是每秒只能訪問內(nèi)存2億次抛虫。所以GPU總是受限于內(nèi)存的訪問性能上,并且通常需要在每塊數(shù)據(jù)上執(zhí)行10~30次運(yùn)算才不會影響整體的圖形輸出简僧。
緩存(buffer)
GPU和CPU都有自己獨(dú)占的內(nèi)存區(qū)域建椰,OpenGL ES為兩個(gè)內(nèi)存區(qū)域間的數(shù)據(jù)交換定義了緩存(buffer)的概念。緩存是指圖形處理器能夠控制和管理的連續(xù)的RAM涎劈。幾乎所有程序提供給GPU的數(shù)據(jù)都應(yīng)該放入緩存中广凸。為緩存提供數(shù)據(jù)有如下7個(gè)步驟:
- Generate-> glGenBuffers()--請求OpenGL ES生成graphics processor控制的唯一標(biāo)志。
- Bind-> glBindBuffer()--告訴OpenGL ES使用緩存處理接下來的運(yùn)算蛛枚。
- Buffer Data-> glBufferData() or glBufferSubData()--告訴OpenGL ES為當(dāng)前綁定的buffer分配并初始化足夠多的連續(xù)內(nèi)存(通常是從CPU控制的內(nèi)存復(fù)制數(shù)據(jù)到GPU控制的內(nèi)存)谅海。
- Eable or Disable -> glEnableVertexAttribArray() or glDisableVertexAttribArray()--告訴OpenGL ES在接下來的渲染中是否使用緩存中的數(shù)據(jù)。
- Set Pointer -> glVertexAttribPointer()--告訴OpenGL ES buffer里的數(shù)據(jù)類型和訪問buffer數(shù)據(jù)的任何內(nèi)存偏移量蹦浦。
- Draw -> glDrawArrays() or glDrawElements()--告訴OpenGL ES使用當(dāng)前綁定的和可以使用的緩存來渲染部分或者全部場景扭吁。
- Delete -> glDeleteBuffers()--告訴OpenGL ES去刪除之前生成的緩存和釋放相關(guān)聯(lián)的資源。
幀緩存(frame buffer)
就像GPU提供數(shù)據(jù)的緩存一樣,接收渲染結(jié)果的緩沖區(qū)叫做幀緩存侥袜◎蚬睿可以同時(shí)存在很多的幀緩存,并且可以通過OpenGL ES讓GPU把渲染結(jié)果存儲到任意的幀緩存中枫吧。有front frame buffer和back frame buffer的概念浦旱,front frame buffer控制屏幕上顯示的像素顏色和布局,所以一般不會直接渲染到front frame buffer中九杂,這樣的話就會讓用戶看到?jīng)]有渲染完成的圖像颁湖。相反程序會把渲染結(jié)果保存到包含back frame buffer的其他frame buffer中,當(dāng)back frame buffer渲染成功之后就會立即與front frame buffer進(jìn)行交換例隆。
OpenGL ES context
Context 封裝了配置OpenGL ES保存在特定平臺的數(shù)據(jù)結(jié)構(gòu)信息甥捺。因?yàn)镺penGL ES是一個(gè)狀態(tài)機(jī),這意味著在一個(gè)程序中配置之后就會一直保留這個(gè)值镀层,直到程序修改了這個(gè)值镰禾。上下文信息可能被保存在CPU所控制的內(nèi)存中,也可能在GPU所控制的內(nèi)存中唱逢。OpenGL ES Context的內(nèi)部實(shí)現(xiàn)依賴于不同的嵌入式系統(tǒng)和GPU硬件吴侦,所以O(shè)penGL ES提供了標(biāo)準(zhǔn)的ANSI C語言函數(shù)來與Context交互。OpenGL ES Context會跟蹤用于渲染的幀緩存惶我,一會跟蹤用于幾何數(shù)據(jù)妈倔、顏色等的緩存。
Core Animation與OpenGL ES
iOS操作系統(tǒng)不支持直接訪問front frame buffer和back frame buffer绸贡,有操作系統(tǒng)來操作盯蝴,這樣的話方便隨時(shí)使用Core Animation Compositor來控制顯示的最終外觀。
這個(gè)也很好理解听怕,因?yàn)閏ore animation是一個(gè)單獨(dú)的進(jìn)程捧挺,不僅僅要生成我們自己應(yīng)用里面的layer,還要控制app之間的切換尿瞭,所以這是個(gè)系統(tǒng)的工作闽烙,沒必要暴露出來。
layer保存了所有繪制操作的結(jié)果声搁。比如黑竞,iOS提供了對象有效的在layer上繪制視頻,在layer上繪制淡入淡出的圖片疏旨。layer content也可以用Core Graphics來繪制很魂,也可以用OpenGL ES直接繪制。不過Core Animation Compositor使用的是OpenGL ES來管理GPU檐涝,混合圖層和賈環(huán)frame buffer的遏匆。所以一切通過Core Animation的繪制最終都會涉及OpenGL ES法挨。
GLKit
ios封裝了OpenGL ES,我們可以直接使用GLKit來使用OpenGL ES幅聘。分別使用GLKView和GLKViewController就可以直接操作OpenGL ES了凡纳。
以下是個(gè)人理解:GLKBaseEffect封裝了片段著色器程序,所以只要向該對象傳入紋理或者顏色帝蒿,就可以直接渲染出來荐糜。
OpenGL ES使用
OpenGL ES的使用過程就是操作Buffer的過程(見buffer一節(jié)的內(nèi)容)。下面具體講一下每個(gè)函數(shù):
- glGenBuffers(1,&name):這個(gè)方法是用來生成緩存葛超,數(shù)量是一個(gè)狞尔,傳入首地址標(biāo)識。
- glBindBuffer(GL_ ARRAY_BUFFER, name): 聲明是一個(gè)頂點(diǎn)數(shù)組類型的buffer巩掺,使用對應(yīng)name的緩存來處理接下來的運(yùn)算。
- glBufferData(GL_ARRAY_BUFFER,size,data,usage): 第一個(gè)是類型页畦,第二個(gè)是需要申請的內(nèi)存大小胖替,第三個(gè)是需要拷貝到GPU內(nèi)存的數(shù)據(jù),第四個(gè)是讀取頻率的標(biāo)識豫缨,用于內(nèi)存優(yōu)化
- glEnableVertexAttribArray(1)独令,默認(rèn)就禁止的,這里需要開啟一下好芭。
- glVertexAttribPointer(index,size,GL_FLOAT,GL_FALSE,stribe,pointer)燃箭。該函數(shù)告訴OpenGL怎樣處理傳入的頂點(diǎn)。因?yàn)樵陧旤c(diǎn)數(shù)組中舍败,一般還要保存紋理的UV數(shù)據(jù)招狸,所以需要指定類型(位置、顏色邻薯、紋理之類的)裙戏、大小、步幅以及偏移量厕诡,這樣的話就可以合理使用頂點(diǎn)累榜。
- 下面就是繪制頂點(diǎn)緩存和刪除頂點(diǎn)緩存了。
紋理
如果直接使用GLKit提供的方法來初始化紋理的話是非常簡單點(diǎn)的灵嫌,首先根據(jù)CGImage對象生成一個(gè)GLKTextureInfo對象壹罚,然后將該對象的Target和name賦值給baseEffect對象的對應(yīng)屬性就可以了。在準(zhǔn)備渲染的時(shí)候配置定點(diǎn)的讀取規(guī)則就可以了寿羞。
如果不使用GLKit的話猖凛,需要自己手動(dòng)生成、綁定和配置紋理稠曼,與生成緩存類似形病。
- glGenTexture(1,&id)生成紋理客年。
- glBindTexture(GL_ TEXTURE_2D, id),綁定紋理。
- glTexImage2D(GL_ TEXTURE,0,GL_RGBA,width,height,0,GL_ RGBA,GL_ UNSIGNED_BYTE, [imageData bytes]),將圖片數(shù)據(jù)保存成紋理
- glTextParameteri(GL_ TEXTURE_2D, GL _ TEXTURE_ MIN_ FILTER,GL_LINEAR)配置紋理的顯示漠吻。只要是紋理的大小大于或小于圖形大小量瓜,紋理的布局方式。GL_LINEAR表示的是取顏色的方式是取周圍的混合色途乃,這樣的紋理是模糊和漸變的绍傲。
透明度、混合和多重紋理
當(dāng)紋理計(jì)算出一個(gè)完全不透明的fragment color的時(shí)候耍共,會直接替換調(diào)frame buffer中的color render buffer中對應(yīng)的pixel color烫饼。如果存在不透明的話就需要混合。通過glEnable(GL_BLEND)來開啟混合试读。glBlendFunc(sourceFactor,destinationFactor)來設(shè)置混合函數(shù)杠纵。在每幀渲染的過程中需要替換baseEffect中的Target和name,然后[baseeffect prepareToDraw]會同步狀態(tài)钩骇,這樣的話就會繪制兩個(gè)紋理了比藻。
GLKit提供了多重紋理的渲染,可以直接給baseEffect指定兩個(gè)紋理倘屹,并且指定混合模式银亲,就可以直接混合多種紋理。這也間接可以看出CoreAnimation的層設(shè)計(jì)也是以此為基礎(chǔ)的纽匙。
燈光
GPU首先為每個(gè)三角形的每個(gè)定點(diǎn)執(zhí)行光線計(jì)算务蝠,然后把計(jì)算的結(jié)果插補(bǔ)在頂點(diǎn)之間來修改每個(gè)渲染的片元的最終顏色。因此模擬燈光的質(zhì)量和光滑度要取決于組成每個(gè)3D物體的頂點(diǎn)的數(shù)量烛缔。光線的計(jì)算是以三角形為單位的馏段,光線的向量與三角面的法向量的夾角決定了采光量的多少。所以在使用燈光的時(shí)候需要在定點(diǎn)中傳入法向量的值力穗。
GLKit使用燈光的話就是直接在baseEffect對象里面直接使用light0屬性進(jìn)行設(shè)置毅弧。設(shè)置了這個(gè)屬性之后,就算沒有打開当窗,顏色屬性也會失效够坐,而是變成燈光的顏色屬性,所以如果還要渲染其他有顏色的物體的時(shí)候崖面,需要新建baseEffect實(shí)例元咙。
將baseEffect當(dāng)做片段著色器,并且OpenGL ES Context是一個(gè)狀態(tài)機(jī)巫员,所以完全可以將前幾個(gè)定點(diǎn)繪制成黃色庶香,然后改變baseEffect的顏色填充屬性為綠色,再調(diào)用繪制简识,就會將剩下的定點(diǎn)繪制成綠色赶掖。
深度測試
深度測試是OpenGL自動(dòng)完成的感猛,我們只需要告訴OpenGL保存測試深度的大小,一般是16位和24位大小奢赂。然后調(diào)用深度函數(shù)來決定通過的門檻陪白。一般使用默認(rèn)的即可。在iOS里面的代碼設(shè)置如下:
glGenRenderbuffers(1, &depthRenderBuffer); // Step 1
glBindRenderbuffer(GL_RENDERBUFFER, // Step 2
depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, // Step 3
GL_DEPTH_COMPONENT16,
currentDrawableWidth,
currentDrawableHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, // Step 4
GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER,
depthRenderBuffer);
坐標(biāo)系統(tǒng)及變換
在以前使用3DMax(制作3D模型的軟件)的時(shí)候就會有很多的坐標(biāo)系可以選擇膳灶,在做游戲的時(shí)候也是可以選擇世界坐標(biāo)或者是本地坐標(biāo)咱士,原來是OpenGL規(guī)定的標(biāo)準(zhǔn)。
對物體的每個(gè)點(diǎn)做矩陣變換就是對整個(gè)物體的進(jìn)行移動(dòng)轧钓、縮放序厉、旋轉(zhuǎn)。OpenGL封裝好了變換的矩陣毕箍,只需要我們傳入改變的值就可以生成對應(yīng)的矩陣弛房。與iOS里面的CoreGraphics或者是CALayer的transform矩陣變換是一樣的。列舉一下常用的幾種變換:
- 圍繞一個(gè)點(diǎn)旋轉(zhuǎn):1)平移到所需要的旋轉(zhuǎn)中心而柑。2)施加所需要的旋轉(zhuǎn)庭再。3)使用與第一步相反的平移值平移回來
- 圍繞一個(gè)點(diǎn)縮放:1)平移到所需要的縮放中心。2)施加想要的縮放牺堰。3)使用與第一步相反的平移值平移回來
透視投影是以一個(gè)”平截投體“,例下圖所示:
其設(shè)置的代碼如下所示(所傳入的屬性就是確定平截投體的大小的):
self.baseEffect.transform.projectionMatrix =
GLKMatrix4MakeFrustum(
-1.0 , //left
1.0 , //right
-1.0, //bottom
1.0, //top
1.0, //near
60.0); //far
OpenGL ES剩余內(nèi)容
剩下的還有動(dòng)畫颅围、讀取模型伟葫、特效(天空盒、粒子等)這些內(nèi)容院促,這部分沒有仔細(xì)的實(shí)現(xiàn)demo筏养,而是大概看了一下,前段時(shí)間看了游戲相關(guān)的內(nèi)容常拓,這些部分感覺就是專門為游戲和3D定制的渐溶,和應(yīng)用類的ios開發(fā)有些靠不上,所以這里等日后再學(xué)習(xí)弄抬。動(dòng)畫就是在單位時(shí)間內(nèi)(取決于刷新頻率)對定點(diǎn)做矩陣變化茎辐,骨骼動(dòng)畫就是制定父節(jié)點(diǎn),父節(jié)點(diǎn)發(fā)生矩陣變化的時(shí)候同樣也要對子節(jié)點(diǎn)做變換掂恕,這樣的話就會有聯(lián)動(dòng)的效果了拖陆。因?yàn)閭鬟f到GPU的都是定點(diǎn)信息,那么模型其實(shí)就是頂點(diǎn)信息的文件懊亡,讀取的過程就是取出頂點(diǎn)依啰、紋理等信息。